export interface ApiEnvelope<T> {
  data: T;
  page: { nextCursor: string | null; limit: number } | null;
  meta: { runId: string | null; schemaVersion: number };
}

export class ApiError extends Error {
  constructor(
    message: string,
    readonly status: number
  ) {
    super(message);
    this.name = "ApiError";
  }
}

export type JsonQuery = Record<string, string | number | boolean | null | undefined>;

export function withQuery(path: string, query?: JsonQuery): string {
  if (!query) {
    return path;
  }
  const search = new URLSearchParams();
  for (const [key, value] of Object.entries(query)) {
    if (value === undefined || value === null) {
      continue;
    }
    search.set(key, String(value));
  }
  const queryString = search.toString();
  return queryString ? `${path}?${queryString}` : path;
}

export async function getJson<T>(path: string, query?: JsonQuery): Promise<ApiEnvelope<T>> {
  const response = await fetch(withQuery(path, query));
  const body = (await response.json().catch(() => ({}))) as { error?: string };
  if (!response.ok) {
    throw new ApiError(body.error ?? `Request failed: ${response.status}`, response.status);
  }
  return normalizeEnvelope<T>(body);
}

export async function postJson<T>(path: string, body?: unknown): Promise<ApiEnvelope<T>> {
  const response = await fetch(path, {
    body: JSON.stringify(body ?? {}),
    headers: { "content-type": "application/json" },
    method: "POST"
  });
  const responseBody = (await response.json().catch(() => ({}))) as { error?: string };
  if (!response.ok) {
    throw new ApiError(responseBody.error ?? `Request failed: ${response.status}`, response.status);
  }
  return normalizeEnvelope<T>(responseBody);
}

export async function getBinary(path: string): Promise<Blob> {
  const response = await fetch(path);
  if (!response.ok) {
    const body = (await response.json().catch(() => ({}))) as { error?: string };
    throw new ApiError(body.error ?? `Request failed: ${response.status}`, response.status);
  }
  return response.blob();
}

export function normalizeEnvelope<T>(value: unknown): ApiEnvelope<T> {
  if (typeof value !== "object" || value === null) {
    return {
      data: value as T,
      page: null,
      meta: { runId: null, schemaVersion: 1 }
    };
  }
  const record = value as {
    data?: T;
    page?: { nextCursor?: string | null; limit?: number } | null;
    meta?: { runId?: string | null; schemaVersion?: number } | null;
  };
  return {
    data: record.data as T,
    page: record.page
      ? {
          nextCursor: record.page.nextCursor ?? null,
          limit: record.page.limit ?? 0
        }
      : null,
    meta: {
      runId: record.meta?.runId ?? null,
      schemaVersion: record.meta?.schemaVersion ?? 1
    }
  };
}

export function normalizeApiError(error: unknown): Error {
  if (error instanceof ApiError) {
    return error;
  }
  if (error instanceof Error) {
    return error;
  }
  return new Error("Unknown API error");
}
