/**
 * useApi hooks generator for ReactAppStarter
 *
 * Emits typed per-model React Query hooks that the starter's view
 * skeletons import directly (`useTasksQuery`, `useCreateTaskMutation`,
 * etc.). All hooks hit the realized backend's REST endpoints under
 * `/api/{resource}` via the sibling `apiClient` helper.
 *
 * Why a separate generator from the runtime one: ReactAppRuntime
 * ships a generic `useEntitiesQuery(controller, model)` because its
 * views are rendered by @specverse/runtime using runtime dispatch.
 * The starter wants plain typed hooks the user can read and edit.
 */

/**
 * Minimal English pluralizer. Matches the conventions used by
 * view-emitter so the generated hooks pair cleanly with the generated
 * view imports.
 */
function pluralize(s: string): string {
  if (/y$/.test(s) && !/[aeiou]y$/.test(s)) return s.replace(/y$/, 'ies');
  if (/(s|x|z|ch|sh)$/.test(s)) return s + 'es';
  return s + 's';
}

export interface UseApiHooksStarterContext {
  spec: {
    models?: Record<string, any>;
  };
  manifest?: unknown;
}

/**
 * Extract lifecycle metadata from a model for the Evolve mutation.
 * Returns an array of { name, states } entries. Both normalized
 * (`states: [{name}]`) and raw (`flow: "a -> b -> c"`) shapes handled.
 */
function extractLifecycles(model: any): Array<{ name: string; states: string[] }> {
  const out: Array<{ name: string; states: string[] }> = [];
  const lifecycles = (model?.lifecycles ?? {}) as Record<string, any>;
  for (const [name, def] of Object.entries(lifecycles)) {
    if (!def || typeof def !== 'object') continue;
    let states: string[] = [];
    if (Array.isArray(def.states)) {
      states = def.states
        .map((s: any) => typeof s === 'string' ? s : s?.name ?? s?.id ?? '')
        .filter(Boolean);
    } else if (typeof def.flow === 'string') {
      states = def.flow.split(/\s*(?:->|,)\s*/).map((s: string) => s.trim()).filter(Boolean);
    }
    if (states.length > 0) out.push({ name, states });
  }
  return out;
}

export async function generate(context: UseApiHooksStarterContext): Promise<string> {
  const models = Object.keys(context.spec.models ?? {});

  const importsAndTypes = `/**
 * useApi — typed per-model React Query hooks (ReactAppStarter)
 *
 * Safe to edit. Users who want to reshape their API client or add
 * request/response interceptors should start here. Each hook calls
 * the sibling \`apiClient\` which does the actual fetch; edit
 * \`apiClient.ts\` to change transport concerns (headers, auth, etc.).
 */

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
${models.map(m => `import type { ${m} } from '../types/api';`).join('\n')}

const API_BASE = import.meta.env.VITE_API_BASE_URL || '';

async function fetchJSON<T = unknown>(url: string, init?: RequestInit): Promise<T> {
  // Only declare Content-Type: application/json when there's actually a
  // body. Fastify's default JSON parser rejects empty-body requests that
  // claim JSON (FST_ERR_CTP_EMPTY_JSON_BODY) — so DELETE / GET without
  // bodies must NOT send the header.
  const headers: Record<string, string> = {};
  if (init?.body != null) headers['Content-Type'] = 'application/json';
  const res = await fetch(url, { ...init, headers: { ...headers, ...init?.headers } });
  if (!res.ok) {
    throw new Error(\`\${res.status} \${res.statusText} — \${url}\`);
  }
  if (res.status === 204) return undefined as T;
  return (await res.json()) as T;
}
`;

  const hookBlocks = models
    .map(m => generateModelHooks(m, context.spec.models?.[m]))
    .join('\n\n');

  return importsAndTypes + '\n' + hookBlocks + '\n';
}

function generateModelHooks(model: string, modelDef: any): string {
  const plural = pluralize(model);
  const resource = plural.toLowerCase();
  const lifecycles = extractLifecycles(modelDef);
  const hasLifecycle = lifecycles.length > 0;

  // Lifecycle metadata for the Evolve tab. Stored as a const so form
  // skeletons can read state lists + primary lifecycle name without
  // re-parsing the spec. One entry per declared lifecycle attribute.
  const lifecycleBlock = hasLifecycle
    ? `
export const ${model.toUpperCase()}_LIFECYCLES = ${JSON.stringify(
        Object.fromEntries(lifecycles.map(l => [l.name, { states: l.states }])),
        null,
        2,
      )} as const;
`
    : '';

  // Evolve mutation — only emitted for models with at least one
  // lifecycle. Body shape matches the generated backend's evolve
  // handler: { toState, lifecycleName? } → update that column.
  const evolveHook = hasLifecycle
    ? `
export function useEvolve${model}Mutation() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ id, toState, lifecycleName }: { id: string | number; toState: string; lifecycleName?: string }) =>
      fetchJSON<${model}>(\`\${API_BASE}/api/${resource}/\${id}/evolve\`, {
        method: 'PATCH',
        body: JSON.stringify({ toState, lifecycleName }),
      }),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['${resource}'] }),
  });
}
`
    : '';

  return `// ─── ${model} ───────────────────────────────────────────────────
${lifecycleBlock}
export function use${plural}Query() {
  return useQuery({
    queryKey: ['${resource}'],
    queryFn: () => fetchJSON<${model}[]>(\`\${API_BASE}/api/${resource}\`),
  });
}

export function use${model}Query(id: string | number | undefined) {
  return useQuery({
    queryKey: ['${resource}', id],
    queryFn: () => fetchJSON<${model}>(\`\${API_BASE}/api/${resource}/\${id}\`),
    enabled: id !== undefined && id !== null,
  });
}

export function useCreate${model}Mutation() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (data: Partial<${model}>) =>
      fetchJSON<${model}>(\`\${API_BASE}/api/${resource}\`, {
        method: 'POST',
        body: JSON.stringify(data),
      }),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['${resource}'] }),
  });
}

export function useUpdate${model}Mutation() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ id, data }: { id: string | number; data: Partial<${model}> }) =>
      fetchJSON<${model}>(\`\${API_BASE}/api/${resource}/\${id}\`, {
        method: 'PUT',
        body: JSON.stringify(data),
      }),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['${resource}'] }),
  });
}

export function useDelete${model}Mutation() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string | number) =>
      fetchJSON<void>(\`\${API_BASE}/api/${resource}/\${id}\`, { method: 'DELETE' }),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['${resource}'] }),
  });
}${evolveHook}`;
}

export default generate;
