/**
 * Purchase pure decider.
 *
 * PURITY CONTRACT (enforced by _purity-check.sh purchase):
 *   - No imports from '@/server/db'
 *   - No imports from 'do-client'
 *   - No imports from 'outbox-producer'
 *   - No imports from '@/server/payments'
 *   - No imports from '@/server/storage'
 *   - No imports from '@/server/email'
 *   - No imports from '@/server/push'
 *   - No Date.now() calls (caller provides `at` via event)
 *
 * Transition table:
 *   PENDING + charge_attempted(ok: true, registered) → COMPLETED
 *     effects: [complete-payment, enqueue-outbox(email_receipt), enqueue-outbox(email_details_qr),
 *               enqueue-outbox(club_membership.create), send-vendor-push, check-sold-out]
 *
 *   PENDING + charge_attempted(ok: true, guest) → COMPLETED
 *     effects: [complete-payment, enqueue-outbox(guest_email_receipt),
 *               enqueue-outbox(guest_email_details_qr), send-vendor-push, check-sold-out]
 *
 *   PENDING + charge_attempted(ok: false) → DELETED
 *     effects: [delete-pending]
 *
 * The multiVendorCharge call stays in the orchestrator (workflows/purchase.ts) because
 * it is non-idempotent external I/O. The decider runs post-charge.
 */

import type { PurchaseEvent } from './events.js';
import type { PurchaseEffect } from './effects.js';

// ─── State type ───────────────────────────────────────────────────────────────

/**
 * Represents the paymentStatus column states relevant to this workflow.
 * PENDING → COMPLETED (happy path) or row deleted on failure.
 * DELETED is a virtual terminal state — no DB row exists after this transition.
 */
export type PurchasePaymentState = 'PENDING' | 'COMPLETED' | 'DELETED';

// ─── Context ─────────────────────────────────────────────────────────────────

export interface PurchaseContext {
  state: PurchasePaymentState;
  purchaseId: string;
  dealId: string;
  finalQrTokenHash: string;
  qrPngUrl: string;
}

// ─── Result type ─────────────────────────────────────────────────────────────

export type DecideResult =
  | { ok: true; nextState: PurchasePaymentState; effects: PurchaseEffect[] }
  | { ok: false; error: 'INVALID_STATE' | 'ALREADY_TERMINAL'; from: PurchasePaymentState };

// ─── Terminal states ──────────────────────────────────────────────────────────

const TERMINAL_STATES = new Set<PurchasePaymentState>(['COMPLETED', 'DELETED']);

// ─── Decider ─────────────────────────────────────────────────────────────────

/**
 * Pure decider function.
 * Takes current context + event, returns next state + effects list.
 * Never throws — returns typed error result instead.
 */
export function decide(ctx: PurchaseContext, event: PurchaseEvent): DecideResult {
  const { state, purchaseId, dealId, finalQrTokenHash, qrPngUrl } = ctx;

  if (TERMINAL_STATES.has(state)) {
    return { ok: false, error: 'ALREADY_TERMINAL', from: state };
  }

  switch (event.kind) {
    case 'charge_attempted': {
      if (state !== 'PENDING') {
        return { ok: false, error: 'INVALID_STATE', from: state };
      }

      // ── Charge failed ──────────────────────────────────────────────────────
      if (!event.outcome.ok) {
        const effects: PurchaseEffect[] = [{ kind: 'delete-pending', purchaseId }];
        return { ok: true, nextState: 'DELETED', effects };
      }

      // ── Charge succeeded ───────────────────────────────────────────────────
      const effects: PurchaseEffect[] = [
        {
          kind: 'complete-payment',
          purchaseId,
          dealId,
          userId: event.userId,
          finalQrTokenHash,
          qrPngUrl,
        },
      ];

      const isGuest = event.guestEmail !== undefined;

      if (isGuest) {
        // Guest: receipt + details+QR emails (with magic link)
        effects.push({
          kind: 'enqueue-outbox',
          eventType: 'purchase.guest_email_receipt',
          aggregateType: 'purchase',
          aggregateId: purchaseId,
          payload: {
            purchaseId,
            guestEmail: event.guestEmail,
            dealTitle: event.dealTitle,
            amountPaid: event.amountPaid,
            qrPngUrl,
          },
        });
        effects.push({
          kind: 'enqueue-outbox',
          eventType: 'purchase.guest_email_details_qr',
          aggregateType: 'purchase',
          aggregateId: purchaseId,
          payload: {
            purchaseId,
            guestEmail: event.guestEmail,
            dealTitle: event.dealTitle,
            qrPngUrl,
            magicLinkUrl: event.magicLinkUrl,
            confirmationUrl: event.confirmationUrl,
          },
        });
      } else if (event.hasBuyerEmail) {
        // Registered with email: receipt + details+QR emails
        effects.push({
          kind: 'enqueue-outbox',
          eventType: 'purchase.email_receipt',
          aggregateType: 'purchase',
          aggregateId: purchaseId,
          payload: {
            purchaseId,
            userId: event.userId,
            dealTitle: event.dealTitle,
            amountPaid: event.amountPaid,
            qrPngUrl,
          },
        });
        effects.push({
          kind: 'enqueue-outbox',
          eventType: 'purchase.email_details_qr',
          aggregateType: 'purchase',
          aggregateId: purchaseId,
          payload: {
            purchaseId,
            userId: event.userId,
            dealTitle: event.dealTitle,
            qrPngUrl,
          },
        });
      }

      // Club membership: registered path only
      if (event.createClubMembership && event.userId) {
        effects.push({
          kind: 'enqueue-outbox',
          eventType: 'club_membership.create',
          aggregateType: 'purchase',
          aggregateId: purchaseId,
          payload: { userId: event.userId, vendorId: event.vendorId, purchaseId },
        });
      }

      // Vendor push (best-effort)
      effects.push({
        kind: 'send-vendor-push',
        vendorId: event.vendorId,
        dealTitle: event.dealTitle,
        amountPaid: event.amountPaid,
        purchaseId,
      });

      // Sold-out check
      effects.push({ kind: 'check-sold-out', dealId });

      return { ok: true, nextState: 'COMPLETED', effects };
    }

    default: {
      // TypeScript exhaustiveness guard — future-proofs when new event kinds are added
      const _exhaustive: never = event as never;
      void _exhaustive;
      return { ok: false, error: 'INVALID_STATE', from: state };
    }
  }
}
