export const AUTH_TOKEN_STORAGE_KEY = "pi-chat.authToken";

export const authTokenFromStorage = () => {
  const stored = window.localStorage.getItem(AUTH_TOKEN_STORAGE_KEY)?.trim();
  const configured = import.meta.env.VITE_AUTH_TOKEN?.trim();
  return stored || configured || null;
};

// Only adds the Authorization header. Does NOT set content-type so callers
// that send a body without content-type don't get rejected by Fastify
// (DELETE/PATCH with content-type: application/json and an empty body 400s).
export const authorizedHeaders = (headers?: HeadersInit) => {
  const result = new Headers(headers);
  const token = authTokenFromStorage();
  if (token && !result.has("authorization")) {
    const authorization = token.toLowerCase().startsWith("bearer ") ? token : `Bearer ${token}`;
    result.set("authorization", authorization);
  }
  return result;
};

// Convenience for requests that send a JSON body.
export const jsonHeaders = (headers?: HeadersInit) => {
  const result = authorizedHeaders(headers);
  if (!result.has("content-type")) {
    result.set("content-type", "application/json");
  }
  return result;
};

export const api = async <T,>(path: string, init?: RequestInit): Promise<T> => {
  // Only set content-type when there's actually a body. DELETE / PATCH /
  // GET through this helper without an explicit body must NOT trigger the
  // "Body cannot be empty when content-type is set to 'application/json'"
  // 400 in Fastify. This makes `api()` safe for any verb.
  const hasBody = init?.body !== undefined && init?.body !== null;
  const headers = hasBody ? jsonHeaders(init?.headers) : authorizedHeaders(init?.headers);

  const response = await fetch(path, { ...init, headers });

  if (!response.ok) {
    throw new Error(`api_error:${response.status}`);
  }

  // 204 No Content has no body to parse; don't blow up trying.
  if (response.status === 204) return undefined as T;
  // Some servers return 200 with empty body; guard the JSON parse too.
  const text = await response.text();
  if (text.length === 0) return undefined as T;
  return JSON.parse(text) as T;
};

export const parseMetadata = (metadata: string | null) => {
  if (!metadata) return null;
  try {
    return JSON.parse(metadata) as {
      docs?: unknown[];
      actions?: Array<{ id: string }>;
      model?: string;
      usage?: {
        promptTokens?: number;
        completionTokens?: number;
        totalTokens?: number;
        cachedTokens?: number;
        durationMs?: number;
      };
    };
  } catch {
    return null;
  }
};

export const formatDuration = (ms: number): string => {
  if (ms < 1000) return `${ms} ms`;
  if (ms < 60_000) return `${(ms / 1000).toFixed(1)} s`;
  const minutes = Math.floor(ms / 60_000);
  const seconds = Math.floor((ms % 60_000) / 1000);
  return `${minutes}m ${seconds}s`;
};

export const formatNumber = (n: number): string => {
  if (n >= 1000) return `${(n / 1000).toFixed(n >= 10_000 ? 0 : 1)}k`;
  return String(n);
};

export const temporaryId = () => {
  const randomUUID = window.crypto?.randomUUID?.bind(window.crypto);
  if (randomUUID) return randomUUID();
  return `tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
};

export const formatScore = (value: unknown) =>
  typeof value === "number" ? value.toFixed(2) : "s/d";
