import type { ApplicationDetail, LicenseStatus } from '@ema-platform/api';
import { isSeafarerCertificate } from './license-types';

/**
 * Where an action is rendered. One tier per action, decided here rather than
 * by whoever happens to be laying out the page — that is what produced buttons
 * scattered down the right-hand column with no ordering principle.
 */
export type ActionTier =
  /** Approve / Request Adjustment / Reject. Decision Bar, right. Max three. */
  | 'primary'
  /** Claim, Assign, Escalate, Hold, Return. Decision Bar, left. */
  | 'workflow'
  /** Print, Export, Certificate, Audit, Copy Link. Overflow menu. */
  | 'secondary'
  /** Void, Revoke, Cancel. Overflow menu, separated, red, typed confirm. */
  | 'destructive';

export type ActionId =
  | 'claim'
  | 'assign'
  | 'assign-reviewer'
  | 'report-review'
  | 'assign-inspector'
  | 'report-inspection'
  | 'escalate'
  | 'hold'
  | 'resume'
  | 'complete-review'
  | 'approve-documents'
  | 'schedule-inspection'
  | 'reschedule-inspection'
  | 'record-inspection'
  | 'final-approve'
  | 'request-adjustment'
  | 'reject'
  | 'confirm-payment'
  | 'schedule-issuance'
  | 'issue-certificate'
  | 'print'
  | 'copy-link'
  | 'download-documents'
  | 'audit-trail';

export interface ActionDefinition {
  id: ActionId;
  tier: ActionTier;
  labelKey: string;
  /** Statuses the action can be fired from. Mirrors the API transition table. */
  from?: LicenseStatus[];
  /** Any one of these authorises it. Omitted means no permission needed. */
  permissions?: string[];
  /** Only one primary action is ever filled; everything else is light. */
  emphasis?: 'filled' | 'light' | 'subtle';
  color?: string;
  /** Requires a typed reason before it will submit. */
  requiresReason?: boolean;
  /** Cannot be undone — the confirmation says so explicitly. */
  irreversible?: boolean;
}

/**
 * Every action an officer can take, in one place.
 *
 * Actions absent from this list are absent because the API has no endpoint for
 * them. Void, Revoke and Cancel are the notable gaps: `SUSPEND_LICENSE` and
 * `CANCEL_LICENSE` permissions exist, but no route does either, so rendering
 * them would be a button that cannot work.
 */
