/**
 * cli:scaffold-api-client — types.ts
 */
import { z } from 'zod'

export const ApiFieldSchema = z.object({ name: z.string(), type: z.string(), required: z.boolean().default(true) })

/**
 * Per-page mode (Step 5 of the per-page vertical-slice refactor): every
 * non-CRUD action declared in the BA's `BaScreen.Config.actions[]` adds:
 *  - one method on `{entity}Service` (calls api.post(...))
 *  - one React hook (useState/useEffect) wrapping the service call
 *
 * `action.code` is the **business identifier** (kebab-case) used for
 * TypeScript member naming:
 *  - service member name  =  toCamelCase(code)   → `toggleActif`
 *  - hook name            =  `use` + toPascalCase(code) + Entity → `useToggleActifEmploye`
 *
 * `action.endpoint` (optional) is the **URL segment** matching the controller
 * route attribute. When omitted, `code` is used as the URL segment
 * (backward-compatible for controllers whose business and route names align).
 * Use `endpoint` when the controller route diverges from the business
 * identifier — e.g.:
 *  - `code: 'toggle-actif'`  →  `endpoint: 'activate'`
 *  - `code: 'sync-from-pce'` →  `endpoint: 'sync-from-proconcept'`
 *  - `code: 'map-to-pce'`    →  `endpoint: 'map-proconcept'`
 *
 * The single source of truth across the pipeline lives in
 * `lib/page-spec-actions.ts` (`PageCustomActionSchema`). See that file for the
 * full contract; this schema is the per-CLI projection used by api-client
 * generation only. ba-develop's Phase 3a spec-builder reads each controller's
 * `[Http*("…")]` attributes to populate `endpoint` here.
 *
 * Hook name follows the canonical pattern : kebab → PascalCase + entity name.
 *  - `archive` row              → `useArchiveBudget()` calls POST /api/.../{id}/archive
 *  - `bulk-archive`             → `useBulkArchiveBudget()` calls POST /api/.../bulk/archive
 *  - `export` header            → `useExportBudget()` calls POST /api/.../export
 *  - `sync-from-proconcept`     → `useSyncFromProconceptBudget()` calls POST /api/.../sync-from-proconcept
 *  - `toggle-actif` + endpoint:'activate'
 *                               → `useToggleActifBudget()` calls POST /api/.../{id}/activate
 *
 * @see lib/page-spec-actions.ts (canonical PageCustomActionSchema)
 */
export const ApiCustomActionSchema = z.object({
  code: z.string().min(1).regex(/^[a-z][a-z0-9-]*$/, 'code must be kebab-case'),
  /** Distinguishes API-bound actions from pure client-side navigations.
   *  `api`      → emits the service member + the React hook (the default).
   *  `navigate` → emits NOTHING in scaffold-api-client (no service member,
   *               no hook). The button is rendered by scaffold-component with
   *               an inline `navigate(targetRoute)` call. Without this filter,
   *               every "open detail" or "go to dashboard" button in the BA
   *               pagespecs produced a phantom `POST /{id}/open` endpoint that
   *               does not exist on any controller — 6 entities × 1 button =
   *               6 systematic 404s in the REFERENTIELS post-mortem.
   *  Mirrors the canonical `kind` field of `lib/page-spec-actions.ts`. */
  kind: z.enum(['api', 'navigate']).default('api'),
  /** URL segment used in the service call. When omitted, falls back to `code`.
   *  Use when the controller route differs from the business identifier:
   *    code: 'toggle-actif'  →  endpoint: 'activate'
   *    code: 'sync-from-pce' →  endpoint: 'sync-from-proconcept'
   *  Accepts single segments (`activate`) or slash-separated paths
   *  (`dashboard/consolidated`) — every segment must be kebab-case. */
  endpoint: z
    .string()
    .min(1)
    .regex(/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)*$/, 'endpoint must be kebab-case path segment(s)')
    .optional(),
  scope: z.enum(['row', 'bulk', 'header']).default('row'),
  /** TypeScript payload type emitted in the hook signature (e.g.
   *  `ApproveRequest`). When null, the mutation takes no body — just an id
   *  for row-scope, or no args for header-scope. */
  payloadType: z.string().nullable().optional(),
  /** Optional shape for the `payloadType` interface. When provided, the
   *  scaffolder emits `export interface ${payloadType} { ${field}: ${tsType}; ... }`
   *  in `types/index.ts`. When omitted, a permissive placeholder
   *  `interface ${payloadType} { [key: string]: unknown }` is emitted with a
   *  TODO comment — this keeps TypeScript happy (no TS2304 « cannot find name »)
   *  while signalling that BA work is needed to firm up the shape. Builtin
   *  types (`string`, `string[]`, etc.) and DTOs already generated for the
   *  entity (`${E}ListDto`, …) are never re-emitted. */
  payloadShape: z.record(z.string(), z.string()).optional(),
  /** TypeScript response type for the service method. `void` for 204
   *  (no body) endpoints. */
  responseType: z.string().min(1).default('void'),
  /** Optional shape for the `responseType` interface. Same semantics as
   *  `payloadShape` above. Skipped when `responseType` is `void`. */
  responseShape: z.record(z.string(), z.string()).optional(),
  /** HTTP method for the service call. Defaults to `post` for mutations.
   *  Set to `get` for read-only actions like `export` whose backend
   *  controller uses `[HttpGet]`. */
  httpMethod: z.enum(['get', 'post', 'put', 'patch', 'delete']).default('post'),
})

