/**
 * scaffold-screen-controller — parse-pagespec.ts
 *
 * Reads pagespecs/*.md files in a module folder, extracts the fenced ```json
 * machine block, validates against PageSpecSchema, and returns the parsed
 * pagespecs filtered to a given section.
 *
 * Pure — no Zod throws bubble up; malformed files produce a warning in the
 * second tuple element and are skipped (so a single bad pagespec doesn't
 * abort the whole section generation).
 */

import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs'
import { join } from 'node:path'
import { PageSpecSchema } from './types.js'
import type { PageSpec } from './types.js'

export interface ParseWarning {
  file: string
  reason: string
}

export interface ParseResult {
  pagespecs: PageSpec[]
  warnings: ParseWarning[]
}

/** Extract the FIRST fenced ```json block from a markdown string. Returns null
 *  if no block found. */
export function extractFencedJson(md: string): string | null {
  // Matches ```json\n ... \n``` (lazy, multiline). The closing fence may have
  // trailing whitespace but no other content on its line.
  const re = /```json\s*\r?\n([\s\S]*?)\r?\n```/
  const m = md.match(re)
  return m ? m[1] : null
}

/** Parse a single pagespec.md file. Returns the PageSpec or a warning. */
export function parsePagespecFile(filePath: string, content: string): { ok: PageSpec | null; warning: ParseWarning | null } {
  const json = extractFencedJson(content)
  if (json === null) {
    return { ok: null, warning: { file: filePath, reason: 'no fenced ```json block found' } }
  }
  let raw: unknown
  try {
    raw = JSON.parse(json)
  } catch (err) {
    return { ok: null, warning: { file: filePath, reason: `invalid JSON: ${(err as Error).message}` } }
  }
  const parsed = PageSpecSchema.safeParse(raw)
  if (!parsed.success) {
    const first = parsed.error.issues[0]
    return { ok: null, warning: { file: filePath, reason: `schema violation at ${first.path.join('.')}: ${first.message}` } }
  }
  return { ok: parsed.data, warning: null }
}

/** Read every .md under <moduleDir>/pagespecs/ (one level), parse each, and
 *  return only the pagespecs whose section AND entity match the filters.
 *  When `entity` is omitted, only the section filter is applied (legacy mode). */
export function parsePagespecsForSection(moduleDir: string, section: string, entity?: string): ParseResult {
  const dir = join(moduleDir, 'pagespecs')
  const out: ParseResult = { pagespecs: [], warnings: [] }
  if (!existsSync(dir)) return out

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

  for (const name of entries) {
    if (!name.endsWith('.md')) continue
    const full = join(dir, name)
    if (!statSync(full).isFile()) continue
    let content: string
    try {
      content = readFileSync(full, 'utf8')
    } catch (err) {
      out.warnings.push({ file: full, reason: `cannot read file: ${(err as Error).message}` })
      continue
    }
    const { ok, warning } = parsePagespecFile(full, content)
    if (warning) out.warnings.push(warning)
    if (!ok) continue
    if (ok.section !== section) continue
    if (entity !== undefined && ok.entity !== entity) continue
    out.pagespecs.push(ok)
  }

  // Stable ordering: list → detail → form → dashboard → others (alpha),
  // so the generated file is deterministic regardless of OS readdir order.
  const viewOrder = ['list', 'detail', 'form', 'dashboard', 'kanban', 'card', 'app-home', 'module-home', 'section-home']
  out.pagespecs.sort((a, b) => {
    const ai = viewOrder.indexOf(a.view)
    const bi = viewOrder.indexOf(b.view)
    if (ai !== bi) return ai - bi
    return a.screenCode.localeCompare(b.screenCode)
  })

  return out
}
