#!/usr/bin/env node
// adr-verify — read the persisted adr-patterns + adr-edges namespaces, surface
// dangling refs, supersede cycles, and status mismatches.
//
// Companion to scripts/import.mjs. Run after import to validate graph integrity.
// Useful in CI: exits with code 1 on supersede cycles by default, or on ANY
// issue (dangling refs, status mismatches) when VERIFY_STRICT=1 is set.
//
// Usage:
//   node scripts/verify.mjs                     # markdown report
//   VERIFY_FORMAT=json node scripts/verify.mjs  # JSON for chaining
//   VERIFY_STRICT=1 node scripts/verify.mjs     # exit 1 on ANY issue (default: only on cycles)

import { spawnSync } from 'node:child_process';

// ADR-100 / #1748 Issue 3 — CLI_CORE=1 routes to lite cli-core (~2s cold-cache).
// verify only does list+retrieve across adr-patterns and adr-edges namespaces;
// no semantic search needed. JSON backend is sufficient.
const CLI_PKG = process.env.CLI_CORE === '1'
  ? '@claude-flow/cli-core@alpha'
  : '@claude-flow/cli@latest';

function memoryListJson(namespace) {
  const r = spawnSync('npx', [
    CLI_PKG, 'memory', 'list',
    '--namespace', namespace, '--format', 'json',
  ], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8' });
  if (r.status !== 0) return [];
  const m = /\[[\s\S]*\]/.exec(r.stdout || '');
  if (!m) return [];
  try { return JSON.parse(m[0]); } catch { return []; }
}
function memoryRetrieve(namespace, key) {
  const r = spawnSync('npx', [
    CLI_PKG, 'memory', 'retrieve',
    '--namespace', namespace, '--key', key,
  ], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8' });
  if (r.status !== 0) return null;
  // Strip ANSI / box-drawing
  const txt = (r.stdout || '').replace(/\x1b\[[0-9;]*m/g, '');
  return txt;
}

const patternEntries = memoryListJson('adr-patterns');
const edgeEntries = memoryListJson('adr-edges');

const adrIds = new Set(
  patternEntries.map((e) => (e.key || '').split('::')[0]).filter(Boolean)
);

// Parse edge values to recover {from, to, relation}
const edges = [];
for (const e of edgeEntries) {
  const k = e.key || '';
  // key format: relation:FROM->TO:timestamp-rand
  const m = /^(\w[\w-]*?):(\S+?)->(\S+?):/.exec(k);
  if (m) edges.push({ relation: m[1], from: m[2], to: m[3], key: k });
}

const danglingRefs = edges.filter((e) => !adrIds.has(e.to));
const danglingFroms = edges.filter((e) => !adrIds.has(e.from));

// Cycle detection on supersedes (cycle = data corruption — ADR can't supersede itself transitively)
const supersedesGraph = new Map();
for (const e of edges.filter((e) => e.relation === 'supersedes')) {
  if (!supersedesGraph.has(e.from)) supersedesGraph.set(e.from, []);
  supersedesGraph.get(e.from).push(e.to);
}
const cycles = [];
function findCycle(node, visited, stack) {
  if (stack.has(node)) {
    cycles.push([...stack, node].join(' → '));
    return;
  }
  if (visited.has(node)) return;
  visited.add(node);
  stack.add(node);
  for (const next of supersedesGraph.get(node) || []) {
    findCycle(next, visited, stack);
  }
  stack.delete(node);
}
for (const n of supersedesGraph.keys()) findCycle(n, new Set(), new Set());

const result = {
  adrCount: adrIds.size,
  edgeCount: edges.length,
  byRelation: edges.reduce((acc, e) => { acc[e.relation] = (acc[e.relation] || 0) + 1; return acc; }, {}),
  danglingRefs,
  danglingFroms,
  cycles: [...new Set(cycles)],
};

if (process.env.VERIFY_FORMAT === 'json') {
  console.log(JSON.stringify(result, null, 2));
} else {
  console.log('## ADR Graph Verification');
  console.log('');
  console.log(`| Metric | Value |`);
  console.log(`|---|---:|`);
  console.log(`| ADRs in adr-patterns | ${result.adrCount} |`);
  console.log(`| Edges in adr-edges | ${result.edgeCount} |`);
  for (const [k, n] of Object.entries(result.byRelation).sort((a, b) => b[1] - a[1])) {
    console.log(`| edges (${k}) | ${n} |`);
  }
  console.log(`| Dangling 'to' refs | ${result.danglingRefs.length} |`);
  console.log(`| Dangling 'from' refs | ${result.danglingFroms.length} |`);
  console.log(`| Supersede cycles | ${result.cycles.length} |`);
  if (result.danglingRefs.length) {
    console.log('\n### Sample dangling refs');
    for (const d of result.danglingRefs.slice(0, 8)) console.log(`- ${d.relation} ${d.from} → ${d.to} (missing)`);
  }
  if (result.cycles.length) {
    console.log('\n### Cycles (DATA CORRUPTION — fix immediately)');
    for (const c of result.cycles) console.log(`- ${c}`);
  }
}

const strict = process.env.VERIFY_STRICT === '1';
if (result.cycles.length || (strict && (result.danglingRefs.length || result.danglingFroms.length))) {
  process.exit(1);
}
