// Shared severity + status vocabulary. Single source of truth for the SEV1-4
// rubric, the SEV -> Jira priority map (must match JIRA_PRIORITY_MAP /
// api/_lib/jira.js — the AD project uses P0..P3, not Highest/High/Medium/Low), the friendly customer-facing
// status map, and the ETA cadence. Loaded early; everything hangs off window.NS.
//
// Design rule (from the research): the CUSTOMER never sees a SEV code — they see
// plain language ("logged as high-impact"). SEV codes + Jira priorities are the
// vendor-side abstraction, shown only on the triage board.

(function () {
  // impact x urgency rubric. `code` is internal, `label` is customer-facing.
  const SEVERITY = {
    SEV1: {
      code: "SEV1", rank: 1, label: "Critical", customer: "critical impact",
      emoji: "🔴", jiraPriority: "P0", etaMinutes: 30,
      rubric: "Outage, data loss, or security exposure. Everyone or a whole workflow is blocked.",
    },
    SEV2: {
      code: "SEV2", rank: 2, label: "High", customer: "high impact",
      emoji: "🟠", jiraPriority: "P1", etaMinutes: 120,
      rubric: "A major feature is broken with no good workaround. Significant blast radius.",
    },
    SEV3: {
      code: "SEV3", rank: 3, label: "Medium", customer: "moderate impact",
      emoji: "🟡", jiraPriority: "P2", etaMinutes: 60 * 8,
      rubric: "Degraded or partly broken, but a workaround exists. Limited blast radius.",
    },
    SEV4: {
      code: "SEV4", rank: 4, label: "Low", customer: "minor impact",
      emoji: "⚪", jiraPriority: "P3", etaMinutes: 60 * 48,
      rubric: "Minor or cosmetic. Nice to fix; nobody is blocked.",
    },
  };

  const SEV_ORDER = ["SEV1", "SEV2", "SEV3", "SEV4"];

  function sev(code) {
    return SEVERITY[String(code || "").toUpperCase()] || SEVERITY.SEV3;
  }

  // Friendly, calm status the customer sees, mapped from the live Jira status
  // category. Jira statuses vary per project, so we key off the status
  // *category* (To Do / In Progress / Done) which is stable.
  function friendlyStatus(jiraCategory, jiraName) {
    const c = String(jiraCategory || "").toLowerCase();
    if (c.includes("done") || c.includes("complete")) {
      return { key: "resolved", label: "Resolved", cls: "status-resolved",
               blurb: "Fixed and shipped — here's what we did." };
    }
    if (c.includes("progress") || c.includes("indeterminate")) {
      return { key: "progress", label: "Being worked on", cls: "status-progress",
               blurb: "Our team has picked this up." };
    }
    return { key: "logged", label: "Logged", cls: "status-logged",
             blurb: "We've received it and triaged the severity." };
  }

  // next-update contract: severity sets how soon we promise to check back.
  function nextUpdateLabel(sevCode) {
    const m = sev(sevCode).etaMinutes;
    if (m <= 30) return "within 30 min";
    if (m < 60 * 4) return `within ${Math.round(m / 60)} h`;
    if (m < 60 * 24) return "within the day";
    return `within ${Math.round(m / 60 / 24)} days`;
  }

  // ---- SLA ----------------------------------------------------------------
  // etaMinutes is the promise we made the customer ("next update within 2 h").
  // The same number is the team's clock: a ticket is BREACHED once it's older
  // than its ETA and still unresolved. Mirrored server-side in api/_lib/sla.js —
  // keep the two in step (the rubric above is the single source for etaMinutes).
  function slaDueAt(t) {
    const n = normalizeTicket(t);
    return n.createdAt + sev(n.severity).etaMinutes * 60000;
  }

  // { state: "resolved" | "breached" | "due-soon" | "ok", ms, label }
  // `ms` is time remaining (negative once breached).
  function slaState(t, now) {
    const n = normalizeTicket(t);
    if (n.statusCategory === "Done") return { state: "resolved", ms: 0, label: "Resolved" };
    const ms = slaDueAt(n) - (now || Date.now());
    if (ms < 0) return { state: "breached", ms, label: `Overdue by ${durLabel(-ms)}` };
    // "due soon" = inside the last quarter of the window (min 15 min).
    const window = sev(n.severity).etaMinutes * 60000;
    if (ms <= Math.max(15 * 60000, window * 0.25)) return { state: "due-soon", ms, label: `Due in ${durLabel(ms)}` };
    return { state: "ok", ms, label: `Due in ${durLabel(ms)}` };
  }

  function durLabel(ms) {
    const m = Math.max(1, Math.round(ms / 60000));
    if (m < 60) return m + "m";
    const h = Math.round(m / 60);
    if (h < 48) return h + "h";
    return Math.round(h / 24) + "d";
  }

  // The bug schema the intake agent must fill. `key` matches the tool schema in
  // api/support-agent.js; `required` fields gate ticket creation, the rest are
  // best-effort. Used by the chat's progress rail AND the confirm card.
  const SCHEMA_FIELDS = [
    { key: "title", label: "Summary", required: true },
    { key: "reproSteps", label: "Steps to reproduce", required: true },
    { key: "expected", label: "Expected", required: true },
    { key: "actual", label: "Actual", required: true },
    { key: "component", label: "Area / feature", required: false },
    { key: "environment", label: "Environment", required: false },
    { key: "errorText", label: "Error / console", required: false },
    { key: "blastRadius", label: "Who's affected", required: false },
    { key: "firstSeen", label: "First seen", required: false },
  ];

  function kbVerdictLabel(v) {
    return v === "known-issue" ? "Known issue"
      : v === "works-as-designed" ? "Working as designed"
      : v === "answered" ? "Answered from the KB"
      : "Knowledge-base match";
  }

  // ---- Normalization -------------------------------------------------------
  // Tickets arrive from three places (local mirror, POST response, live Jira
  // read) with slightly different shapes. Everything that renders a ticket goes
  // through this first, so the customer and admin views show the SAME structure
  // for every ticket, whatever its origin.
  const STATUS_CATEGORIES = ["To Do", "In Progress", "Done"];

  function normalizeTicket(raw) {
    const t = raw || {};
    const draft = t.draft || {};
    const statusCategory = STATUS_CATEGORIES.indexOf(t.statusCategory) >= 0 ? t.statusCategory : "To Do";
    const created = num(t.createdAt) || Date.now();
    const updated = num(t.updatedAt) || created;
    return {
      key: t.key || "—",
      url: t.url || null,
      live: !!t.live,
      // "pending" = awaiting admin validation (no Jira issue yet); "filed" = in Jira.
      // Defaults to "filed": tickets read from Jira and the seeded demo rows
      // carry no stage, and only intake marks a ticket pending.
      stage: t.stage === "pending" ? "pending" : t.stage === "rejected" ? "rejected" : "filed",
      pendingId: t.pendingId || null,
      // Only true when there's no Jira behind this deployment. A pending ticket
      // is not a demo ticket.
      demo: t.demo === true || (t.demo === undefined && !t.live && t.stage !== "pending"),
      title: t.title || "(untitled)",
      severity: sev(t.severity).code,
      statusCategory,
      status: t.status || statusCategory,
      component: t.component || draft.component || "",
      account: t.account || "",
      reporter: t.reporter || "",
      createdAt: created,
      updatedAt: updated,
      kbVerdict: t.kbVerdict || draft.kbVerdict || "",
      kbCitation: t.kbCitation || draft.kbCitation || "",
      kbInternal: !!(t.kbInternal || draft.kbInternal),
      // detail fields — present for locally-filed tickets, blank for Jira reads
      reproSteps: t.reproSteps || draft.reproSteps || "",
      expected: t.expected || draft.expected || "",
      actual: t.actual || draft.actual || "",
      environment: t.environment || draft.environment || "",
      errorText: t.errorText || draft.errorText || "",
      blastRadius: t.blastRadius || draft.blastRadius || "",
      severityRationale: t.severityRationale || draft.severityRationale || "",
    };
  }

  // A ticket's lifecycle as an ordered, uniform event list. Jira gives us only
  // created/updated + the current category, so we derive the milestones we can
  // prove and mark the rest as pending — the same shape for every ticket.
  function buildHistory(t) {
    const n = normalizeTicket(t);
    const s = sev(n.severity);
    const cat = n.statusCategory;
    const reached = (c) => STATUS_CATEGORIES.indexOf(cat) >= STATUS_CATEGORIES.indexOf(c);

    const events = [
      { key: "reported", label: "Reported", at: n.createdAt, done: true,
        detail: n.reporter ? `by ${n.reporter}` : "via the support agent" },
      { key: "triaged", label: "Triaged", at: n.createdAt, done: true,
        detail: `Graded ${s.label.toLowerCase()} impact · ${s.jiraPriority} priority` },
      { key: "progress", label: "Being worked on", at: reached("In Progress") ? n.updatedAt : null,
        done: reached("In Progress"), detail: reached("In Progress") ? "Picked up by the team" : "Waiting for the team to pick it up" },
      { key: "resolved", label: "Resolved", at: cat === "Done" ? n.updatedAt : null,
        done: cat === "Done", detail: cat === "Done" ? "Fixed and shipped" : `Next update ${nextUpdateLabel(n.severity)}` },
    ];
    return events;
  }

  function num(v) { const n = Number(v); return isFinite(n) && n > 0 ? n : 0; }

  window.NS = Object.assign(window.NS || {}, {
    SEVERITY, SEV_ORDER, sev, friendlyStatus, nextUpdateLabel, SCHEMA_FIELDS, kbVerdictLabel,
    normalizeTicket, buildHistory, STATUS_CATEGORIES, slaDueAt, slaState, durLabel,
  });
})();
