/**
 * Shared client-side fetch helper + helpers for parsing API error responses
 * and recognising the demo-mode read-only error code returned by `withAuth.ts`.
 *
 * `apiFetch` is the single fetch wrapper that knows how to map a server-side
 * `{ code: "DEMO_READ_ONLY", error }` payload back to the friendly message
 * regardless of the calling module. Per-feature `src/client/api/*.ts` modules
 * should call this instead of rolling their own `fetch + JSON parse + throw`
 * sequence so demo-mode error text stays consistent.
 */

export const DEMO_READ_ONLY_CODE = 'DEMO_READ_ONLY';
export const DEMO_READ_ONLY_MESSAGE =
  'Demo mode is read-only — sign up for a free trial to make changes.';

export interface ApiErrorBody {
  error?: string;
  code?: string;
  /** Per-field validation errors returned by `ValidationError.toJSON()` on
   *  the server (see `@server/errors`). Maps a field name to one or more
   *  human-readable messages so callers can surface them inline. */
  fields?: Record<string, string[]>;
}

export function isDemoReadOnlyError(body: ApiErrorBody | null | undefined): boolean {
  return !!body && body.code === DEMO_READ_ONLY_CODE;
}

export class ApiError extends Error {
  code?: string;
  status: number;
  /** Per-field validation errors forwarded from the server's
   *  `ValidationError`. Lets a caller render inline messages next to the
   *  offending input(s) instead of just a generic toast. */
  fields?: Record<string, string[]>;
  constructor(message: string, status: number, code?: string, fields?: Record<string, string[]>) {
    super(message);
    this.name = 'ApiError';
    this.status = status;
    this.code = code;
    this.fields = fields;
  }
}

/**
 * Shared fetch wrapper. Returns the parsed JSON body on success; throws an
 * `ApiError` whose message is always user-friendly on failure. For
 * `DEMO_READ_ONLY` responses the friendly server-side message is preserved
 * verbatim and a fallback is supplied when the server omits one.
 */
export async function apiFetch<T = unknown>(url: string, options?: RequestInit): Promise<T> {
  const res = await fetch(url, { credentials: 'include', ...options });
  const body = (await res.json().catch(() => ({}))) as ApiErrorBody & Record<string, unknown>;
  if (!res.ok) {
    const message = isDemoReadOnlyError(body)
      ? body.error || DEMO_READ_ONLY_MESSAGE
      : body.error || `Request failed: ${res.status}`;
    throw new ApiError(message, res.status, body.code, body.fields);
  }
  return body as T;
}