/**
 * One column from a pagespec — used in screen-driven mode (Wave F1) to shape
 * the TypeScript `${E}ListDto` / `${E}DetailDto` interfaces from the BA contract
 * (`pagespecs/<Entity>.<view>.md` → `columns[]`).
 *
 * - `key`        is the property name on the C# DTO (PascalCase on the wire,
 *                JSON-serialised camelCase by ASP.NET → matches `key` as-is
 *                when the BA author writes camelCase).
 * - `formatHint` mirrors `scaffold-screen-controller`'s `dotnetTypeFor` so the
 *                TypeScript field type matches the C# one byte-for-byte:
 *                  currency/decimal/number → number
 *                  integer/count           → number
 *                  date/datetime           → string (ISO-8601)
 *                  bool/boolean            → boolean
 *                  guid/uuid               → string
 *                  (default)               → string
 *
 * The same shape is used in the backend CLI's `PageSpecColumn` (see
 * `templates/skills/development/backend/screen-controller/cli/scaffold-screen-controller/types.ts`).
 * Drift between the two is caught by the integration test in
 * `__tests__/screen-strata-contract.test.ts` (added in Wave F4).
 */
export const ApiScreenColumnSchema = z.object({
  key: z.string().min(1),
  formatHint: z.string().optional(),
})

