/* Sentinel — live API client + adapters.
   Wires the prototype to the FastAPI backend (see ../sentinel-backend). The
   adapters reshape the API's responses into the exact mock shapes the existing
   components already consume, so the UI is unchanged — only the data source is.
   Backend RBAC is the source of truth; /rounds/{id}/changes is already filtered
   to the signed-in role (CEO sees High, Head sees the rest). */

const API_BASE = window.SENTINEL_API_BASE || "http://localhost:8000";

async function apiFetch(path, opts = {}) {
  const res = await fetch(API_BASE + path, {
    credentials: "include",
    headers: { "Content-Type": "application/json", ...(opts.headers || {}) },
    ...opts,
  });
  if (!res.ok) {
    // A4: an expired/invalid session on a protected call → return to sign-in.
    if (res.status === 401 && !path.startsWith("/auth") && !window.__sentinelSignout) {
      window.__sentinelSignout = true;
      setTimeout(() => window.location.reload(), 50);
    }
    let detail;
    try { detail = (await res.json()).detail; } catch (e) {}
    const err = new Error(typeof detail === "string" ? detail : "HTTP " + res.status);
    err.status = res.status;
    throw err;
  }
  if (res.status === 204) return null;
  const ct = res.headers.get("content-type") || "";
  return ct.includes("application/json") ? res.json() : res.text();
}

const Api = {
  devLogin: (email, role) => apiFetch("/auth/dev-login", { method: "POST", body: JSON.stringify({ email, role }) }),
  requestOtp: (email) => apiFetch("/auth/otp/request", { method: "POST", body: JSON.stringify({ email }) }),
  verifyOtp: (email, code) => apiFetch("/auth/otp/verify", { method: "POST", body: JSON.stringify({ email, code }) }),
  register: (email, name, role) => apiFetch("/auth/register", { method: "POST", body: JSON.stringify({ email, name, role }) }),
  registrations: () => apiFetch("/admin/registrations"),
  approveRegistration: (id, role) => apiFetch("/admin/registrations/" + id + "/approve", { method: "POST", body: JSON.stringify(role ? { role } : {}) }),
  rejectRegistration: (id) => apiFetch("/admin/registrations/" + id + "/reject", { method: "POST" }),
  me: () => apiFetch("/me"),
  logout: () => apiFetch("/auth/logout", { method: "POST" }),
  providers: () => apiFetch("/providers"),
  contractTypes: () => apiFetch("/contract-types"),
  createContract: (provider_id, contract_type_id) =>
    apiFetch("/contracts", { method: "POST", body: JSON.stringify({ provider_id, contract_type_id }) }),
  // multipart: let the browser set the Content-Type boundary (don't use apiFetch).
  uploadRound: (cid, file) => {
    const fd = new FormData();
    fd.append("file", file);
    return fetch(API_BASE + "/contracts/" + cid + "/rounds", { method: "POST", credentials: "include", body: fd })
      .then((r) => r.ok ? r.json() : r.json().then((e) => { throw new Error(e.detail || ("HTTP " + r.status)); }));
  },
  listContracts: () => apiFetch("/contracts"),
  getContract: (id) => apiFetch("/contracts/" + id),
  roundChanges: (rid) => apiFetch("/rounds/" + rid + "/changes"),
  decide: (cid, decision, finalText, note) =>
    apiFetch("/changes/" + cid + "/decision", { method: "POST", body: JSON.stringify({ decision, final_text: finalText, note }) }),
  finalize: (rid) => apiFetch("/rounds/" + rid + "/finalize", { method: "POST" }),
  outputs: (rid) => apiFetch("/rounds/" + rid + "/outputs"),
  sendOutputs: (rid) => apiFetch("/rounds/" + rid + "/send", { method: "POST" }),
  roundSource: (rid) => apiFetch("/rounds/" + rid + "/source"),
  roundDelegations: (rid) => apiFetch("/rounds/" + rid + "/delegations"),
  delegateRound: (rid, toEmail, revoke) => apiFetch("/rounds/" + rid + "/delegate", { method: "POST", body: JSON.stringify({ toEmail, revoke: !!revoke }) }),
  audit: () => apiFetch("/audit"),
  precedent: (cid) => apiFetch("/changes/" + cid + "/precedent"),
  slaDashboard: () => apiFetch("/analytics/sla"),
  registryAnalytics: () => apiFetch("/analytics/registry"),
  registryGrouped: () => apiFetch("/registry?group_by=clause"),
  routing: () => apiFetch("/admin/routing"),
  putRouting: (body) => apiFetch("/admin/routing", { method: "PUT", body: JSON.stringify(body) }),
  killSwitch: (on) => apiFetch("/admin/kill-switch", { method: "POST", body: JSON.stringify({ on }) }),
  killSwitchType: (contractTypeId, on) => apiFetch("/admin/kill-switch", { method: "POST", body: JSON.stringify({ contractTypeId, on }) }),
  killSwitchProvider: (providerId, on) => apiFetch("/admin/kill-switch", { method: "POST", body: JSON.stringify({ providerId, on }) }),
  templateClauses: (tid) => apiFetch("/templates/" + tid + "/clauses"),
  addClause: (tid, body) => apiFetch("/templates/" + tid + "/clauses", { method: "POST", body: JSON.stringify(body) }),
  patchClause: (tid, cid, body) => apiFetch("/templates/" + tid + "/clauses/" + cid, { method: "PATCH", body: JSON.stringify(body) }),
  enableAutoAccept: () => apiFetch("/admin/auto-accept/enable", { method: "POST" }),
  disableAutoAccept: () => apiFetch("/admin/auto-accept/disable", { method: "POST" }),
  auditVerify: () => apiFetch("/admin/audit/verify"),
  adminUsers: () => apiFetch("/admin/users"),
  inviteUser: (email, name, role) => apiFetch("/admin/users", { method: "POST", body: JSON.stringify({ email, name, role }) }),
  updateUserRole: (id, role) => apiFetch("/admin/users/" + id, { method: "PATCH", body: JSON.stringify({ role }) }),
  removeUser: (id) => apiFetch("/admin/users/" + id, { method: "DELETE" }),
  accessConfig: () => apiFetch("/admin/access-config"),
  putDomains: (domains) => apiFetch("/admin/domains", { method: "PUT", body: JSON.stringify({ domains }) }),
  clauseRules: () => apiFetch("/admin/clause-rules"),
  templates: () => apiFetch("/admin/templates"),
  // Download a golden template as a .docx (format reference, generated from its clauses).
  downloadTemplateSample: (tid, filename) =>
    fetch(API_BASE + "/admin/templates/" + tid + "/sample", { credentials: "include" })
      .then((r) => { if (!r.ok) throw new Error("Couldn't generate the sample"); return r.blob(); })
      .then((blob) => {
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url; a.download = filename || "golden_template.docx";
        document.body.appendChild(a); a.click(); a.remove();
        setTimeout(() => URL.revokeObjectURL(url), 1000);
      }),
  uploadTemplate: (contractTypeId, file) => {
    const fd = new FormData();
    fd.append("file", file);
    fd.append("contract_type_id", contractTypeId);
    return fetch(API_BASE + "/admin/templates", { method: "POST", credentials: "include", body: fd })
      .then((r) => r.ok ? r.json() : r.json().then((e) => { throw new Error(e.detail || ("HTTP " + r.status)); }));
  },
};

