/**
 * Support-case pure decider.
 *
 * PURITY CONTRACT (mirrors refund): no DB, env, wall-clock reads, fetch, queues,
 * payments, email, or push. Timestamps must be provided by event/ctx.
 *
 * Case transition table:
 *   opened + normal case_opened -> vendor_review
 *   opened + fraud/safety/escape case_opened -> escalated
 *   vendor_review + vendor_offer_submitted -> vendor_offered
 *   vendor_review + offer_auto_executed -> resolved
 *   vendor_review + escalation_requested -> escalated
 *   vendor_offered + customer_accepted -> resolved
 *   vendor_offered + customer_rejected/timeout -> escalated
 *   escalated -> ai_handling | human_review
 *   ai_handling -> awaiting_info | resolved | human_review
 *   awaiting_info -> ai_handling
 *   human_review -> resolved | vendor_review
 *   resolved -> closed
 *   closed -> reopened
 *   reopened -> ai_handling | escalated
 *
 * Ticket transition table:
 *   open -> ai_handling | escalated
 *   ai_handling -> resolved | awaiting_user | escalated
 *   awaiting_user -> ai_handling | human_handling
 *   escalated -> awaiting_agent
 *   awaiting_agent -> human_handling
 *   human_handling -> awaiting_user | resolved
 *   resolved -> closed | reopened
 *   closed -> reopened
 *   reopened -> ai_handling
 */

import type {
  CaseStatus,
  CaseTransitionContext,
  SupportCaseEvent,
  TicketState,
  TicketTransitionContext,
} from './events.js';
import type { SupportCaseEffect } from './effects.js';

export type SupportCaseState = CaseStatus | TicketState;

export type DecideResult<TState extends SupportCaseState> =
  | { ok: true; nextState: TState; effects: SupportCaseEffect[] }
  | { ok: false; error: 'INVALID_STATE'; from: TState; message: string };

export interface SupportCaseContext {
  state: CaseStatus;
  reopenCount?: number;
}

const TICKET_LEGAL: Record<TicketState, readonly TicketState[]> = {
  open: ['ai_handling', 'escalated'],
  ai_handling: ['resolved', 'awaiting_user', 'escalated'],
  awaiting_user: ['ai_handling', 'human_handling'],
  escalated: ['awaiting_agent'],
  awaiting_agent: ['human_handling'],
  human_handling: ['awaiting_user', 'resolved'],
  resolved: ['closed', 'reopened'],
  closed: ['reopened'],
  reopened: ['ai_handling'],
};

const CASE_MAP: Record<CaseStatus, Array<(ctx: CaseTransitionContext) => CaseStatus | null>> = {
  opened: [
    (c) => (c.category === 'fraud' || c.category === 'safety' || c.escapeUsed ? 'escalated' : null),
    (c) =>
      c.category !== 'fraud' && c.category !== 'safety' && !c.escapeUsed ? 'vendor_review' : null,
    (c) => (c.category === 'fraud' || c.category === 'safety' ? 'escalated' : null),
    () => 'vendor_review',
  ],
  vendor_review: [
    () => 'vendor_offered',
    (c) => (c.autoExecuted ? 'resolved' : null),
    () => 'escalated',
  ],
  vendor_offered: [(c) => (c.autoExecuted ? 'resolved' : null), () => 'escalated'],
  escalated: [() => 'ai_handling', () => 'human_review'],
  ai_handling: [() => 'awaiting_info', () => 'resolved', () => 'human_review'],
  awaiting_info: [() => 'ai_handling'],
  human_review: [() => 'resolved', () => 'vendor_review'],
  resolved: [() => 'closed'],
  closed: [() => 'reopened'],
  reopened: [() => 'ai_handling', () => 'escalated'],
};

export function listLegalTicketTransitions(from: TicketState): readonly TicketState[] {
  return TICKET_LEGAL[from] ?? [];
}

export function listLegalCaseTransitions(from: CaseStatus): CaseStatus[] {
  const rules = CASE_MAP[from] ?? [];
  const out = new Set<CaseStatus>();
  const probes: CaseTransitionContext[] = [
    {},
    { category: 'fraud' },
    { category: 'safety' },
    { autoExecuted: true },
    { category: 'quality_issue' },
  ];
  for (const rule of rules) {
    for (const probe of probes) {
      const next = rule(probe);
      if (next !== null) out.add(next);
    }
  }
  return [...out];
}

