// ── API layer (cookie + CSRF auth) ───────────────────────────────────────────
// Auth tokens now live in httpOnly cookies set by the server, so there is no
// token in JS-readable storage. We send credentials with every request and
// attach the double-submit CSRF token (a JS-readable cookie) on mutations.
const API_BASE =
  window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"
    ? "http://localhost:3001/api"
    : "/api";

function getCookie(name) {
  const m = document.cookie.match("(?:^|; )" + name.replace(/([.$?*|{}()\[\]\\\/\+^])/g, "\\$1") + "=([^;]*)");
  return m ? decodeURIComponent(m[1]) : null;
}

// Make sure we have a CSRF token (the server sets it on any response, but fetch
// it explicitly if this is a very first interaction).
async function ensureCsrf() {
  let t = getCookie("cl_csrf");
  if (!t) {
    try {
      const r = await fetch(API_BASE + "/csrf", { credentials: "include" });
      const d = await r.json();
      t = d.csrfToken || getCookie("cl_csrf");
    } catch {
      /* ignore */
    }
  }
  return t;
}

const UNSAFE = { POST: 1, PUT: 1, PATCH: 1, DELETE: 1 };

async function apiFetch(endpoint, opts = {}) {
  const method = (opts.method || "GET").toUpperCase();
  const headers = { "Content-Type": "application/json", ...(opts.headers || {}) };

  if (UNSAFE[method]) {
    const token = getCookie("cl_csrf") || (await ensureCsrf());
    if (token) headers["X-CSRF-Token"] = token;
  }

  let res = await fetch(API_BASE + endpoint, { ...opts, headers, credentials: "include" });

  // Access token expired? Silently rotate once via the refresh cookie and retry.
  if (res.status === 401 && !opts._retry && !endpoint.startsWith("/auth/")) {
    const refreshed = await fetch(API_BASE + "/auth/refresh", { method: "POST", credentials: "include" });
    if (refreshed.ok) {
      return apiFetch(endpoint, { ...opts, _retry: true });
    }
  }
  return res;
}

// Convenience JSON helpers.
const apiGet = (endpoint) => apiFetch(endpoint).then((r) => r.json());
const apiSend = (endpoint, method, body) =>
  apiFetch(endpoint, { method, body: body != null ? JSON.stringify(body) : undefined });

// Global session holder so non-React helpers (sidebars, role gates) can read the
// current role/permissions without threading props everywhere. Populated by
// me()/login() and cleared on logout.
const Session = { user: null, permissions: [] };
function currentRole() {
  return Session.user ? Session.user.role : null;
}
function hasPerm(p) {
  if (currentRole() === "superadmin") return true;
  return (Session.permissions || []).indexOf(p) !== -1;
}

// Auth helpers used by the login/logout flows.
const authApi = {
  async login(email, password) {
    const r = await apiFetch("/auth/login", { method: "POST", body: JSON.stringify({ email, password }) });
    const data = await r.json().catch(() => ({}));
    if (!r.ok) throw new Error(data.error || "Sign in failed");
    await ensureCsrf();
    // Pull permissions for the freshly logged-in user.
    const full = await authApi.me();
    return full || data.user;
  },
  async me() {
    const r = await apiFetch("/auth/me");
    if (!r.ok) {
      Session.user = null;
      Session.permissions = [];
      return null;
    }
    const data = await r.json();
    Session.user = data.user;
    Session.permissions = data.permissions || [];
    return { ...data.user, permissions: Session.permissions };
  },
  async logout() {
    try {
      await apiFetch("/auth/logout", { method: "POST" });
    } finally {
      Session.user = null;
      Session.permissions = [];
    }
  },
};

Object.assign(window, { apiFetch, apiGet, apiSend, useApi, Spinner, authApi, Session, currentRole, hasPerm });

const useApi = (endpoint, fallback = []) => {
  const [data, setData] = React.useState(fallback);
  const [loading, setLoading] = React.useState(!!endpoint);
  React.useEffect(() => {
    if (!endpoint) {
      setLoading(false);
      setData(fallback);
      return;
    }
    setLoading(true);
    apiFetch(endpoint)
      .then((r) => (r.ok ? r.json() : Promise.reject(r)))
      .then((d) => {
        // Paginated list endpoints return { data, total } — unwrap to the array
        // so existing components that expect an array keep working.
        setData(d && typeof d === "object" && Array.isArray(d.data) ? d.data : d);
        setLoading(false);
      })
      .catch(() => setLoading(false));
  }, [endpoint]);
  return { data, loading };
};

const Spinner = () => (
  <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", padding: 40 }}>
    <div style={{ width: 32, height: 32, border: "3px solid var(--rule)", borderTopColor: "var(--coral)", borderRadius: "50%", animation: "spin .7s linear infinite" }} />
    <style>{`@keyframes spin{to{transform:rotate(360deg)}}`}</style>
  </div>
);
