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

export const FieldSchema = z.object({
  name: z.string().min(1),
  type: z.string().min(1),
  required: z.boolean().default(true),
  maxLength: z.number().optional(),
  isKey: z.boolean().default(false),
  /**
   * Optional C# expression deriving this field from other (non-computed) fields
   * of the SAME entity. When set, the field becomes read-only:
   * - included in `${e}ListDto` and `${e}DetailDto`
   * - excluded from `Create${e}Dto`, `Update${e}Dto`, Commands and Validators
   * - injected into the LINQ projection of `Get${e}` and `Get${plural}` queries
   *   (one SQL query, no N+1)
   * Tokens in the formula must be PascalCase property names of the same entity
   * (e.g. `(InitialAmount - CurrentBalance) / InitialAmount`). Cross-entity
   * references are rejected at the BA level by audit DM-016.
   */
  formula: z.string().optional(),
})

export const BusinessRuleSchema = z.object({
  id: z.string(),
  description: z.string(),
  // Raw FluentValidation rule body — anything valid inside `AbstractValidator<T>.ctor`.
  // Example: `RuleFor(x => x.StartDate).LessThan(x => x.EndDate).WithMessage("Start must precede end")`
  // When omitted, generate.ts falls back to simple heuristics (NotEmpty, range, pattern)
  // based on description, then emits a TODO comment if nothing matches.
  expression: z.string().optional(),
  validExamples: z.array(z.string()).default([]),
  invalidExamples: z.array(z.string()).default([]),
})

/**
 * Per-page mode (Step 4 of the per-page vertical-slice refactor): every
 * non-CRUD action declared in the BA's `BaScreen.Config.actions[]` becomes
 * one extra Command + Handler + service method beyond the standard CRUD.
 *
 * The `code` field is the single source of truth across all 4 layers
 * (controller URL segment, MediatR Command name, service method, frontend
 * hook). scaffold-controller (Step 3) emits the matching HTTP route.
 *
 * Canonical recipes (auto-emitted, no UC body needed) :
 *  - `archive`     → entity.Status = 'archived'
 *  - `restore`     → entity.Status = 'active'
 *  - `activate`    → entity.IsActive = true
 *  - `deactivate`  → entity.IsActive = false
 *  - `duplicate`   → reflection-clone + new Guid + Add()
 *
 * Unknown codes fall back to `throw new NotImplementedException` with a
 * comment pointing to the linked UC so the developer knows where to find
 * the business specification.
 */
export const BusinessCustomActionSchema = z.object({
  code: z.string().min(1).regex(/^[a-z][a-z0-9-]*$/, 'code must be kebab-case'),
  scope: z.enum(['row', 'bulk', 'header']).default('row'),
  payloadDto: z.string().nullable().optional(),
  responseDto: z.string().min(1).default('NoContent'),
  /** Optional UC code (e.g. `UC-BUD-014`) — emitted as a comment on the stub
   *  body when the recipe is non-canonical, so devs can trace the spec. */
  ucReference: z.string().optional(),
  // ─── Workflow metadata (SK-001 / SK-002) ────────────────────────────
  /** Current status the entity must be in before this action can fire.
   *  When provided, the generator emits a guard: `if (entity.Status != fromStatus) throw …` */
  fromStatus: z.string().optional(),
  /** Status the entity transitions to after this action succeeds.
   *  When provided, the generator emits `entity.Status = toStatus` + SaveChanges. */
  toStatus: z.string().optional(),
  /** Business rule IDs that gate this transition (e.g. `["BR-001","BR-003"]`).
   *  Emitted as guard comments referencing the rule. */
  guardRules: z.array(z.string()).optional(),
  /** Extra parameter names extracted from UC flow steps (e.g. `["motif","tacheIds"]`).
   *  When present AND payloadDto is null, the generator creates an inline DTO
   *  with these string properties. */
  flowParameters: z.array(z.string()).optional(),
  // ─── Cross-module metadata (SK-004 / SK-005) ────────────────────────
  /** Module dependencies that must be delivered before this action can be
   *  fully implemented. Generates `// BLOCKED: depends on module '{dep}'` TODOs. */
  crossModuleDeps: z.array(z.string()).optional(),
})

export const ScaffoldBusinessInputSchema = z.object({
  name: z.string().min(1).regex(/^[A-Z][A-Za-z0-9]*$/),
  pluralName: z.string().optional(),
  module: z.string().min(1),
  appCode: z.string().min(1),
  namespace: z.string().optional(),
  fields: z.array(FieldSchema).min(1),
  businessRules: z.array(BusinessRuleSchema).default([]),
  customActions: z.array(BusinessCustomActionSchema).default([]),
  projectPath: z.string().min(1),
  /**
   * Optional PascalCase property name used to project `{Entity}RefDto.DisplayName`
   * in `GetLookupAsync`. When omitted, the generator picks the first stored
   * string field in this order: `Name` → `Label` → `Code` → `Title` → first
   * remaining string. Computed (formula) fields are excluded — the lookup
   * projection must be SQL-translatable. Used by `ba-develop` Phase 2 which
   * derives the value from `entité.md`'s `**Affichage** :` line when present.
   */
  displayNameExpr: z.string().optional(),
})

export type Field = z.infer<typeof FieldSchema>
export type BusinessRule = z.infer<typeof BusinessRuleSchema>
export type BusinessCustomAction = z.infer<typeof BusinessCustomActionSchema>
export type ScaffoldBusinessInput = z.infer<typeof ScaffoldBusinessInputSchema>
export interface GeneratedFile { path: string; content: string }
export interface ValidationResult { valid: boolean; errors: string[]; warnings: string[] }