export const ACTIONS: ActionDefinition[] = [
  // ------------------------------------------------------------- workflow
  {
    id: 'claim',
    tier: 'workflow',
    labelKey: 'review.actions.claim',
    // Mirrors the CLAIM transition's `from` list: an examined cert (CoC/CoP)
    // sits in ELIGIBILITY_PAID once its eligibility fee clears, not SUBMITTED.
    from: ['SUBMITTED', 'ELIGIBILITY_PAID'],
    permissions: ['can:claim:license-application'],
    emphasis: 'light',
  },
  /**
   * The push-model actions.
   *
   * `assign-reviewer` and `assign-inspector` start a stage; `report-review`
   * and `report-inspection` hand it back. An employee sees only the two report
   * actions — the assign pair is gated on ASSIGN_APPLICATION, which their
   * position type does not carry.
   */
  {
    id: 'assign-reviewer',
    tier: 'workflow',
    labelKey: 'review.actions.assignReviewer',
    from: ['SUBMITTED', 'ELIGIBILITY_PAID'],
    permissions: ['can:assign:license-application'],
    emphasis: 'filled',
  },
  {
    id: 'report-review',
    tier: 'workflow',
    labelKey: 'review.actions.reportReview',
    from: ['UNDER_REVIEW', 'UNDER_EVALUATION'],
    permissions: ['can:review:license-application'],
    emphasis: 'filled',
  },
  {
    id: 'assign-inspector',
    tier: 'workflow',
    labelKey: 'review.actions.assignInspector',
    from: ['REVIEW_REPORTED', 'UNDER_EVALUATION'],
    permissions: ['can:assign:license-application'],
    emphasis: 'filled',
  },
  {
    id: 'report-inspection',
    tier: 'workflow',
    labelKey: 'review.actions.reportInspection',
    from: ['INSPECTION_COMPLETED'],
    permissions: ['can:update:inspection'],
    emphasis: 'filled',
  },
  // Claim is deliberately absent here: an officer claims from the queue
  // (LicenseQueuePage), not from this detail page. That implementation is
  // separate — see LicenseQueuePage/actions.tsx — and is unaffected by this.
  {
    id: 'assign',
    tier: 'workflow',
    labelKey: 'review.actions.assign',
    from: [
      'SUBMITTED',
      'UNDER_REVIEW',
      'UNDER_EVALUATION',
      'INSPECTION_PENDING',
      'INSPECTION_COMPLETED',
      'INSPECTION_FAILED',
      'ON_HOLD',
    ],
    permissions: ['can:assign:license-application'],
    emphasis: 'subtle',
  },
  {
    id: 'escalate',
    tier: 'workflow',
    labelKey: 'review.actions.escalate',
    from: ['UNDER_REVIEW', 'UNDER_EVALUATION', 'INSPECTION_COMPLETED'],
    permissions: ['can:escalate:license-application'],
    emphasis: 'subtle',
    requiresReason: true,
  },
  {
    id: 'hold',
    tier: 'workflow',
    labelKey: 'review.actions.hold',
    from: [
      'UNDER_REVIEW',
      'UNDER_EVALUATION',
      'INSPECTION_PENDING',
      'INSPECTION_COMPLETED',
      'INSPECTION_FAILED',
    ],
    permissions: ['can:hold:license-application'],
    emphasis: 'subtle',
    requiresReason: true,
  },
  {
    id: 'resume',
    tier: 'workflow',
    labelKey: 'review.actions.resume',
    from: ['ON_HOLD'],
    permissions: ['can:hold:license-application'],
    emphasis: 'light',
  },

  // -------------------------------------------------------------- primary
  {
    id: 'complete-review',
    tier: 'primary',
    labelKey: 'review.actions.completeReview',
    from: ['UNDER_REVIEW'],
    permissions: [
      'can:review:license-application',
      'can:evaluate:license-application',
    ],
    emphasis: 'filled',
  },
  {
    id: 'approve-documents',
    tier: 'primary',
    labelKey: 'review.actions.approveDocuments',
    from: ['UNDER_EVALUATION'],
    permissions: ['can:evaluate:license-application'],
    emphasis: 'filled',
  },
  {
    id: 'schedule-inspection',
    tier: 'primary',
    labelKey: 'review.actions.scheduleInspection',
    // INSPECTION_FAILED: booking the re-inspection after a failed visit.
    from: ['INSPECTION_PENDING', 'INSPECTION_FAILED'],
    permissions: ['can:create:inspection'],
    emphasis: 'filled',
  },
  {
    id: 'reschedule-inspection',
    tier: 'primary',
    labelKey: 'review.actions.rescheduleInspection',
    from: ['INSPECTION_PENDING', 'INSPECTION_FAILED'],
    // Either permission: the team leader who booked the visit holds CREATE,
    // the inspector who has to attend holds UPDATE, and both have a reason to
    // move it. The server guards the same pair.
    permissions: ['can:create:inspection', 'can:update:inspection'],
    emphasis: 'light',
  },
  {
    id: 'record-inspection',
    tier: 'primary',
    labelKey: 'review.actions.recordInspection',
    from: ['INSPECTION_PENDING', 'INSPECTION_FAILED'],
    permissions: ['can:update:inspection'],
    emphasis: 'filled',
  },
  {
    id: 'final-approve',
    tier: 'primary',
    labelKey: 'review.actions.finalApprove',
    // ELIGIBILITY_PAID: an examined certificate (CoC/CoP) is decided straight
    // off the eligibility queue — no assignment step.
    from: [
      'INSPECTION_COMPLETED',
      'REVIEW_REPORTED',
      'INSPECTION_REPORTED',
      'UNDER_EVALUATION',
      'ELIGIBILITY_PAID',
    ],
    permissions: ['can:approve:license-application'],
    emphasis: 'filled',
    color: 'teal',
    irreversible: true,
  },
  {
    id: 'request-adjustment',
    tier: 'primary',
    labelKey: 'review.actions.requestAdjustment',
    from: [
      'UNDER_REVIEW',
      'UNDER_EVALUATION',
      'INSPECTION_COMPLETED',
      'REVIEW_REPORTED',
      'INSPECTION_REPORTED',
      'INSPECTION_FAILED',
      'ELIGIBILITY_PAID',
    ],
    permissions: ['can:request-adjustment:license-application'],
    emphasis: 'light',
    color: 'orange',
    requiresReason: true,
  },
  {
    id: 'reject',
    tier: 'primary',
    labelKey: 'review.actions.reject',
    from: [
      'UNDER_REVIEW',
      'UNDER_EVALUATION',
      'INSPECTION_PENDING',
      'INSPECTION_COMPLETED',
      'REVIEW_REPORTED',
      'INSPECTION_REPORTED',
      'INSPECTION_FAILED',
      'ELIGIBILITY_PAID',
    ],
    permissions: ['can:reject:license-application'],
    emphasis: 'light',
    color: 'red',
    requiresReason: true,
    irreversible: true,
  },
  // No `schedule-exam` or `record-exam-outcome`. CoC sittings are published as
  // master schedules and the candidate registers for one themselves, so an
  // officer action that seated a named applicant registered people who had
  // never asked. An outcome is what the exam engine computed and a supervisor
  // approved; a queue action taking pass/fail and a score let the back office
  // manufacture one with no paper behind it. Both endpoints are gone too.
  {
    id: 'confirm-payment',
    tier: 'primary',
    labelKey: 'review.actions.confirmPayment',
    from: ['PAID'],
    permissions: ['can:confirm:license-payment'],
    emphasis: 'filled',
    color: 'teal',
  },
  {
    id: 'schedule-issuance',
    tier: 'primary',
    labelKey: 'review.actions.scheduleIssuance',
    // Only reachable for a license type with `requiresIssuanceScheduling` —
    // everything else cascades straight to CERTIFICATE_ISSUED and never
    // shows PAYMENT_CONFIRMED with this action available (the server's
    // `availableEvents` omits it there, same as the rest of this list).
    from: ['PAYMENT_CONFIRMED'],
    permissions: ['can:schedule:license-issuance'],
    emphasis: 'filled',
    color: 'cyan',
  },
  {
    id: 'issue-certificate',
    tier: 'primary',
    labelKey: 'review.actions.issueCertificate',
    from: ['SCHEDULED'],
    permissions: ['can:issue:license-certificate'],
    emphasis: 'filled',
    color: 'teal',
  },

  // ------------------------------------------------------------ secondary
  { id: 'print', tier: 'secondary', labelKey: 'review.actions.print' },
  { id: 'copy-link', tier: 'secondary', labelKey: 'review.actions.copyLink' },
  {
    id: 'download-documents',
    tier: 'secondary',
    labelKey: 'review.actions.downloadDocuments',
  },
  { id: 'audit-trail', tier: 'secondary', labelKey: 'review.actions.auditTrail' },
];