// Adapt API registry rows -> the Tracker's row shape (injects clause keys).
function adaptRegistryRows(items) {
  return (items || []).map((r) => {
    const clauseKey = ensureClause(r.clauseRef, r.clauseTitle, r.ourCounter, r.risk);
    return {
      id: r.id, clauseKey, clause: r.clauseTitle, clauseRisk: RISK[r.risk] ? r.risk : "medhigh",
      provider: r.provider, contract: "CT-" + String(r.roundId).replace(/-/g, "").slice(0, 6).toUpperCase(),
      type: r.contractType || "Contract", round: r.round || 1,
      providerAsk: r.providerAsk || "—", ourCounter: r.ourCounter || "—",
      final: r.final || null, state: r.state || "proposed",
      decidedBy: r.decidedBy || "—", date: r.decidedAt ? r.decidedAt.slice(0, 10) : "—",
    };
  });
}

/* ── Vocabulary maps (API → prototype) ─────────────────────────────────── */
const ROUND_STATUS_FE = {
  received: "classifying", classifying: "classifying", in_review: "in_review",
  decided: "in_review", generating: "in_review", sent: "cleared", error: "in_review",
};
const DISPOSITION_TO_ACTION = {
  accept: "accept", auto_accept: "accept", edit: "edit",
  reject: "reject", renegotiated: "reject", pending: null,
};

/* Inject a provider/clause into the prototype's global lookups on demand so
   components that index PROVIDERS[key] / CLAUSES[key] never hit undefined. */
function ensureProvider(name, type) {
  if (!name) name = "Unknown provider";
  const key = "p_" + name.toLowerCase().replace(/[^a-z0-9]+/g, "_");
  if (!PROVIDERS[key]) PROVIDERS[key] = { name, type: type || "Provider", tone: "neutral" };
  return key;
}
function ensureClause(ref, title, standard, risk) {
  const byTitle = (typeof CLAUSE_KEY_BY_TITLE !== "undefined") && title ? CLAUSE_KEY_BY_TITLE[title] : null;
  if (byTitle && CLAUSES[byTitle]) return byTitle;
  const key = "c_" + String(ref || title || "x").replace(/[^a-z0-9]+/gi, "_").toLowerCase();
  if (!CLAUSES[key]) {
    CLAUSES[key] = { no: ref || "—", title: title || "Clause", protected: false,
                     risk: risk || "medhigh", tier: "—", standard: standard || "" };
  }
  return key;
}

// The prototype's CHANGE_TYPE only knows edit/deletion/addition/missing;
// the API also emits 'substitution' (a kind of edit). Normalize so no component
// dereferences an undefined CHANGE_TYPE entry.
const CHANGE_TYPE_FE = { edit: "edit", substitution: "edit", deletion: "deletion", addition: "addition", missing: "missing" };

