#!/usr/bin/env node

/**
 * audit-portfolio.mjs — PORTFOLIO.md Portfolio Management Audit
 *
 * Checks:
 *  1. portfolio.md exists (template or actual)
 *  2. Project states are legal (7-state portfolio states)
 *  3. Priority / Health / ROI / Agent Allocation fields are complete
 *  4. Duplicate work detection mechanism exists
 *  5. Portfolio Health Score formula defined
 *  6. Kill / Pause / Promotion rules defined
 *  7. Executive Dashboard schema defined
 *
 * Usage:
 *   node scripts/audit-portfolio.mjs          ← standalone
 *   import { runPortfolioAudit } from './audit-portfolio.mjs'  ← via orchestrator
 */

import { fileURLToPath } from 'url';
import path from 'path';
import fs from 'fs';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const WORKSPACE = path.resolve(__dirname, '..');
const SELF = process.argv[1].replace(/\\/g, '/');

/**
 * @typedef {Object} CheckResult
 * @property {string} id
 * @property {string} name
 * @property {'PASS'|'WARN'|'FAIL'} status
 * @property {string} evidence
 * @property {string} [suggestion]
 */

/** @returns {CheckResult[]} */
export function runPortfolioAudit(workspaceDir) {
  const root = workspaceDir || WORKSPACE;
  const checks = [];

  // ---- Helpers ----
  function readFile(rel) {
    const p = path.join(root, rel);
    if (!fs.existsSync(p)) return null;
    return fs.readFileSync(p, 'utf8');
  }

  function check(prefix, id, name, ok, passEv, failEv, suggestion) {
    checks.push({
      id: `${prefix}-${id}`,
      name,
      status: ok ? 'PASS' : 'FAIL',
      evidence: ok ? passEv : failEv,
      ...(ok ? {} : { suggestion: suggestion || `Implement ${id} in PORTFOLIO.md` }),
    });
  }

  function warn(prefix, id, name, evidence, suggestion) {
    checks.push({ id: `${prefix}-${id}`, name, status: 'WARN', evidence, suggestion });
  }

  // ---- Read protocol file ----
  const port = readFile('PORTFOLIO.md');
  if (!port) {
    checks.push({
      id: 'PF-001',
      name: 'PORTFOLIO.md exists',
      status: 'FAIL',
      evidence: 'PORTFOLIO.md not found in workspace root',
      suggestion: 'Create PORTFOLIO.md with the Portfolio Management Protocol',
    });
    return checks;
  }
  checks.push({
    id: 'PF-001',
    name: 'PORTFOLIO.md exists',
    status: 'PASS',
    evidence: `PORTFOLIO.md found (${port.length} bytes)`,
  });

  // ---- §1 Portfolio Registry ----
  const hasReg = /§1 Portfolio Registry/.test(port);
  check('PF', '1', '§1 Portfolio Registry defined', hasReg,
    'Found "§1 Portfolio Registry" with schema and field definitions',
    'Add §1 Portfolio Registry section');

  // Registry schema has all required fields
  const hasFields = /Project-ID.*Name.*Status.*Priority.*Health.*Agents.*Progress.*Last Updated.*ROI/.test(port);
  check('PF', '1a', '§1a Registry schema: all 9 fields (ID/Name/Status/Priority/Health/Agents/Progress/Updated/ROI)',
    hasFields,
    'Registry schema found with all 9 fields',
    'Ensure registry schema includes: Project-ID, Name, Status, Priority, Health, Agents, Progress, Last Updated, ROI');

  // portfolio.md template exists
  const portTmpl = readFile('scripts/templates/portfolio.md.template');
  check('PF', '1b', 'portfolio.md.template exists', portTmpl !== null,
    'Found scripts/templates/portfolio.md.template',
    'Create scripts/templates/portfolio.md.template');

  // Actual portfolio.md in workspace?
  const actualPortfolio = readFile('portfolio.md');
  warn('PF', '1c', 'Actual portfolio.md in workspace root',
    actualPortfolio
      ? `Found portfolio.md (${actualPortfolio.length} bytes)`
      : 'No portfolio.md found — expected when projects are active',
    actualPortfolio ? undefined : 'Create portfolio.md when first project becomes active');

  // ---- §2 Project States ----
  const hasStates = /§2 Project States/.test(port);
  check('PF', '2', '§2 Project States defined', hasStates,
    'Found "§2 Project States" with 7 portfolio states and transition rules',
    'Add §2 Project States with PLANNING/ACTIVE/BLOCKED/RECOVERY/PAUSED/COMPLETED/ARCHIVED');

  // 7 states
  const states = ['PLANNING', 'ACTIVE', 'BLOCKED', 'RECOVERY', 'PAUSED', 'COMPLETED', 'ARCHIVED'];
  const foundStates = states.filter(s => port.includes(s));
  check('PF', '2a', `§2a ${states.length} portfolio states defined (found ${foundStates.length})`,
    foundStates.length >= states.length,
    `All ${states.length} states present: ${foundStates.join(', ')}`,
    `Missing: ${states.filter(s => !port.includes(s)).join(', ')}`,
    'Add missing portfolio states');

  // State diagram
  const hasDiagram = /stateDiagram-v2/.test(port);
  check('PF', '2b', '§2b Portfolio state transition diagram (mermaid)', hasDiagram,
    'Found mermaid stateDiagram-v2 for portfolio states',
    'Add mermaid stateDiagram-v2 for portfolio state transitions');

  // Prohibited transitions
  const hasProhibited = /Prohibited Transitions[\s\S]*❌/.test(port);
  check('PF', '2c', '§2c Prohibited portfolio transitions defined', hasProhibited,
    'Found Prohibited Transitions with ❌ markers',
    'Add Prohibited Transitions section for portfolio states');

  // ---- §3 Priority System ----
  const hasPriority = /§3 Priority System/.test(port);
  check('PF', '3', '§3 Priority System defined (P0/P1/P2/P3)', hasPriority,
    'Found "§3 Priority System" with P0(P1(P2(P3 levels)',
    'Add §3 Priority System with P0(P1(P2(P3 levels');

  const hasP0 = port.includes('P0');
  const hasP1 = port.includes('P1');
  const hasP2 = port.includes('P2');
  const hasP3 = port.includes('P3');
  check('PF', '3a', `§3a All priority levels: P0/P1/P2/P3`, hasP0 && hasP1 && hasP2 && hasP3,
    `P0=${hasP0}, P1=${hasP1}, P2=${hasP2}, P3=${hasP3}`,
    'Define all 4 priority levels: P0(Mission Critical), P1(High Value), P2(Normal), P3(Low Priority)');

  // Collision resolution
  const hasCollision = /Priority Collision/.test(port);
  check('PF', '3b', '§3b Priority collision resolution defined', hasCollision,
    'Found Priority Collision Resolution: ROI sort → dependency sort → user decision',
    'Add Priority Collision Resolution for multiple P0 conflicts');

  // ---- §4 Resource Allocation ----
  const hasResources = /§4 Resource Allocation/.test(port);
  check('PF', '4', '§4 Resource Allocation defined', hasResources,
    'Found "§4 Resource Allocation" with Agent Inventory / Utilization Score / Rebalancing',
    'Add §4 Resource Allocation section');

  // Agent inventory = 5 metrics
  const hasTotal = port.includes('Total Agents');
  const hasBusy = port.includes('Busy Agents');
  const hasIdle = port.includes('Idle Agents');
  const hasBlocked = port.includes('Blocked Agents');
  const hasRecovery = port.includes('Recovery Agents');
  const all5inv = hasTotal && hasBusy && hasIdle && hasBlocked && hasRecovery;
  check('PF', '4a', `§4a Agent inventory: all 5 metrics (Total/Busy/Idle/Blocked/Recovery)`,
    all5inv,
    `Total=${hasTotal}, Busy=${hasBusy}, Idle=${hasIdle}, Blocked=${hasBlocked}, Recovery=${hasRecovery}`,
    'Define Agent Inventory with Total/Busy/Idle/Blocked/Recovery metrics');

  // Utilization formula
  const hasUtilFormula = /Utilization\s*=\s*Busy_Agents\s*\/\s*Total_Agents/.test(port);
  check('PF', '4b', '§4b Agent Utilization formula defined', hasUtilFormula,
    'Found Utilization = Busy_Agents / Total_Agents with 4-level rating',
    'Define Agent Utilization Score formula and rating');

  // ---- §5 Project Health Aggregation ----
  const hasAgg = /§5 Project Health Aggregation/.test(port);
  check('PF', '5', '§5 Project Health Aggregation defined', hasAgg,
    'Found "§5 Project Health Aggregation" with 5 dimensions and weights',
    'Add §5 Project Health Aggregation with Delivery/Quality/Learning/Execution/Risk dimensions');

  // ---- §6 Portfolio Health Score ----
  const hasPortHealth = /§6 Portfolio Health Score/.test(port);
  check('PF', '6', '§6 Portfolio Health Score defined', hasPortHealth,
    'Found "§6 Portfolio Health Score" with weighted average formula',
    'Add §6 Portfolio Health Score with weighted average calculation');

  // Weighted formula
  const hasWeighted = /加权[\s\S]*平均/.test(port) || /weighted/i.test(port) || /Σ\(Project_Health[\s\S]*Agent_Count\)/.test(port);
  check('PF', '6a', '§6a Weighted portfolio formula defined', hasWeighted,
    'Found weighted portfolio formula (Project_Health × Agent_Count / Σ Agent_Count)',
    'Define Portfolio_Health = Σ(Project_Health × Agent_Count) / Σ(Agent_Count)');

  // ---- §7 Learning Reuse ----
  const hasReuse = /§7 Learning Reuse/.test(port);
  check('PF', '7', '§7 Learning Reuse Detector defined', hasReuse,
    'Found "§7 Learning Reuse Detector" with detection criteria and action plan',
    'Add §7 Learning Reuse Detector with duplicate work detection');

  // Duplicate work criteria
  const hasDupCriteria = /Duplicate Work/.test(port);
  check('PF', '7a', '§7a Duplicate Work criteria defined', hasDupCriteria,
    'Found Duplicate Work criteria (30% overlap, same Pattern Class, same deps, same domain)',
    'Add Duplicate Work criteria with actionable thresholds');

  // ---- §8 Portfolio Audit ----
  const hasAudit = /§8 Portfolio Audit/.test(port);
  check('PF', '8', '§8 Portfolio Audit defined', hasAudit,
    'Found "§8 Portfolio Audit" with daily generation and audit schema',
    'Add §8 Portfolio Audit with daily generation and schema');

  // portfolio-audit.md template exists
  const auditTmpl = readFile('scripts/templates/portfolio-audit.md.template');
  check('PF', '8a', 'portfolio-audit.md.template exists', auditTmpl !== null,
    'Found scripts/templates/portfolio-audit.md.template',
    'Create scripts/templates/portfolio-audit.md.template');

  // ---- §9 Kill Rule ----
  const hasKill = /§9 Project Kill/.test(port);
  check('PF', '9', '§9 Project Kill Rule defined', hasKill,
    'Found "§9 Project Kill Rule" with K1-K4 conditions and graceful shutdown',
    'Add §9 Project Kill Rule with K1(Health<50), K2(>3 recovery fails), K3(ROI<threshold), K4(Scope+P3)');

  // ---- §10 Pause Rule ----
  const hasPause = /§10 Project Pause/.test(port);
  check('PF', '10', '§10 Project Pause Rule defined', hasPause,
    'Found "§10 Project Pause Rule" with P1-P3 conditions and resume decision',
    'Add §10 Project Pause Rule with P1(Priority), P2(Resources), P3(Dependency)');

  // ---- §11 Promotion Rule ----
  const hasPromotion = /§11 Project Promotion/.test(port);
  check('PF', '11', '§11 Project Promotion Rule defined', hasPromotion,
    'Found "§11 Project Promotion Rule" with F1(Health>90), F2(Progress>80%), F3(Risk<20)',
    'Add §11 Project Promotion Rule with Fast Track criteria');

  // ---- §12 Executive Dashboard ----
  const hasDash = /§12 Executive Dashboard/.test(port);
  check('PF', '12', '§12 Executive Dashboard defined', hasDash,
    'Found "§12 Executive Dashboard" with Portfolio Health / Top Projects / Risks / Opportunities / Utilization / Learning Reuse / Quick Actions',
    'Add §12 Executive Dashboard schema');

  // dashboard.md template exists
  const dashTmpl = readFile('scripts/templates/dashboard.md.template');
  check('PF', '12a', 'dashboard.md.template exists', dashTmpl !== null,
    'Found scripts/templates/dashboard.md.template',
    'Create scripts/templates/dashboard.md.template');

  // ---- §13 Quality Gate ----
  const hasQualityGate = /§13 Portfolio Quality Gate/.test(port);
  check('PF', '13', '§13 Portfolio Quality Gate defined', hasQualityGate,
    'Found "§13 Portfolio Quality Gate" with 8-item checklist',
    'Add §13 Portfolio Quality Gate with 8 pre-output checks');

  // ---- §14 Integration ----
  const hasIntegration = /§14 Integration Summary/.test(port);
  check('PF', '14', '§14 Integration Summary defined', hasIntegration,
    'Found "§14 Integration Summary" with data flow and lifecycle',
    'Add §14 Integration Summary with data flow and lifecycle diagram');

  // ---- Cross-refs ----
  const refsGovernance = (port.match(/GOVERNANCE\.md/g) || []).length;
  warn('PF', 'XR-gov', `Cross-references to GOVERNANCE.md: ${refsGovernance} (target: ≥5)`,
    `Found ${refsGovernance} references to GOVERNANCE.md`,
    'PORTFOLIO.md should reference GOVERNANCE.md for project-level health/blockers');

  const refsAgents = (port.match(/AGENTS\.md/g) || []).length;
  warn('PF', 'XR-ag', `Cross-references to AGENTS.md: ${refsAgents} (target: ≥2)`,
    `Found ${refsAgents} references to AGENTS.md`,
    'PORTFOLIO.md should reference AGENTS.md for execution protocol');

  // ---- Duplicate work scan ----
  const projectDirs = findProjectDirs(root);
  warn('PF', 'DW-scan', `Potential duplicate work scan: ${projectDirs.length} project directories found`,
    projectDirs.length > 0
      ? `Found ${projectDirs.length} project directories. Manual review recommended for duplicate patterns: ${projectDirs.join(', ')}`
      : 'No project directories found for duplicate work analysis',
    projectDirs.length >= 2 ? 'Check for duplicate code across project directories' : undefined);

  // ---- Portfolio Health Score (from available data) ----
  checks.push({
    id: 'PF-HS',
    name: 'Portfolio Health Score calculated',
    status: 'WARN',
    evidence: 'No active project data available for portfolio health calculation. Formula is defined in PORTFOLIO.md §6.',
    suggestion: 'Portfolio Health will be calculated after at least one project has a Health Score',
  });

  // ---- Summary ----
  const passCount = checks.filter(c => c.status === 'PASS').length;
  const warnCount = checks.filter(c => c.status === 'WARN').length;
  const failCount = checks.filter(c => c.status === 'FAIL').length;
  checks.push({
    id: 'PF-SUMMARY',
    name: `Summary — ${passCount} PASS / ${warnCount} WARN / ${failCount} FAIL`,
    status: failCount > 0 ? 'FAIL' : warnCount > 0 ? 'WARN' : 'PASS',
    evidence: `${passCount} passed, ${warnCount} warnings, ${failCount} failures`,
  });

  return checks;
}

