/**
 * clean.ts — Apply RENAME / DELETE changes to the downstream .md files.
 *
 * RENAME: substitute every UC/SCR/BR/RBAC code carrying the old (APP, MOD, SEC)
 *         triplet for the same code carrying the new triplet. Idempotent.
 *
 * DELETE: drop every heading block (### CODE — title … until next ### / ## / EOF)
 *         whose code carries the deleted triplet, AND scrub body references to
 *         those codes (Règles liées, Écrans liés, linkedUseCases lines).
 *         Empty list bullets collapse to `—`.
 *
 * Both passes touch ONLY files inside baRoot. Directory deletes happen only when
 * the deleted section's folder is empty after the pass.
 */
import path from 'node:path';
import {
  findFiles,
  readText,
  writeText,
  removeDirectory,
  directoryExists,
} from '../../../../lib/fs.js';
import type {
  RenameChange,
  DeletionChange,
  SectionKey,
} from './types.js';

interface ApplyResult {
  filesModified: string[];
  directoriesRemoved: string[];
}

const DOWNSTREAM_GLOBS = [
  '**/use-case.md',
  '**/screen.md',
  '**/règles-métier.md',
  '**/rbac.md',
];

/**
 * Apply the full plan (renames first, then deletions). Returns the set of
 * files actually modified on disk.
 */
export async function applyPlan(
  baRoot: string,
  renames: RenameChange[],
  deletions: DeletionChange[],
): Promise<ApplyResult> {
  const modified = new Set<string>();
  const removed = new Set<string>();

  // Discover the candidate file set once — same list for renames + deletions.
  const allFiles = await collectDownstreamFiles(baRoot);

  // --- Pass 1: renames -------------------------------------------------
  for (const r of renames) {
    const [fromApp, fromMod, fromSec] = r.from.split('/');
    const [, , toSec] = r.to.split('/');
    if (!fromSec || !toSec) continue;

    for (const file of allFiles) {
      const before = await readText(file);
      const after = renameCodesInText(before, fromApp, fromMod, fromSec, toSec);
      if (after !== before) {
        await writeText(file, after);
        modified.add(file);
      }
    }
  }

  // --- Pass 2: deletions ----------------------------------------------
  for (const d of deletions) {
    const [delApp, delMod, delSec] = (d.path as SectionKey).split('/');
    if (!delSec) continue;

    for (const file of allFiles) {
      const before = await readText(file);
      const after = removeCodesFromText(before, delApp, delMod, delSec);
      if (after !== before) {
        await writeText(file, after);
        modified.add(file);
      }
    }

    // If the deleted section's folder still exists on disk (orphan after the
    // menu was edited), remove it.
    const sectionFolder = sectionCodeToFolder(delSec);
    const dir = path.join(baRoot, delApp, delMod, sectionFolder);
    if (await directoryExists(dir)) {
      await removeDirectory(dir);
      removed.add(dir);
    }
  }

  return {
    filesModified: Array.from(modified).sort(),
    directoriesRemoved: Array.from(removed).sort(),
  };
}

async function collectDownstreamFiles(baRoot: string): Promise<string[]> {
  const all = new Set<string>();
  for (const pattern of DOWNSTREAM_GLOBS) {
    const files = await findFiles(pattern, { cwd: baRoot });
    for (const f of files) all.add(f);
  }
  return Array.from(all).sort();
}

// ---------------------------------------------------------------------------
// Rename
// ---------------------------------------------------------------------------

export function renameCodesInText(
  content: string,
  app: string,
  mod: string,
  fromSec: string,
  toSec: string,
): string {
  // Substitute every `(KIND)-APP-MOD-FROM-NNN` for `(KIND)-APP-MOD-TO-NNN`.
  const pattern = new RegExp(
    `\\b(UC|SCR|BR|RBAC)-${escapeRegex(app)}-${escapeRegex(mod)}-${escapeRegex(fromSec)}-(\\d{3})\\b`,
    'g',
  );
  return content.replace(pattern, (_m, kind, num) => `${kind}-${app}-${mod}-${toSec}-${num}`);
}

// ---------------------------------------------------------------------------
// Delete
// ---------------------------------------------------------------------------

export function removeCodesFromText(
  content: string,
  app: string,
  mod: string,
  sec: string,
): string {
  let work = removeHeadingBlocks(content, app, mod, sec);
  work = scrubBodyReferences(work, app, mod, sec);
  return work;
}

/**
 * Drop every block starting with `### {KIND}-APP-MOD-SEC-NNN` and ending at
 * the next `### `, `## `, or EOF.
 */
