#!/usr/bin/env node
import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'

const __dirname = dirname(fileURLToPath(import.meta.url))
const ROOT = join(__dirname, '..')
const WORKFLOWS_DIR = join(ROOT, '.claude', 'workflows')

async function parseMeta(path) {
  const src = await readFile(path, 'utf8')
  const marker = 'export const meta = '
  const startIdx = src.indexOf(marker)
  if (startIdx === -1) return null
  let depth = 0, i = startIdx + marker.length, started = false
  for (; i < src.length; i++) {
    if (src[i] === '{') { depth++; started = true }
    else if (src[i] === '}' && started) { depth--; if (depth === 0) { i++; break } }
  }
  const metaSrc = src.slice(startIdx + marker.length, i)
  try {
    return new Function(`return (${metaSrc})`)()
  } catch (e) {
    return { _parseError: e.message }
  }
}

const files = (await readdir(WORKFLOWS_DIR)).filter(f => f.endsWith('.js')).sort()
const catalog = []
for (const f of files) {
  const meta = await parseMeta(join(WORKFLOWS_DIR, f))
  if (meta && !meta._parseError) catalog.push({ _file: f, ...meta })
  else if (meta?._parseError) console.error(`Parse error in ${f}: ${meta._parseError}`)
}

const fmt = {
  cost: c => c ? `${Math.round(c.min / 1000)}k–${Math.round(c.max / 1000)}k` : '—',
  list: xs => xs?.length ? xs.join(', ') : '—',
  yn: b => b ? 'yes' : 'no',
}

function renderTable() {
  const lines = []
  lines.push('| Workflow | Tier | Cadence | Portable | Cost (out tok) | Phases | Composes | Composed by |')
  lines.push('|---|---|---|---|---|---|---|---|')
  for (const w of catalog) {
    const a = w.acos ?? {}
    lines.push(`| \`${w.name}\` | ${a.tier ?? '—'} | ${a.cadence ?? '—'} | ${fmt.yn(a.portable)} | ${fmt.cost(a.estimatedCost)} | ${(w.phases ?? []).length} | ${fmt.list(a.composes)} | ${fmt.list(a.composedBy)} |`)
  }
  return lines.join('\n')
}

function renderGraph() {
  const lines = ['```mermaid', 'graph LR']
  const seen = new Set()
  for (const w of catalog) {
    const a = w.acos ?? {}
    const tag = a.portable ? 'portable' : 'frankx'
    lines.push(`  ${w.name}["${w.name}<br/><i>${tag}</i>"]`)
    for (const c of (a.composes ?? [])) {
      const edge = `${w.name}-->${c}`
      if (!seen.has(edge)) { lines.push(`  ${edge}`); seen.add(edge) }
    }
  }
  lines.push('  classDef portable fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a')
  lines.push('  classDef frankx fill:#fef3c7,stroke:#b45309,color:#78350f')
  for (const w of catalog) {
    lines.push(`  class ${w.name} ${w.acos?.portable ? 'portable' : 'frankx'}`)
  }
  lines.push('```')
  return lines.join('\n')
}

function renderDescriptions() {
  const lines = []
  for (const w of catalog) {
    lines.push(`### \`${w.name}\``)
    lines.push('')
    lines.push(w.description ?? '')
    lines.push('')
    if (w.whenToUse) {
      lines.push(`**When to use:** ${w.whenToUse}`)
      lines.push('')
    }
    lines.push(`**Phases:** ${(w.phases ?? []).map(p => p.title).join(' → ')}`)
    lines.push('')
    const a = w.acos ?? {}
    lines.push(`**Tier:** ${a.tier ?? '—'} · **Cadence:** ${a.cadence ?? '—'} · **Portable:** ${fmt.yn(a.portable)} · **Est. cost:** ${fmt.cost(a.estimatedCost)} tokens`)
    lines.push('')
    lines.push(`**Invoke:**`)
    lines.push('```js')
    lines.push(`Workflow({ name: '${w.name}', args: { /* see ${w._file} */ } })`)
    lines.push('```')
    lines.push('')
  }
  return lines.join('\n')
}

function renderDoc() {
  const portable = catalog.filter(w => w.acos?.portable)
  const local = catalog.filter(w => !w.acos?.portable)
  const totalCostMin = catalog.reduce((s, w) => s + (w.acos?.estimatedCost?.min ?? 0), 0)
  const totalCostMax = catalog.reduce((s, w) => s + (w.acos?.estimatedCost?.max ?? 0), 0)
  return [
    '# Workflow Catalog',
    '',
    `_Auto-generated by \`node scripts/workflow-catalog.mjs write\`. Source of truth: \`.claude/workflows/\`._`,
    '',
    `**Total:** ${catalog.length} workflows · **Portable (ACOS-shippable):** ${portable.length} · **FrankX-local:** ${local.length}`,
    `**Cost envelope (sum of one run of each):** ${Math.round(totalCostMin/1000)}k–${Math.round(totalCostMax/1000)}k output tokens`,
    '',
    '## At a glance',
    '',
    renderTable(),
    '',
    '## Composition graph',
    '',
    renderGraph(),
    '',
    '## How to observe a run',
    '',
    '- **Live progress:** the `/workflows` slash command shows running workflows with per-phase, per-agent status.',
    '- **Run identifiers:** every `Workflow({...})` call returns a `runId` — use it to grep transcript logs.',
    '- **Token accounting:** pass a `+500k`-style directive to cap total output tokens. Workflows respect `budget.remaining()` and refuse to spawn agents past the ceiling.',
    '- **Catalog regeneration:** `node scripts/workflow-catalog.mjs write` regenerates this file.',
    '- **JSON dump (for tooling):** `node scripts/workflow-catalog.mjs json > workflows.json`',
    '- **Mermaid only:** `node scripts/workflow-catalog.mjs mermaid`',
    '',
    '## How to invoke',
    '',
    '```js',
    "// In a Claude Code session, ask for the Workflow tool:",
    "// 'Run the pre-deploy-sweep workflow'",
    "// Or programmatically via tool call:",
    "Workflow({ name: 'pre-deploy-sweep', args: { baseRef: 'main' } })",
    '```',
    '',
    '## Portability contract',
    '',
    'Workflows tagged `portable: true` ship to the ACOS repo for general use. They:',
    '- Make no assumption about repo-specific paths (FrankX-only paths live in `acos.portable: false`).',
    '- Auto-detect package manager / test framework / language conventions.',
    '- Compose only other portable workflows.',
    '- Carry self-contained schemas (no external type imports).',
    '',
    '## Workflows',
    '',
    renderDescriptions(),
  ].join('\n')
}

const mode = process.argv[2] ?? 'table'

if (mode === 'table' || mode === 'list') {
  console.log(renderTable())
} else if (mode === 'graph' || mode === 'mermaid') {
  console.log(renderGraph())
} else if (mode === 'json') {
  console.log(JSON.stringify(catalog, null, 2))
} else if (mode === 'doc') {
  console.log(renderDoc())
} else if (mode === 'write') {
  const doc = renderDoc()
  const docsDir = join(ROOT, 'docs', 'ops')
  await mkdir(docsDir, { recursive: true })
  await writeFile(join(docsDir, 'WORKFLOWS.md'), doc)
  console.log(`Wrote docs/ops/WORKFLOWS.md (${catalog.length} workflows · ${catalog.filter(w => w.acos?.portable).length} portable)`)
} else {
  console.error(`Unknown mode: ${mode}. Use: table | graph | json | doc | write`)
  process.exit(1)
}