export interface ResolvedAction extends ActionDefinition {
  /** False when the officer can see it but cannot fire it right now. */
  enabled: boolean;
  /**
   * Why it is disabled, already translated. Never null when `enabled` is
   * false — a greyed-out control with no explanation is the thing this whole
   * model exists to prevent.
   */
  disabledReason?: string;
}

export interface ResolveContext {
  detail: ApplicationDetail;
  currentUserId: string;
  can: (permissions?: string[]) => boolean;
  /** Translated strings for the disabled explanations. */
  reasons: {
    wrongStatus: string;
    notAssigned: string;
    noPermission: string;
    needsFlags: string;
    needsCapital: string;
    needsInspection: string;
    needsDocumentReviews: string;
    inspectionNotYetDue: string;
  };
  /** Number of sections/documents the officer has flagged for correction. */
  flaggedCount: number;
  /** True when an inspection is scheduled and awaiting a result. */
  hasPendingInspection: boolean;
  /**
   * True while the booked inspection's scheduled instant is still in the
   * future — a visit cannot have an outcome before it happens, so the record
   * button waits (the server refuses early results the same way).
   */
  inspectionNotYetDue: boolean;
  /**
   * False while any uploaded document is still unjudged or rejected. Approving
   * is a statement that every document was checked, so the button stays dead
   * until the officer has actually judged each one.
   */
  allDocumentsAccepted: boolean;
}

/**
 * Action ids that are workflow events, so `availableEvents` decides them.
 *
 * The rest (`schedule-inspection`, the secondary tools) are
 * screens and side effects rather than transitions, and the server has no
 * opinion on them — those keep using their own `from` list.
 */
const WORKFLOW_EVENT_IDS = new Set<ActionId>([
  'claim',
  'assign',
  'escalate',
  'hold',
  'resume',
  'complete-review',
  'approve-documents',
  'record-inspection',
  'final-approve',
  'request-adjustment',
  'reject',
  'confirm-payment',
  'schedule-issuance',
  'issue-certificate',
]);

