/* eslint-disable functional/no-throw-statements */
import { z } from "zod";

const ApiErrorSchema = z.object({
  error: z.string(),
});

/**
 * Parse and validate a JSON response at the browser/server trust boundary.
 *
 * Keeping this in one place makes response validation consistent without
 * coupling callers to a specific API or domain type.
 */
export async function parseJsonResponse<T>(response: Response, schema: z.ZodType<T>): Promise<T> {
  const data: unknown = await response.json();
  return schema.parse(data);
}

/**
 * Read the conventional `{ error: string }` API response when available.
 * Malformed or non-JSON error bodies deliberately fall back to caller-owned
 * text so each screen retains its existing user-facing message.
 */
export async function readApiError(response: Response, fallback: string): Promise<string> {
  try {
    const data: unknown = await response.json();
    const result = ApiErrorSchema.safeParse(data);
    return result.success ? result.data.error : fallback;
  } catch {
    return fallback;
  }
}

/**
 * Fetch a JSON resource and validate its successful response.
 */
export async function fetchJson<T>(
  input: RequestInfo | URL,
  schema: z.ZodType<T>,
  init?: RequestInit,
  errorFallback?: (response: Response) => string,
): Promise<T> {
  const response = await fetch(input, init);
  if (!response.ok) {
    const fallback = errorFallback?.(response) ?? `Request failed with status ${response.status}`;
    throw new Error(await readApiError(response, fallback));
  }
  return parseJsonResponse(response, schema);
}