export const ApiEntitySchema = z.object({
  name: z.string().min(1),
  pluralName: z.string().optional(),
  section: z.string().min(1),
  /**
   * Sub-resource URL template (2026-05-27).
   *
   * When set, the entity is nested under a parent: every service method takes
   * `parentId: string` as its first parameter and the API path is computed
   * by substituting `{parentId}` in this template. Use for sub-resources whose
   * controller route includes a parent ID, e.g.:
   *   - Rue under Site:          '/api/gaf/referentiels/sites/{parentId}/rues'
   *   - Qualificatif under Type: '/api/gaf/referentiels/types-affaire/{parentId}/qualificatifs/audit'
   *
   * When omitted, the standard flat URL applies (derived from `appCode` /
   * `module` / `section` per the `routeMode`). Required substring: `{parentId}`.
   *
   * Constraint: incompatible with `useScreens: true` / `routeMode: 'screens'`
   * (the screen stratum has no nested URL convention). The CLI surfaces an
   * error if both are set.
   */
  parentPath: z
    .string()
    .min(1)
    .regex(/^\/.*\{parentId\}.*$/, "parentPath must be an absolute URL containing the literal '{parentId}' placeholder")
    .optional(),
  /**
   * Parameter name for the parent ID in emitted service / hook signatures.
   * Defaults to `parentId`. Set to a domain-specific name (e.g. `siteId`,
   * `typeAffaireId`) so generated code reads naturally to humans:
   *   useRue(siteId: string, id: string)  vs  useRue(parentId: string, id: string)
   */
  parentIdParam: z
    .string()
    .min(1)
    .regex(/^[a-z][a-zA-Z0-9]*$/, 'parentIdParam must be a camelCase identifier')
    .default('parentId'),
  fields: z.array(ApiFieldSchema).default([]),
  customActions: z.array(ApiCustomActionSchema).default([]),
  /**
   * When true, the scaffolder also emits a typed wrapper for the entity's
   * dashboard endpoints :
   *   getConsolidated() → GET /api/{module}/{plural-kebab}/dashboard/consolidated
   *   getAlerts()       → GET /api/{module}/{plural-kebab}/dashboard/alerts
   * Plus a `useDashboard{Entity}` React Query hook bundling both calls
   * (KPIs in `data.consolidated`, alerts in `data.alerts`).
   * The companion `{Entity}DashboardPage.tsx` (scaffold-component view
   * 'dashboard') consumes this hook — never raw axios.
   */
  hasDashboard: z.boolean().default(false),
  /**
   * Column contract for shaping the TypeScript DTOs (Wave F1, 2026-05-27;
   * extended Wave F5, 2026-05-27).
   *
   * When provided, the `${E}ListDto` and `${E}DetailDto` interfaces are
   * shaped from these pagespec columns — matching the C# DTO returned by the
   * controller — in ALL route modes (direct, integration, screens). The
   * historical `useScreens`-only gate has been removed: drift between the
   * frontend TS interface and the backend C# DTO is a bug class we want
   * eliminated regardless of stratum.
   *
   * The emitted shape:
   *   { Id, <each list column>,   CreatedAt }                 (list)
   *   { Id, <each detail column>, CreatedAt, UpdatedAt }      (detail)
   *
   * `/ba-develop` Phase 3a populates this by parsing every
   * `<MODULE>/pagespecs/<Entity>.<view>.md` for the entity and collecting the
   * `columns[]` JSON block.
   *
   * When absent — only when the spec carries no pagespec column data — the
   * CLI falls back to `entity.fields.slice(0,5)` (legacy heuristic) so older
   * pipelines that haven't wired pagespec extraction yet still emit a
   * well-formed interface.
   *
   * The field name retains the historical `screenColumns` for backward
   * compatibility with existing /ba-develop spec builders; the columns
   * themselves are not screen-mode specific.
   */
  screenColumns: z.object({
    list: z.array(ApiScreenColumnSchema).optional(),
    detail: z.array(ApiScreenColumnSchema).optional(),
  }).optional(),
})

export const ScaffoldApiClientInputSchema = z.object({
  module: z.string().min(1),
  appCode: z.string().min(1),
  entities: z.array(ApiEntitySchema).min(1),
  projectPath: z.string().min(1),
  /**
   * Route mode — selects which URL convention the api-client emits.
   *
   *   'direct' (default) — `/api/{appCode}/{module}/{section}`.
   *     The standard SmartStack controller route emitted by Phase 2a.
   *     Path is computed from the spec's `appCode`, `module`, and each
   *     entity's `section`. REST CRUD methods.
   *
   *   'integration' — `/api/v1/integration/{plural-kebab}`.
   *     Legacy machine-to-machine stratum. Use when the backend
   *     controller was generated by `scaffold-controller`.
   *
   *   'screens' — `/api/screens/{plural-kebab}/{view}`.
   *     Screen-driven stratum. Set when Phase 2b's screen controllers
   *     exist. `delete` and `getLookup` fall back to integration.
   *
   * Backward compat: `useScreens: true` is equivalent to
   * `routeMode: 'screens'`.
   */
  routeMode: z.enum(['direct', 'integration', 'screens']).default('direct'),
  useScreens: z.boolean().default(false),
  /**
   * HTTP client source. Default 'smartstack' uses `api` from
   * `@atlashub/smartstack` (pre-configured with auth, tenant, retry;
   * returns unwrapped data, not AxiosResponse).
   * 'axios' generates a local axios instance with manual configuration —
   * for projects that don't use the SmartStack npm package.
   */
  httpClient: z.enum(['smartstack', 'axios']).default('smartstack'),
  /**
   * Relative path from projectPath to the web project root.
   * Defaults to `web/${appCode}-web`.
   */
  webRoot: z.string().optional(),
}).strict()

export type ApiCustomAction = z.infer<typeof ApiCustomActionSchema>
export type ApiScreenColumn = z.infer<typeof ApiScreenColumnSchema>
export type ApiEntity = z.infer<typeof ApiEntitySchema>
export type ScaffoldApiClientInput = z.infer<typeof ScaffoldApiClientInputSchema>
export interface GeneratedFile { path: string; content: string }
export interface ValidationResult { valid: boolean; errors: string[]; warnings: string[] }