/** Find all directories that look like projects (have progress.log or AGENTS.md ref) */
function findProjectDirs(root) {
  const candidates = [];
  try {
    const entries = fs.readdirSync(root, { withFileTypes: true });
    for (const e of entries) {
      if (e.isDirectory() && !e.name.startsWith('.') && !['node_modules', 'scripts', 'memory', 'repos'].includes(e.name)) {
        // Check if this dir has project-like files
        const sub = path.join(root, e.name);
        const hasProgress = fs.existsSync(path.join(sub, 'progress.log'));
        const hasReadme = fs.existsSync(path.join(sub, 'README.md'));
        const hasPkg = fs.existsSync(path.join(sub, 'package.json'));
        if (hasProgress || hasReadme || hasPkg) {
          candidates.push(e.name);
        }
      }
    }
  } catch { /* skip */ }
  return candidates;
}

// ---- Standalone runner ----
if (SELF.endsWith('/audit-portfolio.mjs') || SELF.endsWith('\\audit-portfolio.mjs')) {
  const results = runPortfolioAudit(WORKSPACE);
  console.log(`\n=== PORTFOLIO.md Audit ===\n`);
  for (const r of results) {
    const icon = r.status === 'PASS' ? '✅' : r.status === 'WARN' ? '⚠️' : '❌';
    console.log(`  ${icon} [${r.id}] ${r.name}`);
    console.log(`     ${r.evidence}`);
    if (r.suggestion) console.log(`     → ${r.suggestion}`);
    console.log();
  }
}
