/**
 * cli:compute-page-diff — scan-pagespecs.ts
 *
 * Reads every `<moduleRoot>/pagespecs/*.md`, extracts the fenced ```json
 * machine block, and computes a canonical content hash per page. Unlike
 * `scaffold-screen-controller/parse-pagespec.ts` (which filters by section +
 * validates against PageSpecSchema), this scanner is NON-filtering and
 * schema-agnostic: a diff only needs the page identity + a stable hash of the
 * raw spec, so it keeps every JSON-valid pagespec in the module.
 *
 * Pure read-only. Malformed files (no fenced block / invalid JSON) produce a
 * warning and are skipped — a single bad pagespec never aborts the diff.
 */
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs'
import { join, basename } from 'node:path'
import { canonicalHash } from '../../../lib/canonical-hash.js'

export const SNAPSHOT_FILENAME = '.run-snapshot.json'

export interface ScannedPage {
  /** Page identity = pagespec filename without `.md` (convention `<Entity>.<view>`). */
  key: string
  /** Canonical sha256 of the parsed fenced JSON. */
  hash: string
}

export interface ScanResult {
  pages: ScannedPage[]
  warnings: string[]
}

export interface RunSnapshot {
  lastRun?: string
  /** Flat `key → hash` map. */
  pages: Record<string, string>
}

/** Extract the FIRST fenced ```json block. Identical regex to parse-pagespec. */
export function extractFencedJson(md: string): string | null {
  const m = md.match(/```json\s*\r?\n([\s\S]*?)\r?\n```/)
  return m ? m[1] : null
}

/** Page identity from the pagespec filename (`TypeAffaire.list.md` → `TypeAffaire.list`). */
export function pageKeyFromFilename(filename: string): string {
  return basename(filename, '.md')
}

/**
 * Scan `<moduleRoot>/pagespecs/*.md` → `{ key, hash }[]` sorted by key.
 * Returns an empty result (no warning) when the `pagespecs/` dir is absent.
 */
export function scanPagespecs(moduleRoot: string): ScanResult {
  const dir = join(moduleRoot, 'pagespecs')
  const out: ScanResult = { pages: [], warnings: [] }
  if (!existsSync(dir)) return out

  let entries: string[]
  try {
    entries = readdirSync(dir)
  } catch (err) {
    out.warnings.push(`cannot read ${dir}: ${(err as Error).message}`)
    return out
  }

  for (const name of entries) {
    if (!name.endsWith('.md')) continue
    const full = join(dir, name)
    try {
      if (!statSync(full).isFile()) continue
    } catch {
      continue
    }
    let content: string
    try {
      content = readFileSync(full, 'utf8')
    } catch (err) {
      out.warnings.push(`${name}: cannot read file: ${(err as Error).message}`)
      continue
    }
    const json = extractFencedJson(content)
    if (json === null) {
      out.warnings.push(`${name}: no fenced \`\`\`json block found — skipped`)
      continue
    }
    let parsed: unknown
    try {
      parsed = JSON.parse(json)
    } catch (err) {
      out.warnings.push(`${name}: invalid JSON: ${(err as Error).message} — skipped`)
      continue
    }
    out.pages.push({ key: pageKeyFromFilename(name), hash: canonicalHash(parsed) })
  }

  out.pages.sort((a, b) => a.key.localeCompare(b.key))
  return out
}

/**
 * Load `<moduleRoot>/.run-snapshot.json`. Returns an empty snapshot (and
 * `found: false`) when the file is absent OR malformed — both cases mean
 * "treat every current page as new", which the diff renders as `added`.
 */
export function loadSnapshot(moduleRoot: string): { snapshot: RunSnapshot; found: boolean } {
  const path = join(moduleRoot, SNAPSHOT_FILENAME)
  if (!existsSync(path)) return { snapshot: { pages: {} }, found: false }
  try {
    const raw = JSON.parse(readFileSync(path, 'utf8')) as Partial<RunSnapshot>
    const pages = raw.pages && typeof raw.pages === 'object' ? raw.pages : {}
    return { snapshot: { pages, lastRun: raw.lastRun }, found: true }
  } catch {
    return { snapshot: { pages: {} }, found: false }
  }
}
