#!/usr/bin/env node

import { readFileSync, existsSync, readdirSync } from 'node:fs';
import { join, resolve, relative } from 'node:path';

const PROJECT_ROOT = resolve(import.meta.dirname, '..', '..');
const PLANS_DIR = join(PROJECT_ROOT, 'ai-dev', 'plans');

const PLAN_STATUS_RE = /^>\s*(?:Plan\s+)?Status:\s*\*{0,2}(proposed|planned|in progress|partially completed|completed|superseded|replaced|deferred|cancelled|draft)\*{0,2}\s*$/im;
const PHASE_HEADER_RE = /^#{2,3}\s+(?:Phase|Workstream)\s+\d+/i;
const PHASE_STATUS_RE = /^Status:\s*(.*)$/im;
const CHECKLIST_UNCHECKED_RE = /^(\s*)-\s+\[\s?\]\s+(.+)$/gm;
const CHECKLIST_CHECKED_RE = /^(\s*)-\s+\[x\]\s+(.+)$/gim;
const EXIT_CRITERIA_RE = /^#{2,4}\s+Exit\s+Criteria/i;
const CLOSURE_GATES_RE = /^#{2,4}\s+Closure\s+Gates/i;
const DEFERRED_RE = /^#{2,4}\s+Deferred\s+But\s+Adjudicated/i;
const NON_BLOCKING_RE = /^#{2,4}\s+Non-Blocking\s+Follow/i;

function toPosix(p) {
  return p.split(/\\/).join('/');
}

function getSectionRange(content, startIdx) {
  const nextHeading = content.indexOf('\n## ', startIdx + 1);
  return nextIdx => nextHeading === -1 ? content.length : nextHeading;
}

