/**
 * Thin typed HTTP client. The extension uses it with a bearer token; the web
 * panel uses it same-origin with cookie auth (credentials: include).
 */

export class ApiError extends Error {
  status: number;
  detail: unknown;

  constructor(status: number, detail: unknown) {
    super(`API error ${status}: ${typeof detail === "string" ? detail : JSON.stringify(detail)}`);
    this.status = status;
    this.detail = detail;
  }
}

export interface ApiClientOptions {
  /** Base URL, e.g. "http://localhost:8001" or "" for same-origin. */
  baseUrl?: string;
  /** Bearer token (extension mode). Omit to rely on cookie auth. */
  token?: string | null;
  /** Called on 401 — hook to clear local session state. */
  onUnauthorized?: () => void;
}

export interface HttpClient {
  baseUrl: string;
  get<T>(path: string): Promise<T>;
  post<T>(path: string, body?: unknown): Promise<T>;
  patch<T>(path: string, body?: unknown): Promise<T>;
  del(path: string): Promise<void>;
  /** Multipart upload; `file` is sent as the "file" field. */
  upload<T>(path: string, file: File): Promise<T>;
  /** Low-level fetch with auth headers applied. */
  raw(path: string, init?: RequestInit): Promise<Response>;
}

export function createHttpClient(options: ApiClientOptions = {}): HttpClient {
  const baseUrl = options.baseUrl ?? "";

  async function raw(path: string, init: RequestInit = {}): Promise<Response> {
    const headers = new Headers(init.headers);
    if (options.token) headers.set("Authorization", `Bearer ${options.token}`);
    if (init.body && !(init.body instanceof FormData) && !(init.body instanceof Blob)) {
      headers.set("Content-Type", "application/json");
    }
    const response = await fetch(baseUrl + path, {
      credentials: options.token ? "omit" : "include",
      ...init,
      headers,
    });
    if (response.status === 401) options.onUnauthorized?.();
    return response;
  }

  async function parse<T>(response: Response): Promise<T> {
    if (response.status === 204) return undefined as T;
    const text = await response.text();
    let data: unknown = undefined;
    if (text) {
      try {
        data = JSON.parse(text);
      } catch {
        data = text;
      }
    }
    if (!response.ok) {
      const detail = (data as { detail?: unknown } | undefined)?.detail ?? data;
      throw new ApiError(response.status, detail);
    }
    return data as T;
  }

  return {
    baseUrl,
    raw,
    get: <T,>(path: string) => raw(path).then((r) => parse<T>(r)),
    post: <T,>(path: string, body?: unknown) =>
      raw(path, { method: "POST", body: JSON.stringify(body ?? {}) }).then((r) => parse<T>(r)),
    patch: <T,>(path: string, body?: unknown) =>
      raw(path, { method: "PATCH", body: JSON.stringify(body ?? {}) }).then((r) => parse<T>(r)),
    del: async (path: string) => {
      await parse(await raw(path, { method: "DELETE" }));
    },
    upload: <T,>(path: string, file: File) => {
      const form = new FormData();
      form.append("file", file);
      return raw(path, { method: "POST", body: form }).then((r) => parse<T>(r));
    },
  };
}