// Client store + API client. The dashboards read from here so they behave the
// same whether we're live (real Jira via /api/*) or creds-free (simulated).
//
// Every created ticket is mirrored to localStorage. When live, /api/tickets
// refreshes status from Jira and we merge it in; when creds-free, the mirror IS
// the store and simulated tickets drift through their lifecycle on their own.

(function () {
  const LS_KEY = "ns_tickets_v1";
  const listeners = new Set();

  function load() {
    try { return JSON.parse(localStorage.getItem(LS_KEY) || "[]"); }
    catch (e) { return []; }
  }
  function save(list) {
    try { localStorage.setItem(LS_KEY, JSON.stringify(list)); } catch (e) {}
    listeners.forEach((fn) => { try { fn(list); } catch (e) {} });
  }

  function all() { return load().sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0)); }
  function forReporter(email) { return all().filter((t) => !email || t.reporter === email); }
  function get(key) { return load().find((t) => t.key === key) || null; }

  function upsert(ticket) {
    const list = load();
    const i = list.findIndex((t) => t.key === ticket.key);
    if (i >= 0) list[i] = Object.assign({}, list[i], ticket);
    else list.unshift(ticket);
    save(list);
    return ticket;
  }

  function subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); }

  // ---- API client with graceful fallback ----------------------------------
  // A non-2xx or network error is treated as "not configured" and we fall back
  // to a local simulation, so the flow never dead-ends (mirrors the api/ 503).

  async function post(path, body) {
    const r = await fetch(path, {
      method: "POST", headers: { "content-type": "application/json" },
      body: JSON.stringify(body),
    });
    if (!r.ok) throw Object.assign(new Error("http " + r.status), { status: r.status });
    return r.json();
  }

  // Create a ticket. Returns { key, url, severity, friendlyStatus, live, duplicateOf? }.
  async function createTicket(draft) {
    try {
      const res = await post("/api/v1/tickets", draft);
      if (res && res.ok) {
        if (res.duplicateOf) return res; // let the UI offer subscribe-instead
        const t = normalizeCreated(draft, res, true);
        upsert(t);
        return Object.assign({ live: true }, res, { ticket: t });
      }
    } catch (e) { /* fall through to simulation */ }
    // ---- simulated fallback ----
    const t = simulateCreated(draft);
    upsert(t);
    return { ok: true, live: false, key: t.key, url: null, severity: t.severity,
             friendlyStatus: t.status, ticket: t };
  }

  function normalizeCreated(draft, res, live) {
    const s = window.NS.sev(draft.severity || res.severity);
    return {
      key: res.key, url: res.url || null, live: !!live,
      reporter: draft.reporter || "you@customer.com",
      account: draft.account || "Acme Co",
      title: draft.title || "Untitled issue",
      component: draft.component || "",
      severity: s.code,
      kbVerdict: draft.kbVerdict || (res && res.ticket && res.ticket.kbVerdict) || "",
      kbCitation: draft.kbCitation || (res && res.ticket && res.ticket.kbCitation) || "",
      statusCategory: "To Do",
      status: window.NS.friendlyStatus("To Do").label,
      createdAt: Date.now(),
      updatedAt: Date.now(),
      draft,
    };
  }

  // Deterministic-ish simulated key. No Date.now collisions in practice.
  function simulateCreated(draft) {
    const list = load();
    const n = 1000 + list.length + Math.floor((Date.now() / 1000) % 1000);
    const key = "SUP-" + n;
    const t = normalizeCreated(draft, { key, url: null }, false);
    return t;
  }

  // Refresh statuses from Jira when live; no-op (returns local) when creds-free.
  async function refresh(email) {
    try {
      const r = await fetch("/api/v1/tickets?reporter=" + encodeURIComponent(email || ""));
      if (r.ok) {
        const data = await r.json();
        if (data && Array.isArray(data.tickets)) {
          data.tickets.forEach((t) => upsert(t));
          return all();
        }
      }
    } catch (e) { /* creds-free: keep the local mirror */ }
    return all();
  }

  window.NS = Object.assign(window.NS || {}, {
    tickets: { all, forReporter, get, upsert, subscribe, createTicket, refresh },
  });
})();