function parseSections(content) {
  const sections = [];
  const lines = content.split('\n');
  let currentSection = { heading: '_top', startLine: 1, startIndex: 0 };
  
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    if (/^#{2,4}\s+/.test(line)) {
      if (currentSection) {
        currentSection.endLine = i;
        currentSection.endIndex = content.indexOf('\n' + line, currentSection.startIndex);
      }
      sections.push({ heading: line.replace(/^#+\s+/, ''), startLine: i + 1, startIndex: content.indexOf(line) });
      currentSection = sections[sections.length - 1];
    }
  }
  if (currentSection) {
    currentSection.endLine = lines.length;
    currentSection.endIndex = content.length;
  }
  return sections;
}

function analyzePlan(filePath) {
  const content = readFileSync(filePath, 'utf-8');
  const relPath = toPosix(relative(PROJECT_ROOT, filePath));
  const lines = content.split('\n');
  
  const statusMatch = content.match(PLAN_STATUS_RE);
  const planStatus = statusMatch ? statusMatch[1].trim().toLowerCase() : 'unknown';
  
  const isCompleted = planStatus === 'completed';
  const isTerminal = ['completed', 'superseded', 'replaced', 'cancelled'].includes(planStatus);
  
  const sections = parseSections(content);
  
  const phaseSections = sections.filter(s => /^Phase\s+\d+|^Workstream\s+\d+/.test(s.heading));
  
  const allUnchecked = [];
  const allChecked = [];
  
  let m;
  
  CHECKLIST_UNCHECKED_RE.lastIndex = 0;
  while ((m = CHECKLIST_UNCHECKED_RE.exec(content)) !== null) {
    const lineNum = content.substring(0, m.index).split('\n').length;
    allUnchecked.push({ line: lineNum, text: m[2].trim(), indent: m[1].length });
  }
  
  CHECKLIST_CHECKED_RE.lastIndex = 0;
  while ((m = CHECKLIST_CHECKED_RE.exec(content)) !== null) {
    const lineNum = content.substring(0, m.index).split('\n').length;
    allChecked.push({ line: lineNum, text: m[2].trim(), indent: m[1].length });
  }
  
  const uncheckedBySection = [];
  for (const section of phaseSections) {
    const sectionContent = content.substring(section.startIndex, section.endIndex);
    const sectionUnchecked = [];
    CHECKLIST_UNCHECKED_RE.lastIndex = 0;
    while ((m = CHECKLIST_UNCHECKED_RE.exec(sectionContent)) !== null) {
      const lineNum = section.startLine + sectionContent.substring(0, m.index).split('\n').length - 1;
      sectionUnchecked.push({ line: lineNum, text: m[2].trim() });
    }
    if (sectionUnchecked.length > 0) {
      const phaseStatusMatch = sectionContent.match(PHASE_STATUS_RE);
      const phaseStatus = phaseStatusMatch ? phaseStatusMatch[1].trim() : 'unknown';
      uncheckedBySection.push({
        section: section.heading,
        status: phaseStatus,
        unchecked: sectionUnchecked
      });
    }
  }
  
  const closureSection = sections.find(s => /^Closure\s+Gates/i.test(s.heading));
  const closureUnchecked = [];
  if (closureSection) {
    const sectionContent = content.substring(closureSection.startIndex, closureSection.endIndex);
    CHECKLIST_UNCHECKED_RE.lastIndex = 0;
    while ((m = CHECKLIST_UNCHECKED_RE.exec(sectionContent)) !== null) {
      const lineNum = closureSection.startLine + sectionContent.substring(0, m.index).split('\n').length - 1;
      closureUnchecked.push({ line: lineNum, text: m[2].trim() });
    }
  }
  
  const closureAuditSection = sections.find(s => /^Closure$/i.test(s.heading));
  const hasClosureEvidence = content.match(/Closure Audit Evidence|Closure Evidence|Reviewer.*Agent.*audit/i) !== null
    || (closureAuditSection && content.substring(closureAuditSection.startIndex, closureAuditSection.endIndex).includes('Evidence:'));
  
  const deferredSection = sections.find(s => /^Deferred\s+But\s+Adjudicated/i.test(s.heading));
  const deferredItems = [];
  if (deferredSection) {
    const sectionContent = content.substring(deferredSection.startIndex, deferredSection.endIndex);
    CHECKLIST_UNCHECKED_RE.lastIndex = 0;
    while ((m = CHECKLIST_UNCHECKED_RE.exec(sectionContent)) !== null) {
      const lineNum = deferredSection.startLine + sectionContent.substring(0, m.index).split('\n').length - 1;
      deferredItems.push({ line: lineNum, text: m[2].trim() });
    }
  }
  
  const phaseStatuses = phaseSections.map(section => {
    const sectionContent = content.substring(section.startIndex, section.endIndex);
    const phaseStatusMatch = sectionContent.match(PHASE_STATUS_RE);
    return {
      name: section.heading,
      status: phaseStatusMatch ? phaseStatusMatch[1].trim() : 'unknown'
    };
  });
  
  return {
    file: relPath,
    planStatus,
    isCompleted,
    isTerminal,
    totalUnchecked: allUnchecked.length,
    totalChecked: allChecked.length,
    uncheckedBySection,
    closureUnchecked,
    hasClosureEvidence,
    deferredItems,
    phaseStatuses,
    allUnchecked
  };
}

function formatReport(result, verbose) {
  const { file, planStatus, isCompleted, totalUnchecked, totalChecked } = result;
  
  if (result.isTerminal && totalUnchecked === 0) {
    if (!verbose) return null;
    return `[PASS] ${file} — status: ${planStatus}, all ${totalChecked} items checked`;
  }
  
  if (totalUnchecked === 0 && !isCompleted) {
    return `[PASS] ${file} — status: ${planStatus}, all ${totalChecked} items checked (not yet completed)`;
  }
  
  if (totalUnchecked === 0 && isCompleted) {
    return `[PASS] ${file} — status: completed, all ${totalChecked} items checked`;
  }
  
  let report = `[FAIL] ${file} — status: ${planStatus}, ${totalUnchecked} unchecked of ${totalUnchecked + totalChecked} total\n`;
  
  if (isCompleted) {
    report += `  ERROR: Plan is marked "completed" but has ${totalUnchecked} unchecked checklist items!\n`;
    report += `  Per plan guide rule #18 and #26: ALL checklist items must be [x] before marking completed.\n`;
    report += `  Either complete the items, move them to "Deferred But Adjudicated", or revert plan status.\n`;
  }
  
  for (const section of result.uncheckedBySection) {
    report += `\n  ${section.section} (status: ${section.status}) — ${section.unchecked.length} unchecked:\n`;
    for (const item of section.unchecked) {
      report += `    L${item.line}: - [ ] ${item.text}\n`;
    }
  }
  
  if (result.closureUnchecked.length > 0) {
    report += `\n  Closure Gates — ${result.closureUnchecked.length} unchecked:\n`;
    for (const item of result.closureUnchecked) {
      report += `    L${item.line}: - [ ] ${item.text}\n`;
    }
  }
  
  if (isCompleted && !result.hasClosureEvidence) {
    report += `\n  WARNING: Plan is "completed" but Closure section has no Evidence record.\n`;
    report += `  Per plan guide rule #27: Closure evidence MUST be written into the plan file.\n`;
  }
  
  return report;
}

function main() {
  const args = process.argv.slice(2);
  const strictMode = args.includes('--strict');
  const verbose = args.includes('--verbose') || args.includes('-v');
  
  const planFiles = [];
  
  const specificPlans = args.filter(a => !a.startsWith('-') && a.endsWith('.md'));
  if (specificPlans.length > 0) {
    for (const name of specificPlans) {
      const direct = join(process.cwd(), name);
      if (existsSync(direct)) {
        planFiles.push(direct);
      } else {
        const inPlansDir = join(PLANS_DIR, name);
        if (existsSync(inPlansDir)) {
          planFiles.push(inPlansDir);
        } else {
          console.error(`Plan file not found: ${name}`);
          process.exit(1);
        }
      }
    }
  } else {
    if (!existsSync(PLANS_DIR)) {
      console.error(`Plans directory not found: ${PLANS_DIR}`);
      process.exit(1);
    }
    for (const entry of readdirSync(PLANS_DIR, { withFileTypes: true })) {
      if (entry.isFile() && entry.name.endsWith('.md') && !entry.name.startsWith('00-')) {
        planFiles.push(join(PLANS_DIR, entry.name));
      }
    }
    planFiles.sort();
  }
  
  if (planFiles.length === 0) {
    console.log('No plan files found.');
    process.exit(0);
  }
  
  console.log(`Checking ${planFiles.length} plan(s)...\n`);
  
  let totalFail = 0;
  let totalPass = 0;
  let totalWarn = 0;
  
  const results = [];
  for (const f of planFiles) {
    results.push(analyzePlan(f));
  }
  
  const failedResults = results.filter(r => r.totalUnchecked > 0 || (r.isCompleted && !r.hasClosureEvidence));
  const passedResults = results.filter(r => r.totalUnchecked === 0 && !(r.isCompleted && !r.hasClosureEvidence));
  const hardFailResults = failedResults.filter(r => r.isCompleted);
  
  for (const result of failedResults) {
    const report = formatReport(result, verbose);
    if (report) {
      console.log(report);
      totalFail++;
    }
  }
  
  if (verbose) {
    for (const result of passedResults) {
      const report = formatReport(result, verbose);
      if (report) console.log(report);
    }
  }
  
  console.log(`\n--- Summary ---`);
  console.log(`Plans checked: ${results.length}`);
  console.log(`Passed: ${results.length - failedResults.length}`);
  console.log(`Failed: ${failedResults.length} (completed plans with issues: ${hardFailResults.length})`);
  
  if (hardFailResults.length > 0) {
    console.log(`\nRemediation required before marking plan(s) as completed:`);
    console.log(`  1. Complete each unchecked item and mark it [x]`);
    console.log(`  2. Or move it to "Deferred But Adjudicated" with a justification`);
    console.log(`  3. Or revert Plan Status from "completed" to "in progress"`);
    console.log(`  4. Re-run: node ai-dev/tools/check-plan-checklist.mjs`);
    if (strictMode) process.exit(1);
  } else if (failedResults.length > 0) {
    console.log(`\n${failedResults.length} non-completed plan(s) have unchecked items (warnings only).`);
  } else {
    console.log(`\nAll plans passed checklist verification.`);
  }
}

main();