/**
 * Règle d'or (ADR-001) : Zod → OpenAPI → clients typés.
 * Génère docs/openapi.json (COMMITTÉE) depuis src/contract.ts.
 * Usage : pnpm --filter @siop/shared contract   (raccourci racine : pnpm contract)
 * La CI (ci-contract, R0.12) échoue si le fichier committé diffère de la génération.
 */
import { mkdirSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { z } from 'zod';
import { API_CONTRACT } from '../src/contract';

const OUT = resolve(__dirname, '../../../docs/openapi.json');

const schemas: Record<string, unknown> = {};

function register(name: string, schema: z.ZodType): { $ref: string } {
  if (!(name in schemas)) {
    schemas[name] = z.toJSONSchema(schema, { target: 'draft-2020-12' });
  }
  return { $ref: `#/components/schemas/${name}` };
}

const paths: Record<string, Record<string, unknown>> = {};

for (const op of API_CONTRACT) {
  const responses: Record<string, unknown> = {};
  for (const [status, res] of Object.entries(op.responses)) {
    responses[status] = {
      description: res.description,
      ...(res.schema && res.name
        ? { content: { 'application/json': { schema: register(res.name, res.schema) } } }
        : {}),
    };
  }
  paths[op.path] = {
    ...(paths[op.path] ?? {}),
    [op.method]: {
      operationId: op.operationId,
      summary: op.summary,
      tags: op.tags,
      ...(op.pathParams?.length || op.queryParams?.length
        ? {
            parameters: [
              ...(op.pathParams ?? []).map((name) => ({
                name,
                in: 'path',
                required: true,
                // Convention : « id » / «…Id » sont des UUID, le reste est libre
                schema:
                  name === 'id' || name.endsWith('Id')
                    ? { type: 'string', format: 'uuid' }
                    : { type: 'string' },
              })),
              ...(op.queryParams ?? []).map((q) => ({
                name: q.name,
                in: 'query',
                required: q.required ?? false,
                schema: { type: 'string' },
              })),
            ],
          }
        : {}),
      ...(op.isPublic ? {} : { security: [{ bearerAuth: [] }] }),
      ...(op.demoOnly
        ? {
            'x-demo-only': true,
            description:
              'ADR-002 : cette route est absente (404) quand DEMO_MODE n’est pas actif.',
          }
        : {}),
      ...(op.request
        ? {
            requestBody: {
              required: true,
              content: {
                'application/json': { schema: register(op.request.name, op.request.schema) },
              },
            },
          }
        : {}),
      ...(op.multipartFields
        ? {
            requestBody: {
              required: true,
              content: {
                'multipart/form-data': {
                  schema: {
                    type: 'object',
                    properties: Object.fromEntries(
                      Object.entries(op.multipartFields).map(([name, sorte]) => [
                        name,
                        sorte === 'file'
                          ? { type: 'string', format: 'binary' }
                          : { type: 'string' },
                      ]),
                    ),
                    required: ['file'],
                  },
                },
              },
            },
          }
        : {}),
      responses,
    },
  };
}

const doc = {
  openapi: '3.1.0',
  info: {
    title: 'SIOP V2 API',
    version: '0.1.0',
    description:
      'GMAO ascenseurs — contrat R0 (auth, démo-login ADR-002, profil, santé). ' +
      'Généré depuis packages/shared/src/contract.ts — NE PAS ÉDITER À LA MAIN.',
  },
  paths,
  components: {
    schemas,
    securitySchemes: {
      bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
    },
  },
};

mkdirSync(dirname(OUT), { recursive: true });
writeFileSync(OUT, JSON.stringify(doc, null, 2) + '\n');
console.log(`OpenAPI générée : ${OUT} (${API_CONTRACT.length} opérations)`);
