/**
 * scaffold-routes / generate.test.ts (2026-05-27)
 *
 * Covers the three regressions reported alongside the GAF/REFERENTIELS
 * post-mortem:
 *  - Bug D: parentSection nests the componentKey (and the URL helper) so the
 *           navigation DB's 4-segment key for sub-resources actually resolves.
 *  - Bug E: `create` + `edit` views share the same `${E}FormPage` lazy const
 *           — emit it exactly once so the registry compiles.
 *  - Bug F: `module-home` view emits the 2-segment `{app}.{module}` key, not
 *           the redundant `{app}.{module}.module-home.module-home`.
 *
 * Plus a couple of guard-rail tests that lock in the baseline contract so a
 * future change can't silently regress the basic 3-segment list/detail/create
 * key shape.
 */
import { describe, it, expect } from 'vitest'
import { generate } from '../generate.js'
import { validateComponentKey } from '../types.js'
import type { ScaffoldRoutesInput } from '../types.js'

function fixture(overrides: Partial<ScaffoldRoutesInput> = {}): ScaffoldRoutesInput {
  return {
    module: 'crm',
    appCode: 'app',
    entities: [
      {
        name: 'Contact',
        section: 'contacts',
        views: ['list', 'detail', 'create', 'edit'],
      },
    ],
    projectPath: '/test',
    ...overrides,
  } as ScaffoldRoutesInput
}

describe('scaffold-routes / generate — baseline contract', () => {
  it('emits a 3-segment componentKey for the list view', () => {
    const files = generate(fixture())
    const registry = files.find(f => f.path.includes('Registry'))!
    expect(registry.content).toContain("PageRegistry.register('app.crm.contacts', ContactsListPage)")
  })

  it('emits 4-segment keys for detail/edit/create', () => {
    const files = generate(fixture())
    const registry = files.find(f => f.path.includes('Registry'))!
    expect(registry.content).toContain("PageRegistry.register('app.crm.contacts.detail',")
    expect(registry.content).toContain("PageRegistry.register('app.crm.contacts.edit',")
    expect(registry.content).toContain("PageRegistry.register('app.crm.contacts.create',")
  })

  it('uses the BA-supplied pluralName instead of `${name}s`', () => {
    const files = generate(fixture({
      entities: [{
        name: 'Rue',
        pluralName: 'Rues',
        section: 'rues',
        views: ['list'],
      }],
    }))
    const registry = files.find(f => f.path.includes('Registry'))!
    expect(registry.content).toMatch(/const RuesListPage = lazyWithRetry/)
    expect(registry.content).not.toMatch(/const RusListPage/) // naive fallback would be wrong
  })
})

