import { clearToken, getToken } from "@/lib/token";

interface ApiEnvelope<T> {
  success: boolean;
  message: string;
  data?: T;
  details?: unknown;
}

export class ApiClientError extends Error {
  statusCode: number;
  details?: unknown;

  constructor(statusCode: number, message: string, details?: unknown) {
    super(message);
    this.name = "ApiClientError";
    this.statusCode = statusCode;
    this.details = details;
  }
}

const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "";

export async function apiFetch<T>(path: string, options: RequestInit = {}): Promise<T> {
  const token = getToken();
  const headers = new Headers(options.headers);
  // Let the browser set its own multipart boundary for FormData bodies (file uploads).
  if (!(options.body instanceof FormData)) headers.set("Content-Type", "application/json");
  if (token) headers.set("Authorization", `Bearer ${token}`);

  let res: Response;
  try {
    res = await fetch(`${BASE_URL}${path}`, { ...options, headers });
  } catch {
    throw new ApiClientError(0, "Couldn't reach the server. Check your connection and try again.");
  }

  const body = (await res.json().catch(() => null)) as ApiEnvelope<T> | null;

  if (!res.ok || !body?.success) {
    // A 401 means the session itself is dead (missing/expired/invalid token) — not just this one
    // request. Handle it once, centrally, here, rather than leaving every caller's onError to
    // remember to check statusCode. Skip when already on /login: that page's own 401s are failed
    // login attempts (wrong password), not an expiring session, and must stay as an inline form
    // error rather than bouncing the user around.
    if (res.status === 401 && typeof window !== "undefined" && window.location.pathname !== "/login") {
      clearToken();
      window.location.href = "/login";
    }
    throw new ApiClientError(res.status, body?.message ?? "Something went wrong.", body?.details);
  }

  return body.data as T;
}
