/**
 * cli:scaffold-core-seed — types.ts
 *
 * Source-of-truth schemas for the multi-application Core Foundation Seed spec
 * — one bundle of navigation + roles + permissions + role-permission mappings
 * + dev test users PER application declared in the project. Generates the 5
 * `{AppPascal}Core*SeedDataProvider.cs` files under
 * `Persistence/Seeding/Applications/{AppPascal}/Core/`.
 *
 * Contrast with scaffold-seed (`development/backend/seed-data/`) which generates
 * per-module reference-data providers ONLY (lookup tables, enum codes), now
 * placed under `Persistence/Seeding/Applications/{AppPascal}/Modules/{ModuleCode}/`.
 *
 * NavigationEntry / RoleEntry / PermissionEntry / RolePermission /
 * TestUserEntry schemas are re-exported from scaffold-seed/types.ts so the
 * shapes stay in lockstep — only the *bundling* differs (per-module vs
 * per-application vs per-project).
 */
import { z } from 'zod';
import {
  NavigationEntrySchema,
  RoleEntrySchema,
  PermissionEntrySchema,
  RolePermissionSchema,
  TestUserEntrySchema,
} from '../../../seed-data/cli/scaffold-seed/types.js';

export {
  NavigationEntrySchema,
  RoleEntrySchema,
  PermissionEntrySchema,
  RolePermissionSchema,
  TestUserEntrySchema,
} from '../../../seed-data/cli/scaffold-seed/types.js';
export type {
  NavigationEntry,
  NavigationLevel,
  RoleEntry,
  PermissionEntry,
  RolePermission,
  TestUserEntry,
} from '../../../seed-data/cli/scaffold-seed/types.js';

/**
 * Per-application Core Foundation Seed slice.
 *
 * `modules[]` is REQUIRED and load-bearing: the gate verifies that every
 * code listed here has at least one matching `level: 'module'` entry in
 * `navigation[]`, ≥ 1 role with that `code`, and ≥ 2 permissions
 * referencing one of its sections. This is the structural fix that makes
 * "module declared but seed empty" (the Budget regression in TestV2/ba-002)
 * impossible by construction.
 */
export const ApplicationSpecSchema = z.object({
  /**
   * Navigation application code (lowercase, kebab-case). Used in
   * `Role.applicationCode` to scope roles per app, and as the path segment
   * for `Persistence/Seeding/Applications/{Pascal(code)}/...`.
   */
  code: z.string().min(1).regex(
    /^[a-z][a-z0-9-]*$/,
    'application code must be kebab-case (lowercase) and start with a letter',
  ),
  /**
   * Module codes whose nav/permissions/roles MUST be present in this app's
   * slice. The validator and the runtime gate use this list to fail fast
   * when a module declared in the PRD has no seeded artefacts.
   */
  modules: z.array(z.string().min(1)).min(1, 'At least one module code is required per application'),
  navigation: z.array(NavigationEntrySchema).min(1, 'At least one navigation entry required per application'),
  roles: z.array(RoleEntrySchema).default([]),
  permissions: z.array(PermissionEntrySchema).default([]),
  rolePermissions: z.array(RolePermissionSchema).default([]),
  testUsers: z.array(TestUserEntrySchema).default([]),
});

export type ApplicationSpec = z.infer<typeof ApplicationSpecSchema>;

/**
 * Project-level Core Foundation Seed spec — one or more applications, each
 * producing 5 `{AppPascal}Core*SeedDataProvider.cs` files under their own
 * `Seeding/Applications/{AppPascal}/Core/` folder. DI is patched once with
 * one block per application between `<<< CORE-SEED-DI-{AppPascal} BEGIN/END >>>`
 * markers.
 */
export const CoreSeedSpecSchema = z.object({
  /**
   * .NET project namespace prefix (PascalCase, e.g. `TestV2`). Drives the
   * `src/{AppCode}.Infrastructure/...` paths and the C# namespaces. Distinct
   * from `applications[].code` which is the per-app navigation code.
   */
  appCode: z.string().min(1).regex(
    /^[A-Za-z][A-Za-z0-9-]*$/,
    'appCode must start with a letter and contain only letters, digits and hyphens (PascalCase like "TestV2" or kebab like "test-v2")',
  ),
  namespace: z.string().optional(),
  applications: z.array(ApplicationSpecSchema).min(1, 'At least one application is required'),
  testUserPassword: z.string().min(8).default('DevTest!2026Secure'),
  testUserEmailDomain: z.string().default('smartstack.local'),
  emitDevCredentialsManifest: z.boolean().default(true),
  projectPath: z.string().min(1),
});

export type CoreSeedSpec = z.infer<typeof CoreSeedSpecSchema>;

export interface GeneratedFile {
  path: string;
  content: string;
}

export interface ModifiedFile {
  path: string;
  content: string;
}

export interface ValidationResult {
  valid: boolean;
  errors: string[];
  warnings: string[];
}

/**
 * Build the BEGIN marker comment for a given application's DI registration
 * block. Each application gets its own marker pair so multi-app projects
 * patch independently and re-runs are idempotent per app.
 */
export function diBeginMarker(appPascal: string): string {
  return `// <<< CORE-SEED-DI-${appPascal} BEGIN >>>`;
}

export function diEndMarker(appPascal: string): string {
  return `// <<< CORE-SEED-DI-${appPascal} END >>>`;
}

/**
 * Suffixes of the 5 Core providers, in DI registration + execution order.
 * Multi-app class names are formed as `{AppPascal}{Suffix}` (e.g.
 * `CrmCoreNavigationSeedDataProvider`). The gate iterates per-app and
 * verifies all 5 prefixed files exist under the per-app Core folder.
 */
export const CORE_PROVIDER_SUFFIXES = [
  'CoreNavigationSeedDataProvider',
  'CoreRolesSeedDataProvider',
  'CorePermissionsSeedDataProvider',
  'CoreRolePermissionsSeedDataProvider',
  'CoreTestUsersSeedDataProvider',
] as const;

export type CoreProviderSuffix = (typeof CORE_PROVIDER_SUFFIXES)[number];

/** Convert a kebab-case code (e.g. "crm", "test-app") to PascalCase ("Crm", "TestApp"). */
export function pascalCase(code: string): string {
  return code
    .split(/[^A-Za-z0-9]+/)
    .filter(Boolean)
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
    .join('');
}

/** Full class name for a Core provider in a given application. */
export function coreProviderClassName(appPascal: string, suffix: CoreProviderSuffix): string {
  return `${appPascal}${suffix}`;
}