/**
 * Which actions to render, and for each, whether it can fire and why not.
 *
 * Actions the user has no permission for are dropped entirely; actions that
 * are merely unavailable right now are kept and disabled with a reason, so the
 * officer can see what the next step would be rather than wondering whether
 * the screen is broken.
 *
 * For anything that is a workflow event, `detail.availableEvents` is the
 * authority on what fires from here — it comes from the same transition table
 * the server validates against, and it is workflow-profile aware. The local
 * `from` lists describe the licence course only, so a registration (which skips
 * evaluation and inspection, and approves straight out of UNDER_REVIEW) was
 * offered Complete Review — rejected server-side with
 * `event_not_available_for_service` — while Final Approve, the one action that
 * would work, was hidden.
 */
export function resolveActions(ctx: ResolveContext): ResolvedAction[] {
  const { detail, currentUserId, can, reasons } = ctx;
  const app = detail.application;
  const serverEvents = detail.availableEvents;
  // A seafarer's certificate is reviewed by whoever opens it. There is no
  // queue to claim it from and no reviewer to assign, so the two actions that
  // move it through one are dropped and ownership stops gating the decisions.
  const skipsAssignment = isSeafarerCertificate(app.licenseType?.key);

  return ACTIONS.filter((action) => can(action.permissions)).flatMap<ResolvedAction>(
    (action) => {
      // Status-scoped actions vanish outside their stage rather than piling up
      // as a column of permanently dead buttons.
      if (WORKFLOW_EVENT_IDS.has(action.id)) {
        // Tolerate an older server that sends no list rather than rendering an
        // empty action bar.
        if (serverEvents?.length && !serverEvents.includes(action.id)) return [];
        if (!serverEvents?.length && action.from && !action.from.includes(app.status)) {
          return [];
        }
      } else if (action.from && !action.from.includes(app.status)) {
        return [];
      }

      if (
        skipsAssignment &&
        (action.id === 'claim' ||
          action.id === 'assign' ||
          action.id === 'assign-reviewer')
      ) {
        return [];
      }

      // Scheduling and recording are the same slot at the same status; which
      // one applies depends on whether an inspection is already booked.
      if (action.id === 'schedule-inspection' && ctx.hasPendingInspection) return [];
      if (action.id === 'record-inspection' && !ctx.hasPendingInspection) return [];
      // The mirror of the scheduling gate: there is nothing to move until a
      // visit is booked, and once one is, moving it is the officer's only
      // option until the day arrives.
      if (action.id === 'reschedule-inspection' && !ctx.hasPendingInspection) {
        return [];
      }

      // The transition table doesn't know which types need an inspection, so
      // `availableEvents` lists approve-documents at UNDER_EVALUATION even for
      // types without one — where the server refuses it
      // (`inspection_not_required_use_final_approve`). Final Approve is the
      // real action there; don't render its dead twin.
      if (
        action.id === 'approve-documents' &&
        !app.licenseType?.inspectionRequired
      ) {
        return [];
      }

      const disabled = (reason: string): ResolvedAction => ({
        ...action,
        enabled: false,
        disabledReason: reason,
      });

      // Booked but not yet due: keep the button visible so the officer sees
      // the next step, disabled with the scheduled time as the reason.
      if (action.id === 'record-inspection' && ctx.inspectionNotYetDue) {
        return disabled(reasons.inspectionNotYetDue);
      }

      // Decisions belong to whoever holds the application.
      const needsOwnership =
        !skipsAssignment &&
        action.tier === 'primary' &&
        action.id !== 'confirm-payment';
      if (
        needsOwnership &&
        app.assignedOfficerId &&
        app.assignedOfficerId !== currentUserId
      ) {
        return disabled(reasons.notAssigned);
      }

      if (
        (action.id === 'approve-documents' || action.id === 'final-approve') &&
        !ctx.allDocumentsAccepted
      ) {
        return disabled(reasons.needsDocumentReviews);
      }

      if (action.id === 'request-adjustment' && ctx.flaggedCount === 0) {
        return disabled(reasons.needsFlags);
      }

      if (action.id === 'final-approve') {
        const threshold = app.licenseType?.capitalThreshold;
        const needsCapital = threshold != null && Number(threshold) > 0;
        if (needsCapital && app.capitalAmountVerified == null) {
          return disabled(reasons.needsCapital);
        }
        if (
          app.licenseType?.inspectionRequired &&
          app.status !== 'INSPECTION_COMPLETED'
        ) {
          return disabled(reasons.needsInspection);
        }
      }

      return { ...action, enabled: true };
    },
  );
}
