import type { ApiError, ApiSuccess, BootstrapData } from '@/types';

/**
 * The single place the client talks to WordPress.
 *
 * One error path: every non-2xx response becomes a FlowWPApiError carrying the
 * server's envelope, so callers never have to guess at the shape of a failure.
 */

export class FlowWPApiError extends Error {
  constructor(
    public readonly code: string,
    message: string,
    public readonly status: number,
    public readonly fieldErrors: Array<{ field?: string; code: string; message: string }> = [],
  ) {
    super(message);
    this.name = 'FlowWPApiError';
  }
}

export function bootstrap(): BootstrapData {
  const data = window.flowwpData;

  if (!data) {
    throw new Error('FlowWP bootstrap data is missing. The admin script was loaded outside a FlowWP screen.');
  }

  return data;
}

async function request<T>(path: string, init: RequestInit = {}): Promise<ApiSuccess<T>> {
  const { apiRoot, nonce } = bootstrap();

  const response = await fetch(`${apiRoot}${path}`, {
    ...init,
    credentials: 'same-origin',
    headers: {
      // A file upload sets its own multipart boundary; forcing JSON would break it.
      ...(init.body instanceof FormData ? {} : { 'Content-Type': 'application/json' }),
      'X-WP-Nonce': nonce,
      ...(init.headers ?? {}),
    },
  });

  const body: unknown = await response.json().catch(() => null);

  if (!response.ok) {
    const error = (body ?? {}) as ApiError;

    throw new FlowWPApiError(
      error.code ?? 'flowwp_request_failed',
      error.message ?? `Request to ${path} failed.`,
      error.data?.status ?? response.status,
      error.data?.errors ?? [],
    );
  }

  return body as ApiSuccess<T>;
}

/**
 * `exactOptionalPropertyTypes` means an explicit `body: undefined` is not the
 * same as omitting it, so the key is only added when there is a payload.
 */
function withBody(method: string, payload?: unknown): RequestInit {
  return payload === undefined ? { method } : { method, body: JSON.stringify(payload) };
}

export const api = {
  get: <T>(path: string) => request<T>(path, { method: 'GET' }),

  post: <T>(path: string, payload?: unknown) => request<T>(path, withBody('POST', payload)),

  put: <T>(path: string, payload?: unknown) => request<T>(path, withBody('PUT', payload)),

  delete: <T>(path: string) => request<T>(path, { method: 'DELETE' }),

  /** A multipart POST, for file uploads. */
  upload: <T>(path: string, form: FormData) => request<T>(path, { method: 'POST', body: form }),
};
