export class ApiError extends Error {
  status: number;
  code?: number;
  body?: unknown;

  constructor(message: string, status: number, code?: number, body?: unknown) {
    super(message);
    this.name = 'ApiError';
    this.status = status;
    this.code = code;
    this.body = body;
  }
}

export interface Envelope<T = unknown> {
  code: number;
  message: string;
  data?: T;
  error?: { biz_code?: string };
}

function getBaseUrl() {
  return (import.meta.env.VITE_API_BASE as string | undefined) ?? '';
}

export function getToken(): string {
  return localStorage.getItem('access_token') ?? '';
}

export function setTokens(access: string, refresh: string) {
  localStorage.setItem('access_token', access);
  localStorage.setItem('refresh_token', refresh);
}

export function clearTokens() {
  localStorage.removeItem('access_token');
  localStorage.removeItem('refresh_token');
  localStorage.removeItem('uid');
  localStorage.removeItem('roles');
}

export async function api<T>(
  path: string,
  init: RequestInit & { auth?: boolean } = {},
): Promise<T> {
  const { auth = true, headers: initHeaders, ...rest } = init;
  const headers = new Headers(initHeaders);
  headers.set('Accept', 'application/json');
  if (rest.body && !headers.has('Content-Type')) {
    headers.set('Content-Type', 'application/json');
  }
  if (auth) {
    const token = getToken();
    if (token) headers.set('Authorization', `Bearer ${token}`);
  }

  const res = await fetch(`${getBaseUrl()}${path}`, { ...rest, headers });
  const text = await res.text();
  let json: Envelope<T> | null = null;
  try {
    json = text ? (JSON.parse(text) as Envelope<T>) : null;
  } catch {
    /* non-json */
  }

  if (!res.ok || (json && json.code !== 102000 && json.code !== 0)) {
    const msg = json?.message ?? res.statusText ?? '請求失敗';
    throw new ApiError(msg, res.status, json?.code, json);
  }
  if (json && (json.code === 102000 || json.code === 0) && json.data != null) {
    return json.data as T;
  }
  return (json?.data ?? json) as T;
}