describe('scaffold-routes / generate — Bug D (parentSection nesting)', () => {
  it('nests sub-resource componentKeys under parentSection', () => {
    const files = generate({
      module: 'referentiels',
      appCode: 'gaf',
      entities: [{
        name: 'QualificatifTypeAudit',
        pluralName: 'QualificatifTypeAudits',
        section: 'types-audit',
        parentSection: 'types-affaire',
        views: ['list', 'detail', 'create', 'edit'],
      }],
      projectPath: '/test',
    })
    const registry = files.find(f => f.path.includes('Registry'))!
    // List view: 4-segment key matches the navigation DB
    expect(registry.content).toContain("'gaf.referentiels.types-affaire.types-audit'")
    // Detail/edit/create: 5-segment keys
    expect(registry.content).toContain("'gaf.referentiels.types-affaire.types-audit.detail'")
    expect(registry.content).toContain("'gaf.referentiels.types-affaire.types-audit.edit'")
    expect(registry.content).toContain("'gaf.referentiels.types-affaire.types-audit.create'")
    // The flat (broken) 3-segment key must NOT appear
    expect(registry.content).not.toContain("'gaf.referentiels.types-audit'")
  })

  it('nests sub-resource default import paths under parentSection', () => {
    const files = generate({
      module: 'referentiels',
      appCode: 'gaf',
      entities: [{
        name: 'Rue',
        pluralName: 'Rues',
        section: 'rues',
        parentSection: 'sites',
        views: ['list', 'detail'],
      }],
      projectPath: '/test',
    })
    const registry = files.find(f => f.path.includes('Registry'))!
    expect(registry.content).toContain("import('@/pages/gaf/referentiels/sites/rues/RuesListPage')")
    expect(registry.content).toContain("import('@/pages/gaf/referentiels/sites/rues/RueDetailPage')")
  })

  it('nests sub-resource URL helpers under parentSection', () => {
    const files = generate({
      module: 'referentiels',
      appCode: 'gaf',
      entities: [{
        name: 'Rue',
        pluralName: 'Rues',
        section: 'rues',
        parentSection: 'sites',
        views: ['list', 'detail', 'edit', 'create'],
      }],
      projectPath: '/test',
    })
    const routes = files.find(f => f.path.includes('Routes'))!
    expect(routes.content).toContain("list: () => '/gaf/referentiels/sites/rues'")
    expect(routes.content).toContain("detail: (id: string) => `/gaf/referentiels/sites/rues/${id}`")
    expect(routes.content).toContain("edit: (id: string) => `/gaf/referentiels/sites/rues/${id}/edit`")
    expect(routes.content).toContain("create: () => '/gaf/referentiels/sites/rues/create'")
  })

  it('flat entities (no parentSection) still emit 3-segment URLs — backward compat', () => {
    const files = generate(fixture())
    const routes = files.find(f => f.path.includes('Routes'))!
    expect(routes.content).toContain("list: () => '/app/crm/contacts'")
    // No spurious empty segment in the URL literals themselves (grepping the
    // whole file would catch the `//` in the header banner).
    expect(routes.content).not.toMatch(/'\/[^']*\/\/[^']*'/) // empty path segment in a single-quoted URL
    expect(routes.content).not.toMatch(/`\/[^`]*\/\/[^`]*`/) // empty path segment in a template URL
  })

  it('accepts 5-segment sub-resource keys (validateComponentKey upper limit bumped to 5)', () => {
    expect(validateComponentKey('gaf.referentiels.types-affaire.types-audit')).toBeNull()
    expect(validateComponentKey('gaf.referentiels.types-affaire.types-audit.detail')).toBeNull()
    // 6 segments still rejected
    expect(validateComponentKey('a.b.c.d.e.f')).toMatch(/must be between 2 and 5/)
  })
})

describe('scaffold-routes / generate — Bug E (deduplicate FormPage const for create+edit)', () => {
  it('emits exactly one `const ContactFormPage = lazyWithRetry` when both create and edit are requested', () => {
    const files = generate(fixture())
    const registry = files.find(f => f.path.includes('Registry'))!
    const matches = registry.content.match(/const ContactFormPage = lazyWithRetry/g) ?? []
    expect(matches).toHaveLength(1)
    // …but BOTH keys still register against that single const
    expect(registry.content).toContain("PageRegistry.register('app.crm.contacts.create', ContactFormPage)")
    expect(registry.content).toContain("PageRegistry.register('app.crm.contacts.edit', ContactFormPage)")
  })

  it('still emits a single const when only one of create/edit is present', () => {
    const files = generate(fixture({
      entities: [{
        name: 'Contact',
        section: 'contacts',
        views: ['list', 'edit'], // edit only
      }],
    }))
    const registry = files.find(f => f.path.includes('Registry'))!
    const matches = registry.content.match(/const ContactFormPage = lazyWithRetry/g) ?? []
    expect(matches).toHaveLength(1)
  })

  it('does not collapse distinct page names — list and detail remain separate consts', () => {
    const files = generate(fixture())
    const registry = files.find(f => f.path.includes('Registry'))!
    expect(registry.content).toMatch(/const ContactsListPage = lazyWithRetry/)
    expect(registry.content).toMatch(/const ContactDetailPage = lazyWithRetry/)
    expect(registry.content).toMatch(/const ContactFormPage = lazyWithRetry/)
  })
})

describe('scaffold-routes / generate — Fix #4 (React.ComponentType<unknown> matches lazyWithRetry constraint)', () => {
  it('emits React.ComponentType<unknown> in the module-cast (NOT bare React.ComponentType)', () => {
    // The SmartStack `lazyWithRetry<T extends ComponentType<unknown>>` constraint
    // rejects `ComponentType<{}>` (the implicit shape of bare
    // `React.ComponentType`). Without the explicit generic, every registration
    // failed TS compilation — `audit-dev-frontend` had to auto-heal every run.
    const files = generate(fixture())
    const registry = files.find(f => f.path.includes('Registry'))!
    // Positive: every cast carries `<unknown>`.
    expect(registry.content).toMatch(/default\?: React\.ComponentType<unknown>;/)
    expect(registry.content).toMatch(/ContactsListPage\?: React\.ComponentType<unknown>/)
    // Negative: the bare form (without `<…>`) MUST NOT leak back.
    expect(registry.content).not.toMatch(/default\?: React\.ComponentType;/)
  })
})

