#!/usr/bin/env node

/**
 * MORPH-SPEC Agent-Skill Validator
 *
 * Valida a consistência entre agents.json e arquivos de skills.
 * Garante que não há agentes sem skills ou skills sem agentes (orphans).
 *
 * Uso:
 *   node bin/validate-agents-skills.js
 *   node bin/validate-agents-skills.js --verbose
 */

import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const AGENTS_CONFIG_PATH = join(__dirname, '../content/.morph/config/agents.json');
const SKILLS_BASE_PATH = join(__dirname, '../content/.claude/skills');

// Cores para output
const colors = {
  reset: '\x1b[0m',
  bright: '\x1b[1m',
  green: '\x1b[32m',
  yellow: '\x1b[33m',
  red: '\x1b[31m',
  cyan: '\x1b[36m',
  gray: '\x1b[90m'
};

function log(message, color = 'reset') {
  console.log(`${colors[color]}${message}${colors.reset}`);
}

/**
 * Carrega agents.json
 */
function loadAgentsConfig() {
  try {
    const content = readFileSync(AGENTS_CONFIG_PATH, 'utf8');
    return JSON.parse(content);
  } catch (err) {
    log(`ERROR: Failed to load agents.json: ${err.message}`, 'red');
    process.exit(1);
  }
}

/**
 * Recursivamente lista todos os arquivos .md no diretório de skills
 */
function getAllSkillFiles(dir, fileList = []) {
  const files = readdirSync(dir);

  files.forEach(file => {
    const filePath = join(dir, file);
    const stat = statSync(filePath);

    if (stat.isDirectory()) {
      getAllSkillFiles(filePath, fileList);
    } else if (file.endsWith('.md')) {
      // Converter para path relativo ao content/
      const relativePath = filePath.replace(/\\/g, '/').split('/content/')[1];
      fileList.push(relativePath);
    }
  });

  return fileList;
}

/**
 * Valida agents vs skills
 */
function validateAgentsSkills(config, verbose = false) {
  const errors = [];
  const warnings = [];
  const info = [];

  // 1. Coletar todos os agentes e seus skillPaths
  const allAgents = [...config.agents.core, ...config.agents.specialists];
  const agentSkillPaths = new Set();

  for (const agent of allAgents) {
    if (agent.skillPath) {
      agentSkillPaths.add(agent.skillPath);

      // Verificar se skill file existe
      const skillFilePath = join(__dirname, '../content', agent.skillPath);
      if (!existsSync(skillFilePath)) {
        errors.push({
          type: 'missing-skill',
          agent: agent.id,
          skillPath: agent.skillPath,
          message: `Agent '${agent.id}' references non-existent skill: ${agent.skillPath}`
        });
      } else if (verbose) {
        info.push({
          type: 'valid-mapping',
          agent: agent.id,
          skillPath: agent.skillPath,
          message: `✓ ${agent.id} → ${agent.skillPath}`
        });
      }
    } else if (verbose) {
      warnings.push({
        type: 'no-skill',
        agent: agent.id,
        message: `Agent '${agent.id}' has no skillPath defined (metadata-only agent)`
      });
    }
  }

  // 2. Verificar skills órfãs (sem agente correspondente)
  const allSkillFiles = getAllSkillFiles(SKILLS_BASE_PATH);

  for (const skillFile of allSkillFiles) {
    const skillPath = `.claude/skills/${skillFile.replace('.claude/skills/', '')}`;

    if (!agentSkillPaths.has(skillPath)) {
      // Orphan skills são OK - podem ser usados manualmente
      // Apenas reportar em verbose mode
      if (verbose) {
        info.push({
          type: 'manual-skill',
          skillPath: skillPath,
          message: `ℹ️  Manual skill: ${skillPath} (no auto-activation)`
        });
      }
    }
  }

  return { errors, warnings, info };
}

/**
 * Formata e exibe resultados
 */
