/* Argus API client.
   Thin fetch wrapper that talks to the Pages Functions proxy at
   /argus/api/*. The proxy validates the session cookie + invite token
   on every request, so the browser doesn't carry an Authorization
   header — same model as the legacy app.js (`credentials: 'same-origin'`
   is the default). The proxy strips backend internals before responding.

   Errors are normalised to a small shape the UI can render:
     { ok: false, status: number, error: string, retry?: boolean }
*/

const API_BASE = "/argus/api";

const json = (body) => ({
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(body),
  credentials: "same-origin",
});

async function call(path, init) {
  let resp;
  try {
    resp = await fetch(`${API_BASE}${path}`, { credentials: "same-origin", ...init });
  } catch (e) {
    return { ok: false, status: 0, error: "network_unreachable", retry: true };
  }
  let data = null;
  const ct = resp.headers.get("content-type") || "";
  if (ct.includes("application/json")) {
    try { data = await resp.json(); } catch { data = null; }
  }
  if (!resp.ok) {
    return { ok: false, status: resp.status, error: (data && data.error) || `http_${resp.status}`, data };
  }
  return { ok: true, status: resp.status, data };
}

/* ── session / identity ──────────────────────────────────────── */
const Api = {
  me:               ()                  => call("/me"),
  signOut:          ()                  => call("/auth/logout", { method: "POST" }),
  login:            (email, password)   => call("/auth/login",        json({ email, password })),
  forgot:           (email)             => call("/auth/forgot",       json({ email })),
  reset:            (token, password)   => call("/auth/reset",        json({ reset_token: token, password })),
  setPassword:      (token, password)   => call("/auth/set-password", json({ token, password })),
  validateInvite:   ()                  => call("/invite"),
  loadShareInvite:  (token)             => call(`/share/${encodeURIComponent(token)}`),
  claimShare:       (token, payload)    => call(`/share/${encodeURIComponent(token)}/claim`, json(payload)),
  saveProfile:      (payload)           => call("/me", { ...json(payload), method: "PATCH" }),

  /* ── jobs ───────────────────────────────────────────────────── */
  analyse: (formData) =>
    call("/analyse", { method: "POST", body: formData }),
  batchAnalyse: (formData) =>
    call("/batch/analyse", { method: "POST", body: formData }),
  status:        (id)             => call(`/status/${encodeURIComponent(id)}`),
  batch:         (id)             => call(`/batch/${encodeURIComponent(id)}`),
  report:        (id)             => call(`/report/${encodeURIComponent(id)}`),
  myReports:     ()               => call("/my-reports"),
  deleteReport:  (id)             => call(`/reports/${encodeURIComponent(id)}`, { method: "DELETE" }),
  rerun:         (id)             => call(`/rerun/${encodeURIComponent(id)}`, { method: "POST" }),
  emailReport:   (id, payload)    => call(`/email/${encodeURIComponent(id)}`, json(payload)),
  supplement:    (id, idx, fd)    => call(`/supplement/${encodeURIComponent(id)}/${encodeURIComponent(idx)}`, { method: "POST", body: fd }),
  supplementStatus:(sid)          => call(`/supplement-status/${encodeURIComponent(sid)}`),

  /* v2.29.0 — live job control. pauseJob/resumeJob/cancelJob are
     cooperative — pipeline checks the flag between stages. notifyJob
     registers an email + channel; dispatchNotify is an opportunistic
     trigger the poller fires once it sees state==completed. */
  pauseJob:      (id)             => call(`/jobs/${encodeURIComponent(id)}/pause`,  { method: "POST" }),
  resumeJob:     (id)             => call(`/jobs/${encodeURIComponent(id)}/resume`, { method: "POST" }),
  cancelJob:     (id)             => call(`/jobs/${encodeURIComponent(id)}/cancel`, { method: "POST" }),
  notifyJob:     (id, payload)    => call(`/jobs/${encodeURIComponent(id)}/notify`, json(payload || {})),
  dispatchNotify:(id)             => call(`/jobs/${encodeURIComponent(id)}/notify`),

  /* ── feedback ─────────────────────────────────────────────── */
  feedback:      (id)             => call(`/feedback/${encodeURIComponent(id)}`),
  submitFeedback:(id, payload)    => call(`/feedback/${encodeURIComponent(id)}`, json(payload)),

  /* ── annotations (v2.25.0) ────────────────────────────────── */
  annotations:        (id)        => call(`/annotations/${encodeURIComponent(id)}`),
  saveAnnotation:     (id, payload) => call(`/annotations/${encodeURIComponent(id)}`, json(payload)),
  deleteAnnotation:   (id, aid)   => call(`/annotations/${encodeURIComponent(id)}/${encodeURIComponent(aid)}`, { method: "DELETE" }),

  /* ── library (v2.25.0) ────────────────────────────────────── */
  librarySources:   ()            => call("/library/sources"),
  sourceBacklinks:  (key)         => call(`/library/sources/${encodeURIComponent(key)}/backlinks`),

  /* ── client-extension lane (v2.26.0) ──────────────────────── */
  /* Per-job map of refs currently routed through the user's MV3
     browser extension. Returned shape:
       { items: [{ id, ref_index, doi, status, created_at,
                   delivered_at, error_code, error_message }, …] }
     status ∈ {pending, claimed, delivered, failed, auth_needed}. */
  clientFetchStatus: (id)         => call(`/client-fetch/status/${encodeURIComponent(id)}`),
  /* v2.26.9 — manual "retry this citation" trigger. Enqueues a
     fresh client_fetch_queue row for (job_id, ref_index). Useful
     after the user has manually solved a captcha or warmed a
     publisher session. */
  retryClientFetch: (id, ri)      => call(`/client-fetch/retry/${encodeURIComponent(id)}/${ri}`, { method: "POST" }),
  /* v2.27.0 — pre-flight publisher probes for the workspace
     pre-flight band. Returns:
       { items: [{ id, publisher, status, target_url, created_at,
                   delivered_at, error_code, error_message }, …] }
     status ∈ {pending, claimed, delivered, failed, auth_needed}. */
  clientFetchProbes: (id)         => call(`/client-fetch/probes/${encodeURIComponent(id)}`),

  /* ── admin ────────────────────────────────────────────────── */
  adminInvites:           ()                       => call("/admin/invites"),
  mintInvite:             (payload)                => call("/admin/invites", json(payload)),
  resetInviteUsage:       (token)                  => call(`/admin/invites/${encodeURIComponent(token)}/reset-usage`, { method: "POST" }),
  revokeInvite:           (token)                  => call(`/admin/invites/${encodeURIComponent(token)}`, { method: "DELETE" }),
  updateInvite:           (token, payload)         => call(`/admin/invites/${encodeURIComponent(token)}`, { ...json(payload), method: "PATCH" }),
  adminShares:            ()                       => call("/admin/shares"),
  mintShare:              (payload)                => call("/admin/shares", json(payload)),
  revokeShare:            (token)                  => call(`/admin/shares/${encodeURIComponent(token)}`, { method: "DELETE" }),
  adminOps:               ()                       => call("/admin/ops"),
  adminAuthHealth:        (live = false)           => call(`/admin/auth-health${live ? "?live=1" : ""}`),
  /* Per-node companion fleet health. Proposed shape:
       { nodes: [{ node_id, hostname, ip_class,
                   service_state ∈ {active, stale, down},
                   last_heartbeat_at, last_claim_at, last_delivery_at,
                   delivered_24h, failed_24h, claimed_24h,
                   publishers_authed?, extension_version }],
         last_updated: ISO }
     Returns 501/404 until backend ships — FleetCard renders wireframe. */
  adminFleet:             ()                       => call("/admin/fleet"),
  publisherHeartbeat:     ()                       => call("/admin/publisher-heartbeat"),
  pipelineEvents:         ({ jobId = "", offset = "", limit = 120 } = {}) => {
    const qs = new URLSearchParams();
    if (jobId) qs.set("job_id", jobId);
    if (offset !== "" && offset != null) qs.set("offset", String(offset));
    if (limit) qs.set("limit", String(limit));
    return call(`/admin/pipeline-events${qs.toString() ? `?${qs}` : ""}`);
  },
};

window.Api = Api;