export function removeHeadingBlocks(
  content: string,
  app: string,
  mod: string,
  sec: string,
): string {
  const headingPattern = new RegExp(
    `^###\\s+(UC|SCR|BR|RBAC)-${escapeRegex(app)}-${escapeRegex(mod)}-${escapeRegex(sec)}-\\d{3}\\b.*$`,
    'm',
  );

  let work = content;
  while (true) {
    const match = headingPattern.exec(work);
    if (!match) break;
    const start = match.index;
    const after = start + match[0].length;
    // Find the next ### or ## heading (or EOF) from `after`.
    const tailPattern = /^(?:###?\s)/m;
    const tail = work.slice(after);
    const nextMatch = tailPattern.exec(tail);
    const end = nextMatch ? after + nextMatch.index : work.length;
    work = work.slice(0, start) + work.slice(end);
    // Collapse triple+ blank lines that may result.
    work = work.replace(/\n{3,}/g, '\n\n');
  }
  return work;
}

/**
 * Remove body-form references (in bullets, lists, table cells). Replaces an
 * empty list bullet ("- " or "  -") with "- —". Removes a `linkedUseCases:`
 * field that becomes empty.
 */
export function scrubBodyReferences(
  content: string,
  app: string,
  mod: string,
  sec: string,
): string {
  const codeRegex = new RegExp(
    `\\b(?:UC|SCR|BR|RBAC)-${escapeRegex(app)}-${escapeRegex(mod)}-${escapeRegex(sec)}-\\d{3}\\b`,
    'g',
  );

  // 1. Drop a comma-separated list entry. "A, UC-…-001, B" → "A, B".
  let work = content.replace(
    new RegExp(`,\\s*${codeRegex.source}|${codeRegex.source}\\s*,\\s*`, 'g'),
    '',
  );

  // 2. Replace standalone tokens with empty string.
  work = work.replace(codeRegex, '');

  // 3. Normalize empty bullets `- ` → `- —`.
  work = work.replace(/^(\s*-\s*)$/gm, '$1—');

  // 4. Normalize `**Règles liées**\s*:\s*$` → `**Règles liées** : —`.
  work = work.replace(
    /^(\s*-\s*\*\*[^*]+\*\*\s*:\s*)$/gm,
    '$1—',
  );

  // 5. Normalize trailing `: ,` or `:  ` from removed lists.
  work = work.replace(/:\s*,/g, ': —');

  // 6. Collapse table rows that become all-empty cells.
  //    (Conservative: only collapse rows whose every cell is `|` plus whitespace.)
  work = work.replace(/^\|(\s*\|)+\s*$\n?/gm, '');

  return work;
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

export function escapeRegex(s: string): string {
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

/** Section code (UPPERCASE_UNDERSCORE) → kebab folder name. */
export function sectionCodeToFolder(code: string): string {
  return code.toLowerCase().replace(/_/g, '-');
}

// ---------------------------------------------------------------------------
// Verdict file (`_audit/reconcile.md`)
// ---------------------------------------------------------------------------

export interface VerdictInput {
  baRoot: string;
  renames: RenameChange[];
  deletions: DeletionChange[];
  staleRefsCount: number;
  filesModified: string[];
  directoriesRemoved: string[];
  dryRun: boolean;
}

export function buildVerdict(input: VerdictInput): string {
  const date = new Date().toISOString().slice(0, 10);
  const errCount = input.deletions.length;
  const warnCount = input.renames.length;
  const okCount =
    errCount === 0 && warnCount === 0 ? 1 : 0;
  const emoji = errCount > 0 ? '❌' : warnCount > 0 ? '⚠️' : '✅';

  const lines: string[] = [
    `<!-- ba:audit dimension=reconcile scope=project -->`,
    `# Audit reconciliation menu — Projet`,
    `_${date} · Verdict : ${emoji} ${warnCount} warn · ${errCount} err · ${okCount} ok_`,
    ``,
  ];

  if (okCount > 0) {
    lines.push(`## ✅ Conforme`);
    lines.push(`- **RECON-OK** — Le menu est cohérent avec les documents downstream. Aucun rename ni suppression à propager.`);
    lines.push(``);
  }

  if (warnCount > 0) {
    lines.push(`## ⚠️ Avertissements`);
    for (const r of input.renames) {
      const confidence = (r.confidence * 100).toFixed(0);
      const tag = input.dryRun ? '(dry-run)' : 'appliqué';
      lines.push(
        `- **RECON-RENAME** — Section renommée détectée : **\`${r.from}\`** → **\`${r.to}\`** (confiance ${confidence}% · ${r.reason}) ${tag}.`,
      );
      lines.push(`  - → Les codes UC/SCR/BR/RBAC ont été ${input.dryRun ? 'planifiés pour être réécrits' : 'réécrits'} sur le nouveau code section.`);
    }
    lines.push(``);
  }

  if (errCount > 0) {
    lines.push(`## ❌ Bloquants`);
    for (const d of input.deletions) {
      const tag = input.dryRun ? '(dry-run)' : 'supprimé';
      lines.push(
        `- **RECON-DELETE** — Section supprimée détectée : **\`${d.path}\`** (${d.codeCount} code(s) orphelin(s) · raison : ${d.reason}) ${tag}.`,
      );
      lines.push(`  - → ${input.dryRun ? 'Les blocs UC/SCR seront supprimés' : 'Les blocs UC/SCR ont été supprimés'} et les références body nettoyées.`);
    }
    lines.push(``);
  }

  lines.push(`## Fichiers impactés`);
  if (input.filesModified.length === 0) {
    lines.push(`_Aucun fichier modifié._`);
  } else {
    for (const f of input.filesModified) {
      const rel = path.relative(input.baRoot, f).replace(/\\/g, '/');
      lines.push(`- \`${rel}\``);
    }
  }

  if (input.directoriesRemoved.length > 0) {
    lines.push(``);
    lines.push(`## Dossiers supprimés`);
    for (const d of input.directoriesRemoved) {
      const rel = path.relative(input.baRoot, d).replace(/\\/g, '/');
      lines.push(`- \`${rel}\``);
    }
  }

  lines.push(``);
  lines.push(`---`);
  lines.push(`_Fix : relancer \`/ba-reconcile-menu\` puis \`/ba-loop\` pour ré-enrichir._`);

  return lines.join('\n') + '\n';
}
