/* Claims Desk — status vocabulary.
   A value not listed here does not exist. If a screen needs one, it is added
   here first, with a tone grouping, a reason, and at least one fixture.
   Anything computed from other fields is NOT a value — derived flags live at
   the bottom of this file and take their clock as an argument.

   Policy constants live here too: they are part of this product's vocabulary
   (an adjuster's authority, the carrier's liability limits, the time bar), and
   nothing outside this folder may read them. */

const CLAIM_POLICY = {
  /* The legal filing deadline: one year from delivery, Hague-Visby art. III(6). */
  timeBarDays: 365,
  /* Above this the adjuster cannot decide alone — a manager signs off. */
  authorityLimitCents: 2500000,
  /* Package / weight limitation of liability, Hague-Visby art. IV(5)(a):
     the HIGHER of 666.67 SDR per package and 2 SDR per kilo. */
  packageLimitSdr: 666.67,
  weightLimitSdrPerKg: 2,
  /* IMF SDR/USD, 21 Aug 2026. One rate, one place. */
  sdrUsdRate: 1.34,
};

/* claim.status — where one cargo claim sits between filing and closure.
   Seven values, five tones, so the grouping answers the question the work
   queue asks: "can I act on it, and if not, who is holding it?"
     neutral = waiting on somebody outside the carrier (customer, surveyor)
               or already closed — nothing for the adjuster to do
     warning = waiting on the adjuster; this is the only actionable tone
     info    = waiting on another team inside the carrier (legal, treasury)
     success = the money has left us
   Deliberately absent because they are derived: `overdue` and `time_barred`
   (both computed from deliveredAt + timeBarDays), `blocked` (computed from the
   mandatory document set), `needs_signoff` (computed from the payable amount
   against authorityLimitCents). */
const CLAIM_STATUS = [
  { id: "awaiting_documents", label: "Awaiting documents", tone: "neutral" },
  { id: "awaiting_survey", label: "Awaiting survey", tone: "neutral" },
  { id: "ready_for_decision", label: "Ready for decision", tone: "warning" },
  { id: "awaiting_signoff", label: "Awaiting sign-off", tone: "info" },
  { id: "escalated_legal", label: "With legal", tone: "info" },
  { id: "approved_awaiting_payment", label: "Approved — awaiting payment", tone: "info" },
  { id: "paid", label: "Paid", tone: "success" },
  { id: "rejected", label: "Rejected", tone: "neutral" },
];
/* ids: "awaiting_documents" | "awaiting_survey" | "ready_for_decision" |
        "awaiting_signoff" | "escalated_legal" | "approved_awaiting_payment" |
        "paid" | "rejected"
   `rejected` is neutral, not danger: turning a claim down is a decision we
   made and can defend, not a failure event. `awaiting_signoff` is a stored
   state, not a derived one: the decision has been submitted and is sitting with
   the manager, which is not the same fact as "the amount is above the limit"
   (that is derived, and lives in CLAIM_FLAGS.needsSignoff). */

/* document.status — one item of evidence on one claim.
   neutral = asked for, not here yet · success = on file and usable ·
   danger = it arrived and is unusable, which is a failure someone must fix. */
const DOCUMENT_STATUS = [
  { id: "requested", label: "Requested", tone: "neutral" },
  { id: "received", label: "Received", tone: "success" },
  { id: "illegible", label: "Illegible", tone: "danger" },
];
/* ids: "requested" | "received" | "illegible" */

/* settlement.status — the payout of an approved claim.
   warning = waiting on the adjuster to schedule it · info = booked or moving ·
   success = the customer's bank has it · danger = it bounced back. */
const SETTLEMENT_STATUS = [
  { id: "unscheduled", label: "To schedule", tone: "warning" },
  { id: "scheduled", label: "Scheduled", tone: "info" },
  { id: "sent", label: "Sent", tone: "info" },
  { id: "cleared", label: "Cleared", tone: "success" },
  { id: "returned", label: "Returned", tone: "danger" },
];
/* ids: "unscheduled" | "scheduled" | "sent" | "cleared" | "returned" */