const _ROUTE_LABEL = { auto: "Auto-accept", head: "Head of SP Contracting",
                       head_ceo: "Head of SP · CEO notified", ceo: "CEO" };

// "How Sentinel assessed this" — provenance trail (rules ▸ AI ▸ confidence)
// built from the change's real classification fields. Not a separate AI call.
function _assessChecks(c) {
  const out = [];
  const riskLabel = (RISK[c.risk] && RISK[c.risk].label) || c.risk;
  out.push({ label: "Deterministic rules", ok: true,
    detail: `${riskLabel} risk → routed to ${_ROUTE_LABEL[c.routedTo] || c.routedTo}. Deterministic rules outrank the AI; protected clauses never auto-accept.` });
  if (c.rationale) out.push({ label: "AI assessment", ok: true, detail: c.rationale });
  if (c.delta) out.push({ label: "Semantic delta vs. standard", ok: !c.isSubstantive, detail: c.delta });
  if (typeof c.confidence === "number") out.push({ label: "Model confidence", ok: c.confidence >= 85,
    detail: `${c.confidence}% — ${c.confidence >= 85 ? "meets" : "below"} the auto-accept confidence floor.` });
  return out;
}

function adaptChange(c) {
  const clauseKey = ensureClause(c.clauseRef, c.clauseTitle, c.standard, c.risk);
  return {
    id: c.id,
    clause: clauseKey,
    clauseRef: c.clauseRef || (CLAUSES[clauseKey] || {}).no || "—",
    changeType: CHANGE_TYPE_FE[c.changeType] || "edit",
    risk: RISK[c.risk] ? c.risk : "medhigh",
    confidence: typeof c.confidence === "number" ? c.confidence : 0,
    summary: c.rationale || c.delta || "",
    delta: c.delta || "",
    standard: c.standard || "",
    provider: c.provider || "",
    counter: c.counter || "",
    checks: _assessChecks(c),
    precedent: null,
    auto: c.status === "auto_accept",
    routedTo: c.routedTo,
    status: c.status,
  };
}

function adaptContract(detail, round, changes) {
  const providerKey = ensureProvider(detail.providerName, detail.providerType);
  return {
    id: detail.id,
    // Short, human-friendly reference for display (the real uuid stays in `id`).
    ref: "CT-" + String(detail.id).replace(/-/g, "").slice(0, 6).toUpperCase(),
    provider: providerKey,
    type: detail.contractType || "Contract",
    round: round ? round.roundNo : 1,
    rm: "Relationship Manager",
    uploadedMs: round && round.uploadedAt ? Date.parse(round.uploadedAt) : Date.now(),
    status: round ? (ROUND_STATUS_FE[round.status] || "in_review") : "classifying",
    _roundId: round ? round.id : null,
    _roundStatus: round ? round.status : null,
    // Full-round tallies (all reviewers) — used to gate "Generate outputs".
    _pendingTotal: round && typeof round.pendingTotal === "number" ? round.pendingTotal : null,
    _changeTotal: round && typeof round.changeTotal === "number" ? round.changeTotal : null,
    changes: (changes || []).map(adaptChange),
  };
}

function adaptAudit(a) {
  const after = a.after && typeof a.after === "object" ? JSON.stringify(a.after) : "";
  return {
    id: String(a.id),
    actor: a.actor || "system",
    role: "",
    action: (a.action || "").split(".")[0] || "event",
    target: a.entityId ? String(a.entityId).slice(0, 8) : (a.entityType || ""),
    detail: (a.action || "") + (after ? " · " + after.slice(0, 90) : ""),
    mins: a.ts ? Math.max(0, Math.round((Date.now() - Date.parse(a.ts)) / 60000)) : 0,
    hash: a.hash ? String(a.hash).slice(0, 6) + "…" + String(a.hash).slice(-3) : "",
  };
}

/* Load the full contract list + each contract's role-filtered changes, shaped
   for the prototype. Returns { contracts, decisions }. */
async function loadContractsFromApi() {
  const list = (await Api.listContracts()).items || [];
  const contracts = await Promise.all(list.map(async (c) => {
    const detail = await Api.getContract(c.id);
    // Show the LATEST round (highest round_no) — the active negotiation round.
    const rounds = (detail.rounds || []).slice().sort((a, b) => (a.roundNo || 0) - (b.roundNo || 0));
    const round = rounds[rounds.length - 1];
    let changes = [];
    if (round) { try { changes = await Api.roundChanges(round.id); } catch (e) {} }
    return adaptContract(detail, round, changes);
  }));
  const decisions = {};
  contracts.forEach((ct) => ct.changes.forEach((ch) => {
    const action = DISPOSITION_TO_ACTION[ch.status];
    if (action) decisions[ct.id + ":" + ch.id] = { action, by: "Recorded", finalText: ch.counter, at: Date.now() };
  }));
  return { contracts, decisions };
}

Object.assign(window, { Api, adaptAudit, adaptRegistryRows, loadContractsFromApi, API_BASE });