function displayResults(results, verbose) {
  const { errors, warnings, info } = results;

  log('\n╔════════════════════════════════════════════════════════════════╗', 'cyan');
  log('║           MORPH-SPEC AGENT-SKILL VALIDATION REPORT            ║', 'cyan');
  log('╠════════════════════════════════════════════════════════════════╣', 'cyan');

  // Erros
  if (errors.length > 0) {
    log('║ ERRORS                                                         ║', 'red');
    log('╠════════════════════════════════════════════════════════════════╣', 'cyan');

    errors.forEach(err => {
      log(`║ ❌ ${err.message.substring(0, 61).padEnd(61)}║`, 'red');
    });

    log('╠════════════════════════════════════════════════════════════════╣', 'cyan');
  }

  // Warnings
  if (warnings.length > 0) {
    log('║ WARNINGS                                                       ║', 'yellow');
    log('╠════════════════════════════════════════════════════════════════╣', 'cyan');

    warnings.forEach(warn => {
      // Quebrar mensagens longas
      const lines = warn.message.match(/.{1,60}/g) || [warn.message];
      lines.forEach((line, idx) => {
        if (idx === 0) {
          log(`║ ⚠️  ${line.padEnd(61)}║`, 'yellow');
        } else {
          log(`║    ${line.padEnd(61)}║`, 'yellow');
        }
      });
    });

    log('╠════════════════════════════════════════════════════════════════╣', 'cyan');
  }

  // Info (verbose mode)
  if (verbose && info.length > 0) {
    log('║ VALID MAPPINGS                                                 ║', 'green');
    log('╠════════════════════════════════════════════════════════════════╣', 'cyan');

    info.forEach(item => {
      log(`║ ${item.message.substring(0, 62).padEnd(62)}║`, 'gray');
    });

    log('╠════════════════════════════════════════════════════════════════╣', 'cyan');
  }

  // Summary
  log('║ SUMMARY                                                        ║', 'cyan');
  log('╠════════════════════════════════════════════════════════════════╣', 'cyan');
  log(`║ Errors: ${errors.length.toString().padEnd(56)}║`, errors.length > 0 ? 'red' : 'green');
  log(`║ Warnings: ${warnings.length.toString().padEnd(54)}║`, warnings.length > 0 ? 'yellow' : 'green');

  if (errors.length === 0 && warnings.length === 0) {
    log('║                                                                ║', 'cyan');
    log('║ ✅ All agents and skills are properly mapped!                  ║', 'green');
  }

  // Nota sobre manual skills
  const manualSkills = info.filter(i => i.type === 'manual-skill').length;
  if (manualSkills > 0 && verbose) {
    log('║                                                                ║', 'cyan');
    log(`║ ℹ️  ${manualSkills} manual-only skills found (use --verbose to see)    ║`, 'gray');
    log('║   These are extra skills without auto-activation.             ║', 'gray');
  }

  log('╚════════════════════════════════════════════════════════════════╝\n', 'cyan');
}

// ============================================================================
// MAIN
// ============================================================================

function main() {
  const args = process.argv.slice(2);
  const verbose = args.includes('--verbose') || args.includes('-v');
  const hasHelp = args.includes('--help') || args.includes('-h');

  if (hasHelp) {
    log('\nMORPH-SPEC Agent-Skill Validator', 'bright');
    log('\nUsage:', 'cyan');
    log('  node bin/validate-agents-skills.js [options]', 'gray');
    log('\nOptions:', 'cyan');
    log('  -v, --verbose     Show all valid mappings', 'gray');
    log('  -h, --help        Show this help', 'gray');
    log('\nDescription:', 'cyan');
    log('  Validates consistency between agents.json and skill files.', 'gray');
    log('  - Checks if all skillPaths exist', 'gray');
    log('  - Detects orphan skills (no corresponding agent)', 'gray');
    log('  - Exit code 0 if valid, 1 if errors found\n', 'gray');
    process.exit(0);
  }

  // Load config and validate
  const config = loadAgentsConfig();
  const results = validateAgentsSkills(config, verbose);

  // Display results
  displayResults(results, verbose);

  // Exit with appropriate code
  if (results.errors.length > 0) {
    process.exit(1);
  }

  process.exit(0);
}

main();