/* response.status — what the customer did with our offer.
   neutral = the ball is theirs · success = settled · warning = they have
   rejected the offer and it is back with the adjuster. Disputing is a
   deliberate act needing attention, not a system failure, so it is warning
   rather than danger. */
const RESPONSE_STATUS = [
  { id: "awaiting_response", label: "Awaiting response", tone: "neutral" },
  { id: "accepted", label: "Accepted", tone: "success" },
  { id: "disputed", label: "Disputed", tone: "warning" },
];
/* ids: "awaiting_response" | "accepted" | "disputed" */

/* recovery.status — pursuing the party that caused the damage. The mirror of
   claim.status, and it takes the same grouping: warning = waiting on us,
   info = waiting on them, success = money back, danger = liability refused,
   neutral = we closed it ourselves.
   Deliberately absent: `time_barred` (derived from fileByAt). */
const RECOVERY_STATUS = [
  { id: "to_file", label: "To file", tone: "warning" },
  { id: "filed", label: "Filed", tone: "info" },
  { id: "negotiating", label: "Negotiating", tone: "info" },
  { id: "recovered", label: "Recovered", tone: "success" },
  { id: "denied", label: "Denied", tone: "danger" },
  { id: "written_off", label: "Written off", tone: "neutral" },
];
/* ids: "to_file" | "filed" | "negotiating" | "recovered" | "denied" | "written_off" */

/* Classification lists — read through labelOf, never reaching a Badge. */
const DOCUMENT_TYPES = [
  { value: "port_photos", label: "Port photographs", mandatory: false },
  { value: "surveyor_report", label: "Surveyor's report", mandatory: true },
  { value: "packing_list", label: "Packing list", mandatory: true },
  { value: "commercial_invoice", label: "Commercial invoice", mandatory: true },
  { value: "delivery_receipt", label: "Delivery receipt", mandatory: true },
];
const MANDATORY_DOCUMENT_TYPES = DOCUMENT_TYPES.filter((d) => d.mandatory).map((d) => d.value);

const COUNTERPARTY_KINDS = [
  { value: "terminal", label: "Terminal" },
  { value: "haulier", label: "Road haulier" },
  { value: "packer", label: "Packer / stuffer" },
  { value: "lashing", label: "Lashing contractor" },
];

const REJECT_REASONS = [
  { value: "pre_shipment_damage", label: "Damage pre-dates shipment" },
  { value: "excepted_peril", label: "Excepted peril (art. IV(2))" },
  { value: "insufficient_evidence", label: "Evidence does not support the loss" },
  { value: "below_deductible", label: "Loss falls below the deductible" },
  { value: "time_barred", label: "Filed after the time bar" },
];

const DECISION_OUTCOMES = [
  { value: "approve_full", label: "Approve in full" },
  { value: "approve_part", label: "Approve in part" },
  { value: "reject", label: "Reject" },
];

/* What this desk raises, and therefore what a person can choose to be emailed
   about. One vocabulary: every notification carries one of these values, and
   the alerts screen offers exactly this list. */
const ALERT_KINDS = [
  { value: "time_bar", label: "Time bars", hint: "A claim moving inside 90 days of its bar, or past it." },
  { value: "documents", label: "Evidence", hint: "Documents arriving, and documents rejected as illegible." },
  { value: "signoff", label: "Sign-off", hint: "Decisions signed off, sent back, or still unsigned after a week." },
  { value: "payment", label: "Payments", hint: "Payments cleared by the bank, and payments returned." },
  { value: "response", label: "Customer responses", hint: "Offers accepted and offers disputed." },
  { value: "recovery", label: "Recoveries", hint: "Replies from terminals, hauliers and packers." },
];

/* Desk preferences — the shape of the settings this product actually acts on.
   Nothing is offered here that no screen reads. */
const DESK_DEFAULTS = { landing: "/", queueTab: "mine", openDecidable: true };