describe('scaffold-routes / generate — Bug F (module-home is the 2-segment app.module root)', () => {
  it('generates a 2-segment key for the module-home view (no redundant suffix)', () => {
    const files = generate({
      module: 'referentiels',
      appCode: 'gaf',
      entities: [{
        name: 'ReferentielsHome',
        pluralName: 'ReferentielsHome',
        section: 'module-home',
        views: ['module-home'],
      }],
      projectPath: '/test',
    })
    const registry = files.find(f => f.path.includes('Registry'))!
    expect(registry.content).toContain("PageRegistry.register('gaf.referentiels', ")
    expect(registry.content).not.toContain("'gaf.referentiels.module-home.module-home'")
    expect(registry.content).not.toContain("'gaf.referentiels.module-home'")
  })

  it('renders a valid TypeScript identifier for hub-view page names (no hyphens)', () => {
    // Regression: the legacy `view.charAt(0).toUpperCase() + view.slice(1)`
    // produced `Module-homePage` — invalid identifier, breaks the build the
    // moment a module-home entity is in the spec.
    const files = generate({
      module: 'referentiels',
      appCode: 'gaf',
      entities: [{
        name: 'ReferentielsHome',
        pluralName: 'ReferentielsHome',
        section: 'module-home',
        views: ['module-home'],
      }],
      projectPath: '/test',
    })
    const registry = files.find(f => f.path.includes('Registry'))!
    expect(registry.content).toMatch(/const ReferentielsHomeModuleHomePage = lazyWithRetry/)
    expect(registry.content).not.toMatch(/Module-home/)
  })

  it('section-home view registers under the section root (same key as list)', () => {
    const files = generate({
      module: 'referentiels',
      appCode: 'gaf',
      entities: [{
        name: 'ReferentielsHome',
        pluralName: 'ReferentielsHome',
        section: 'home',
        views: ['section-home'],
      }],
      projectPath: '/test',
    })
    const registry = files.find(f => f.path.includes('Registry'))!
    // section-home shares its key with the list view by design (3-segment)
    expect(registry.content).toContain("PageRegistry.register('gaf.referentiels.home', ")
  })

  // Bug 7 — kanban + reconduction first-class views
  it('kanban view registers with key {app}.{module}.{section}.kanban + URL helper', () => {
    const files = generate({
      module: 'affaires',
      appCode: 'gaf',
      entities: [{
        name: 'Demande',
        pluralName: 'Demandes',
        section: 'demandes',
        views: ['list', 'kanban'],
      }],
      projectPath: '/test',
    })
    const registry = files.find(f => f.path.includes('Registry'))!
    expect(registry.content).toContain("PageRegistry.register('gaf.affaires.demandes.kanban', ")
    expect(registry.content).toMatch(/const DemandeKanbanPage = lazyWithRetry/)
    const routesFile = files.find(f => f.path.includes('Routes'))!
    expect(routesFile.content).toMatch(/kanban: \(\) => '\/gaf\/affaires\/demandes\/kanban'/)
  })

  it('reconduction view registers with key {app}.{module}.{section}.reconduction + URL helper', () => {
    const files = generate({
      module: 'affaires',
      appCode: 'gaf',
      entities: [{
        name: 'Demande',
        pluralName: 'Demandes',
        section: 'demandes',
        views: ['list', 'reconduction'],
      }],
      projectPath: '/test',
    })
    const registry = files.find(f => f.path.includes('Registry'))!
    expect(registry.content).toContain("PageRegistry.register('gaf.affaires.demandes.reconduction', ")
    expect(registry.content).toMatch(/const DemandeReconductionPage = lazyWithRetry/)
    const routesFile = files.find(f => f.path.includes('Routes'))!
    expect(routesFile.content).toMatch(/reconduction: \(\) => '\/gaf\/affaires\/demandes\/reconduction'/)
  })
})
