// Thin fetch wrapper around the Skori API. The access token lives in memory
// only (never localStorage) to limit XSS blast radius; the refresh token is an
// httpOnly cookie handled entirely by the browser.

const API_BASE = "/api/v1";

let accessToken: string | null = null;
let onUnauthorized: (() => void) | null = null;

export function setAccessToken(token: string | null) {
  accessToken = token;
}

export function getAccessToken(): string | null {
  return accessToken;
}

export function setOnUnauthorized(cb: (() => void) | null) {
  onUnauthorized = cb;
}

export class ApiError extends Error {
  constructor(
    public status: number,
    message: string,
  ) {
    super(message);
  }
}

function rawFetch(
  path: string,
  opts: RequestInit,
  withAuth: boolean,
): Promise<Response> {
  const headers: Record<string, string> = {
    "content-type": "application/json",
    ...((opts.headers as Record<string, string>) || {}),
  };
  if (withAuth && accessToken) {
    headers.authorization = `Bearer ${accessToken}`;
  }
  return fetch(API_BASE + path, {
    ...opts,
    headers,
    credentials: "include",
  });
}

async function tryRefresh(): Promise<boolean> {
  try {
    const res = await rawFetch("/auth/refresh", { method: "POST" }, false);
    if (!res.ok) return false;
    const data = await res.json();
    accessToken = data.accessToken;
    return true;
  } catch {
    return false;
  }
}

interface CallOptions {
  auth?: boolean;
  retry?: boolean;
}

export async function apiFetch(
  path: string,
  opts: RequestInit = {},
  { auth = true, retry = true }: CallOptions = {},
): Promise<Response> {
  let res = await rawFetch(path, opts, auth);
  if (res.status === 401 && auth && retry) {
    const refreshed = await tryRefresh();
    if (refreshed) {
      res = await rawFetch(path, opts, auth);
    } else {
      onUnauthorized?.();
    }
  }
  return res;
}

export async function apiJson<T>(
  path: string,
  opts: RequestInit = {},
  cfg: CallOptions = {},
): Promise<T> {
  const res = await apiFetch(path, opts, cfg);
  if (!res.ok) {
    let message = "request_failed";
    try {
      const body = await res.json();
      message = body.error || message;
    } catch {
      /* ignore */
    }
    throw new ApiError(res.status, message);
  }
  return res.json() as Promise<T>;
}