/* Derived flags. Clock injected, never read from the module. */
const CLAIM_FLAGS = {
  /* The record's own deadline — stored nowhere, computed everywhere. */
  timeBarAt: (claim) => claim.deliveredAt + CLAIM_POLICY.timeBarDays * 864e5,
  daysLeft: (claim, now) => Math.round((CLAIM_FLAGS.timeBarAt(claim) - now) / 864e5),
  isOpen: (claim) => ["paid", "rejected"].indexOf(claim.status) === -1,
  isTimeBarred: (claim, now) => CLAIM_FLAGS.timeBarAt(claim) < now,
  /* Whose desk it is on. */
  awaitsAdjuster: (claim) => claim.status === "ready_for_decision",
  /* Submitted and sitting with the manager. Not the same fact as needsSignoff,
     which is about the amount rather than about where the file is. */
  awaitsSignoff: (claim) => claim.status === "awaiting_signoff",
  /* How long the manager has had it. Only meaningful while awaitsSignoff. */
  signoffWaitingSince: (claim) => (claim.decision ? claim.decision.decidedAt : claim.claimedAt),
  missingMandatory: (claim) => MANDATORY_DOCUMENT_TYPES.filter((type) => {
    const doc = claim.documents.find((d) => d.type === type);
    return !doc || doc.status !== "received";
  }),
  evidenceComplete: (claim) => CLAIM_FLAGS.missingMandatory(claim).length === 0,
  /* THE act-ability predicate. The row, the tab count, the queue headline and
     the nav badge all call this one function — a claim is decidable when it is
     on the adjuster's desk AND every mandatory document is on file. */
  isDecidable: (claim) => CLAIM_FLAGS.awaitsAdjuster(claim) && CLAIM_FLAGS.evidenceComplete(claim),
  /* Open, but nothing the adjuster can do — recessed, never disabled. */
  isWaitingOnOthers: (claim) => CLAIM_FLAGS.isOpen(claim) && !CLAIM_FLAGS.awaitsAdjuster(claim),
  /* Authority. Takes the payable figure rather than recomputing it, so the
     screen and the flag can never disagree. */
  needsSignoff: (payableCents) => payableCents > CLAIM_POLICY.authorityLimitCents,
};

const SETTLEMENT_FLAGS = {
  exists: (claim) => !!claim.settlement,
  needsScheduling: (claim) => !!claim.settlement && ["unscheduled", "returned"].indexOf(claim.settlement.status) > -1,
  isReleased: (claim) => !!claim.settlement && ["sent", "cleared"].indexOf(claim.settlement.status) > -1,
};

const RESPONSE_FLAGS = {
  exists: (claim) => !!claim.response,
  isDisputed: (claim) => !!claim.response && claim.response.status === "disputed",
  awaitsCustomer: (claim) => !!claim.response && claim.response.status === "awaiting_response",
  /* What the customer wants beyond our offer. Null when they have not said. */
  gap: (claim) => (claim.response && claim.response.demandCents ? claim.response.demandCents - claim.settlement.amountCents : null),
};

const RECOVERY_FLAGS = {
  daysLeft: (recovery, now) => Math.round((recovery.fileByAt - now) / 864e5),
  isOpen: (recovery) => ["to_file", "filed", "negotiating"].indexOf(recovery.status) > -1,
  isClosed: (recovery) => !RECOVERY_FLAGS.isOpen(recovery),
  /* The act-ability predicate for recoveries — waiting on us to serve notice. */
  awaitsAdjuster: (recovery) => recovery.status === "to_file",
  shortfall: (recovery) => recovery.soughtCents - recovery.recoveredCents,
};

/* Transitions, named once and derived from the current status: one function per
   entity, called by the row action, the record header and the queue alike.
   kind: "primary" the record's normal path · "nav" moves to another screen ·
   "undo" instant + consequential, raises an undo toast · "confirm"
   irreversible, opens a confirm dialog · "form" opens a staged dialog. */
