/**
 * cli:scaffold-tests-from-ac — generate.ts
 *
 * Pure code-emission. Takes parsed UCs and a spec → returns GeneratedFile[].
 * No filesystem (index.ts writes), no LLM.
 *
 * One file per section that has ≥ 1 AC. Layout:
 *
 *   Tests/{ModuleP}/Acceptance/{SectionP}AcceptanceTests.cs
 *
 * Each AC becomes ONE [Fact] with:
 *   - [Trait("Category","Acceptance")] [Trait("Module",…)] [Trait("AC", <globalId>)]
 *   - Method name AC_NN_{slug-of-first-words}
 *   - Verbatim AC text in a // comment
 *   - // TODO[AC-NN]: ... + Assert.Fail("…") — Phase 5 fills this in.
 *
 * The TODO + Assert.Fail combo guarantees the test fails until a human/LLM
 * implements the assertion. DEV-TEST-001 (Wave 3) audits that no `// TODO[AC-`
 * remains on merge.
 */

import type {
  GeneratedFile,
  ScaffoldTestsFromAcSpec,
  UcWithAc,
} from './types.js'

/** Convert kebab/snake `opportunites-rdv` → `OpportunitesRdv` (PascalCase). */
function pascal(s: string): string {
  return s
    .split(/[-_\s]+/)
    .filter(Boolean)
    .map(p => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase())
    .join('')
}

/** Slug AC text → snake-fragment usable in a C# method name. */
function methodSlug(acText: string, maxWords = 7): string {
  // Strip accents
  const noAccents = acText.normalize('NFD').replace(/[̀-ͯ]/g, '')
  // Keep alphanumerics and spaces; collapse the rest
  const cleaned = noAccents
    .replace(/[^a-zA-Z0-9 ]/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
  const words = cleaned.split(' ').slice(0, maxWords)
  return words
    .map(w => w.toLowerCase())
    .join('_')
    .slice(0, 80)
}

/** Compute the global AC reference `<UC-code>#AC-NN`. */
function globalAcRef(ucCode: string, localId: string): string {
  return `${ucCode}#${localId}`
}

/** Build the default namespace when none is supplied. */
function defaultNamespace(appCode: string, module: string): string {
  return `${pascal(appCode)}.Tests.${pascal(module)}.Acceptance`
}

/** Emit ONE Fact for ONE AC. Indented for the class body (4-space). */
function emitFact(ucCode: string, ucTitle: string, localId: string, acText: string, moduleP: string): string {
  const safeName = `${localId.replace('-', '_')}_${methodSlug(acText)}`.replace(/__+/g, '_')
  const ref = globalAcRef(ucCode, localId)
  // Escape embedded " in AC text for use inside a "..." literal
  const acEscaped = acText.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
  return [
    `    [Fact]`,
    `    [Trait("Category", "Acceptance")]`,
    `    [Trait("Module", "${moduleP}")]`,
    `    [Trait("AC", "${ref}")]`,
    `    public async Task ${safeName}()`,
    `    {`,
    `        // AC: ${acText}`,
    `        // UC: ${ucCode} — ${ucTitle}`,
    `        // TODO[${localId}]: implement assertion (see ${ucCode}#${localId} in use-case.md).`,
    `        //   The /ba-develop Phase 5 subagent fills this body using:`,
    `        //     - the AC text above (what to assert),`,
    `        //     - the controller + DTO scaffolded in Phases 2-3,`,
    `        //     - the WebApplicationFactory<Program> _factory fixture below.`,
    `        await Task.CompletedTask;`,
    `        Assert.Fail("${ref}: not yet implemented — Phase 5 must fill this body. Remove this line + the TODO once implemented.");`,
    `    }`,
    ``,
  ].join('\n')
}

/** Emit ONE full test class (one section's UCs with ACs). */
function emitTestClass(
  ns: string,
  moduleP: string,
  sectionP: string,
  ucsForSection: UcWithAc[]
): string {
  const className = `${sectionP}AcceptanceTests`
  const header = [
    `// <auto-generated by scaffold-tests-from-ac />`,
    `// Each [Fact] in this file is one Acceptance Criterion (AC) declared under a UC`,
    `// in .smartstack/ba/${moduleP.toLowerCase()}/${sectionP.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '')}/use-case.md.`,
    `// Phase 5 of /ba-develop fills the // TODO[AC-NN] body with real assertions.`,
    `// Audit DEV-TEST-001 (Wave 3) blocks merge if any // TODO[AC- marker remains.`,
    ``,
    `using System.Net;`,
    `using System.Net.Http.Json;`,
    `using FluentAssertions;`,
    `using Microsoft.AspNetCore.Mvc.Testing;`,
    `using Xunit;`,
    ``,
    `namespace ${ns};`,
    ``,
    `public class ${className} : IClassFixture<WebApplicationFactory<Program>>`,
    `{`,
    `    private readonly WebApplicationFactory<Program> _factory;`,
    `    private readonly HttpClient _client;`,
    ``,
    `    public ${className}(WebApplicationFactory<Program> factory)`,
    `    {`,
    `        _factory = factory;`,
    `        _client = factory.CreateClient();`,
    `    }`,
    ``,
  ].join('\n')

  const ucBlocks: string[] = []
  for (const uc of ucsForSection) {
    if (uc.acs.length === 0) continue
    ucBlocks.push(`    // ==== ${uc.ucCode} — ${uc.title} ====\n`)
    for (const ac of uc.acs) {
      ucBlocks.push(emitFact(uc.ucCode, uc.title, ac.localId, ac.text, moduleP))
    }
  }

  const footer = `}\n`
  return header + ucBlocks.join('\n') + footer
}

export interface GenerateInput {
  spec: ScaffoldTestsFromAcSpec
  ucs: UcWithAc[]
}

export interface GenerateOutput {
  files: GeneratedFile[]
  ucsScanned: number
  acsParsed: number
  factsEmitted: number
}

/**
 * Group UCs by section, emit one file per section that has ≥ 1 AC, return them.
 * Caller (index.ts) writes them to disk (unless --dry-run).
 */
export function generate(input: GenerateInput): GenerateOutput {
  const { spec, ucs } = input
  const moduleP = pascal(spec.module)
  const ns = spec.namespace ?? defaultNamespace(spec.appCode, spec.module)

  // Group by section
  const bySection = new Map<string, UcWithAc[]>()
  for (const uc of ucs) {
    const key = uc.sectionFolder
    if (!bySection.has(key)) bySection.set(key, [])
    bySection.get(key)!.push(uc)
  }

  const files: GeneratedFile[] = []
  let factsEmitted = 0
  for (const [sectionFolder, sectionUcs] of bySection.entries()) {
    const totalAcs = sectionUcs.reduce((n, uc) => n + uc.acs.length, 0)
    if (totalAcs === 0) continue
    const sectionP = pascal(sectionFolder)
    const content = emitTestClass(ns, moduleP, sectionP, sectionUcs)
    const relPath = `Tests/${moduleP}/Acceptance/${sectionP}AcceptanceTests.cs`
    files.push({ path: relPath, content })
    factsEmitted += totalAcs
  }

  const acsParsed = ucs.reduce((n, uc) => n + uc.acs.length, 0)
  return {
    files,
    ucsScanned: ucs.length,
    acsParsed,
    factsEmitted,
  }
}

// Re-export helpers for tests
export { pascal, methodSlug, globalAcRef, defaultNamespace }
