/**
 * scan.ts — Walk the .smartstack/ba/ tree and extract every code reference
 * embedded in downstream docs.
 *
 * The tree is the source of truth for "which sections currently exist".
 * Downstream docs are parsed to find UC/SCR/BR/RBAC code occurrences whose
 * (APP, MOD, SEC) triplet may no longer match the tree.
 */
import path from 'node:path';
import { findFiles, readText } from '../../../../lib/fs.js';
import type {
  CodeKind,
  ParsedCode,
  ScanResult,
  SectionKey,
  ModuleKey,
} from './types.js';
import { sectionKey, modKey } from './types.js';

/**
 * Regex captures: kind, app, mod, sec, num, optional title-after-em-dash.
 * Matches both heading form (`### UC-APP-MOD-SEC-001 — Title`)
 * and inline body cites (`UC-APP-MOD-SEC-001` in any context).
 */
const CODE_REGEX =
  /\b(UC|SCR|BR|RBAC)-([A-Z][A-Z0-9_]*)-([A-Z][A-Z0-9_]*)-([A-Z][A-Z0-9_]*)-(\d{3})\b/g;

/**
 * Heading-prefixed code (with optional title) for source classification:
 * `^### UC-…-001 — Title` → source: 'heading'
 * everywhere else        → source: 'body'
 */
const HEADING_REGEX =
  /^###\s+((?:UC|SCR|BR|RBAC)-[A-Z][A-Z0-9_]*-[A-Z][A-Z0-9_]*-[A-Z][A-Z0-9_]*-\d{3})(?:\s+[—-]\s+(.+))?\s*$/gm;

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

/**
 * Discover all sections currently present in the menu tree.
 * A section is a `<APP>/<MOD>/<SEC>/index.md` path where APP/MOD/SEC are all
 * UPPERCASE folder names. The folder name is canonicalised to match codes
 * (kebab → underscore, uppercased).
 */
export async function scanTree(baRoot: string): Promise<{
  existingSections: Set<SectionKey>;
  existingModules: Set<ModuleKey>;
}> {
  const indexes = await findFiles('**/index.md', { cwd: baRoot });
  const existingSections = new Set<SectionKey>();
  const existingModules = new Set<ModuleKey>();

  for (const abs of indexes) {
    const rel = path.relative(baRoot, abs).replace(/\\/g, '/');
    const parts = rel.split('/');
    // <APP>/index.md                 → 2 parts → app level (skip — no module/section)
    // <APP>/<MOD>/index.md           → 3 parts → module level
    // <APP>/<MOD>/<SEC>/index.md     → 4 parts → section level
    // <APP>/<MOD>/<SEC>/<RES>/index.md → 5 parts → resource (children of section, skip)
    if (parts.length < 3 || parts.length > 4) continue;
    if (parts[parts.length - 1] !== 'index.md') continue;
    // Skip metadata folders.
    if (parts.some((p) => p.startsWith('_'))) continue;

    const app = parts[0];
    const mod = parts[1];
    if (parts.length === 3) {
      // module-level index.md
      existingModules.add(modKey(app, mod));
    } else {
      // section-level index.md
      const secFolder = parts[2];
      const sec = sectionFolderToCode(secFolder);
      existingSections.add(sectionKey(app, mod, sec));
      existingModules.add(modKey(app, mod));
    }
  }

  return { existingSections, existingModules };
}

/**
 * Section folder names are kebab-case (`exchange-history`).
 * UC codes uppercase them with `-` → `_` (`EXCHANGE_HISTORY`).
 */
export function sectionFolderToCode(folder: string): string {
  return folder.toUpperCase().replace(/-/g, '_');
}

/**
 * Scan all downstream `.md` files and extract every UC/SCR/BR/RBAC code.
 * Returns codes grouped by file.
 */
export async function scanCodes(
  baRoot: string,
): Promise<Map<string, ParsedCode[]>> {
  const codesByFile = new Map<string, ParsedCode[]>();

  for (const pattern of DOWNSTREAM_GLOBS) {
    const files = await findFiles(pattern, { cwd: baRoot });
    for (const file of files) {
      const content = await readText(file);
      const codes = extractCodes(content, file);
      if (codes.length > 0) {
        codesByFile.set(file, codes);
      }
    }
  }

  return codesByFile;
}

/**
 * Extract every UC/SCR/BR/RBAC code from a file's content.
 * Headings are matched first (so we capture their title) and then de-duped
 * against the global pass to avoid double-counting.
 */
export function extractCodes(content: string, file: string): ParsedCode[] {
  const headings = new Map<string, string | undefined>();
  for (const m of content.matchAll(HEADING_REGEX)) {
    const code = m[1];
    const title = m[2]?.trim();
    headings.set(code, title);
  }

  const codes: ParsedCode[] = [];
  const seen = new Set<string>();

  for (const m of content.matchAll(CODE_REGEX)) {
    const [full, kind, app, mod, sec, num] = m;
    const dedupKey = `${full}@${m.index ?? 0}`;
    if (seen.has(dedupKey)) continue;
    seen.add(dedupKey);

    codes.push({
      kind: kind as CodeKind,
      app,
      mod,
      sec,
      num,
      full,
      file,
      title: headings.get(full),
    });
  }

  return codes;
}

/**
 * Full scan: tree + codes + the set of (APP/MOD/SEC) triplets that codes
 * reference.
 */
export async function scan(baRoot: string): Promise<ScanResult> {
  const { existingSections, existingModules } = await scanTree(baRoot);
  const codesByFile = await scanCodes(baRoot);

  const referencedSections = new Set<SectionKey>();
  for (const codes of codesByFile.values()) {
    for (const c of codes) {
      referencedSections.add(sectionKey(c.app, c.mod, c.sec));
    }
  }

  return {
    existingSections,
    existingModules,
    codesByFile,
    referencedSections,
  };
}