function claimTransitions(claim) {
  switch (claim.status) {
    case "awaiting_documents": return [{ id: "chase_documents", label: "Chase documents", kind: "undo", primary: true }];
    case "awaiting_survey": return [{ id: "chase_survey", label: "Chase surveyor", kind: "undo", primary: true }];
    case "ready_for_decision": return [
      { id: "record_decision", label: "Record decision", kind: "nav", primary: true },
      { id: "escalate", label: "Escalate to legal", kind: "confirm" },
    ];
    case "escalated_legal": return [{ id: "recall", label: "Recall from legal", kind: "confirm" }];
    /* The decision is with the manager. The adjuster cannot sign her own work,
       so neither transition here approves anything: she can chase it, or take
       it back and reopen the assessment. Both are reversible. */
    case "awaiting_signoff": return [
      { id: "chase_signoff", label: "Chase sign-off", kind: "undo", primary: true },
      { id: "withdraw_decision", label: "Withdraw decision", kind: "undo" },
    ];
    case "approved_awaiting_payment": return [{ id: "schedule_payment", label: "Schedule payment", kind: "undo", primary: true }];
    default: return [];
  }
}
function settlementTransitions(claim) {
  if (!claim.settlement) return [];
  switch (claim.settlement.status) {
    case "unscheduled": return [{ id: "schedule_payment", label: "Schedule payment", kind: "undo", primary: true }];
    case "returned": return [{ id: "schedule_payment", label: "Schedule payment", kind: "undo", primary: true }];
    case "scheduled": return [{ id: "send_payment", label: "Send payment", kind: "confirm", primary: true }];
    default: return [];
  }
}
function responseTransitions(claim) {
  if (!claim.response) return [];
  switch (claim.response.status) {
    case "awaiting_response": return [{ id: "record_response", label: "Record response", kind: "form", primary: true }];
    case "disputed": return [
      { id: "reopen_assessment", label: "Reopen assessment", kind: "undo", primary: true },
      { id: "escalate", label: "Escalate to legal", kind: "confirm" },
    ];
    default: return [];
  }
}
function recoveryTransitions(recovery) {  switch (recovery.status) {
    case "to_file": return [
      { id: "file_notice", label: "File notice", kind: "undo", primary: true },
      { id: "write_off", label: "Write off recovery", kind: "confirm", destructive: true },
    ];
    case "filed":
    case "negotiating": return [
      { id: "record_recovery", label: "Record recovery", kind: "form", primary: true },
      { id: "write_off", label: "Write off recovery", kind: "confirm", destructive: true },
    ];
    default: return [];
  }
}

/* One item of evidence. Requesting again is reversible; marking a document
   illegible is reversible too — it is a judgement, not a deletion. */
function documentTransitions(document) {
  switch (document.status) {
    case "requested": return [{ id: "request_again", label: "Request again", kind: "undo", primary: true }];
    case "illegible": return [{ id: "request_again", label: "Request again", kind: "undo", primary: true }];
    case "received": return [{ id: "mark_illegible", label: "Mark illegible", kind: "undo" }];
    default: return [];
  }
}

const STATUS_FIELDS = {
  "claim.status": { entity: "claim", field: "status", means: "Where the claim sits between filing and closure", values: CLAIM_STATUS },
  "document.status": { entity: "document", field: "status", means: "Whether one item of evidence is usable", values: DOCUMENT_STATUS },
  "settlement.status": { entity: "settlement", field: "status", means: "The payout of an approved claim", values: SETTLEMENT_STATUS },
  "response.status": { entity: "response", field: "status", means: "What the customer did with our offer", values: RESPONSE_STATUS },
  "recovery.status": { entity: "recovery", field: "status", means: "Pursuit of the party that caused the damage", values: RECOVERY_STATUS },
};

/* Coverage — values with no fixture yet, so nobody assumes they are unused:
   recovery.status `written_off`, reachable only by writing a recovery off,
   which the kit does. claim.status `awaiting_signoff` now has two fixtures
   (CLM-26-0195 and CLM-26-0207) behind the sign-off queue. Every other value in
   every field appears at least once in fixtures.js. */

Object.assign(window, {
  CLAIM_POLICY, CLAIM_STATUS, DOCUMENT_STATUS, SETTLEMENT_STATUS, RESPONSE_STATUS, RECOVERY_STATUS,
  DOCUMENT_TYPES, MANDATORY_DOCUMENT_TYPES, COUNTERPARTY_KINDS, REJECT_REASONS, DECISION_OUTCOMES,
  ALERT_KINDS, DESK_DEFAULTS,
  CLAIM_FLAGS, SETTLEMENT_FLAGS, RESPONSE_FLAGS, RECOVERY_FLAGS,
  claimTransitions, settlementTransitions, responseTransitions, recoveryTransitions, documentTransitions, STATUS_FIELDS,
});