function canCaseTransition(from: CaseStatus, to: CaseStatus, ctx: CaseTransitionContext): boolean {
  const rules = CASE_MAP[from] ?? [];
  return rules.some((rule) => rule(ctx) === to);
}

function canTicketTransition(
  from: TicketState,
  to: TicketState,
  ctx: TicketTransitionContext,
): { ok: true } | { ok: false; reason: string } {
  if (!TICKET_LEGAL[from]?.includes(to)) {
    return { ok: false, reason: `illegal_transition:${from}->${to}` };
  }
  if (
    to === 'reopened' &&
    ctx.reopenLimit !== undefined &&
    (ctx.reopenCount ?? 0) >= ctx.reopenLimit
  ) {
    return { ok: false, reason: 'reopen_limit_exceeded' };
  }
  return { ok: true };
}

export function decideCase(
  ctx: { state: CaseStatus },
  event: Extract<SupportCaseEvent, { kind: 'case_transition_requested' }>,
): DecideResult<CaseStatus> {
  const transitionCtx = event.ctx ?? {};
  if (!canCaseTransition(ctx.state, event.to, transitionCtx)) {
    return {
      ok: false,
      error: 'INVALID_STATE',
      from: ctx.state,
      message: `Transition ${ctx.state} -> ${event.to} is not legal`,
    };
  }

  return {
    ok: true,
    nextState: event.to,
    effects: [
      {
        kind: 'record-transition',
        parentType: 'case',
        parentId: event.caseId,
        fromState: ctx.state,
        toState: event.to,
        actorType: event.actorType,
        actorId: event.actorId,
        reason: event.reason,
        metadata: event.metadata ?? {},
      },
      { kind: 'set-case-status', caseId: event.caseId, status: event.to, at: event.at },
    ],
  };
}

export function decide(
  ctx: SupportCaseContext,
  event: Exclude<SupportCaseEvent, { kind: 'ticket_transition_requested' }>,
): DecideResult<CaseStatus> {
  switch (event.kind) {
    case 'case_transition_requested':
      return decideCase(ctx, event);
    case 'case_message_posted':
      if (ctx.state === 'closed') {
        return {
          ok: false,
          error: 'INVALID_STATE',
          from: ctx.state,
          message: 'Cannot post message to a closed case',
        };
      }
      return { ok: true, nextState: ctx.state, effects: [] };
    case 'resolution_requested':
      if (ctx.state === 'closed') {
        return {
          ok: false,
          error: 'INVALID_STATE',
          from: ctx.state,
          message: 'Cannot resolve a closed case',
        };
      }
      return {
        ok: true,
        nextState: ctx.state,
        effects:
          event.amountCents !== null &&
          event.amountCents > 0 &&
          (event.outcome === 'refund_full' || event.outcome === 'refund_partial')
            ? [
                {
                  kind: 'enqueue-outbox',
                  aggregateType: 'case',
                  aggregateId: event.caseId,
                  eventType: 'support-case.refund.requested',
                  payload: {
                    caseId: event.caseId,
                    purchaseId: event.purchaseId,
                    amountCents: event.amountCents,
                    outcome: event.outcome,
                    executedBy: event.executedBy,
                    email: event.email ?? null,
                  },
                },
              ]
            : [],
      };
  }
}

export function decideTicket(
  ctx: { state: TicketState },
  event: Extract<SupportCaseEvent, { kind: 'ticket_transition_requested' }>,
): DecideResult<TicketState> {
  const check = canTicketTransition(ctx.state, event.to, {
    actorType: event.actorType,
    reopenCount: event.reopenCount,
    reopenLimit: event.reopenLimit,
  });
  if (!check.ok) {
    return { ok: false, error: 'INVALID_STATE', from: ctx.state, message: check.reason };
  }

  return {
    ok: true,
    nextState: event.to,
    effects: [
      {
        kind: 'record-transition',
        parentType: 'ticket',
        parentId: event.ticketId,
        fromState: ctx.state,
        toState: event.to,
        actorType: event.actorType,
        actorId: event.actorId,
        reason: event.reason,
        metadata: event.metadata ?? {},
      },
      { kind: 'set-ticket-status', ticketId: event.ticketId, status: event.to, at: event.at },
    ],
  };
}
