/**
 * Skill Validator - Self-validating skills infrastructure
 *
 * Each skill can define validation strategies in VALIDATION.md or validation.yaml
 * Validation runs automatically after skill work completes.
 *
 * @since v1.0.130
 */
import * as fs from '../../utils/fs-native.js';
import * as path from 'path';
import { exec, spawnSync } from 'child_process';
import { promisify } from 'util';
import yaml from 'js-yaml';
import chalk from 'chalk';
const execAsync = promisify(exec);
/**
 * Default validation configs by domain
 */
const DEFAULT_CONFIGS = {
    frontend: {
        domain: 'frontend',
        validation_type: 'automated',
        requires_auth: false,
        timeout_seconds: 120,
        validations: {
            quick: [
                { command: 'npx tsc --noEmit', name: 'TypeScript check', timeout: 30 },
                { command: 'npm run lint -- --max-warnings 0', name: 'Lint check', timeout: 30, optional: true },
            ],
            standard: [
                { command: 'npm test -- --coverage --watchAll=false', name: 'Unit tests', timeout: 120 },
            ],
            full: [
                { command: 'npx playwright test', name: 'E2E tests', timeout: 300 },
            ],
        },
        preview: {
            command: 'npm run dev',
            port: 3000,
            wait_for: 'Ready',
        },
    },
    backend: {
        domain: 'backend',
        validation_type: 'automated',
        requires_auth: true,
        timeout_seconds: 180,
        validations: {
            quick: [
                { command: 'npx tsc --noEmit', name: 'TypeScript check', timeout: 30 },
            ],
            standard: [
                { command: 'npm test', name: 'Unit tests', timeout: 120 },
                { command: 'npm run test:integration', name: 'Integration tests', timeout: 180, optional: true },
            ],
            full: [
                { command: 'npm run postman:test', name: 'API tests', timeout: 300, optional: true },
            ],
        },
    },
    mobile: {
        domain: 'mobile',
        validation_type: 'automated',
        requires_auth: false,
        timeout_seconds: 300,
        validations: {
            quick: [
                { command: 'npx tsc --noEmit', name: 'TypeScript check', timeout: 30 },
                { command: 'npx expo doctor', name: 'Expo health check', timeout: 30, optional: true },
            ],
            standard: [
                { command: 'npm test -- --watchAll=false', name: 'Jest tests', timeout: 120 },
            ],
            full: [
                { command: 'npx maestro test .maestro/', name: 'Maestro E2E', timeout: 600, optional: true },
            ],
        },
        preview: {
            command: 'npx expo start --web',
            port: 8081,
            wait_for: 'Starting Metro',
            browser_url: 'http://localhost:8081',
        },
    },
    infrastructure: {
        domain: 'infrastructure',
        validation_type: 'automated',
        requires_auth: false,
        timeout_seconds: 120,
        validations: {
            quick: [
                { command: 'terraform fmt -check', name: 'Terraform format', timeout: 10, optional: true },
                { command: 'terraform validate', name: 'Terraform validate', timeout: 30 },
            ],
            standard: [
                { command: 'terraform plan -out=tfplan', name: 'Terraform plan', timeout: 120 },
            ],
            full: [
                { command: 'tflint', name: 'TFLint check', timeout: 60, optional: true },
                { command: 'checkov -d .', name: 'Security scan', timeout: 120, optional: true },
            ],
        },
    },
    ml: {
        domain: 'ml',
        validation_type: 'automated',
        requires_auth: false,
        timeout_seconds: 300,
        validations: {
            quick: [
                { command: 'ruff check .', name: 'Lint check', timeout: 30, optional: true },
            ],
            standard: [
                { command: 'pytest tests/ -v', name: 'Pytest', timeout: 180 },
            ],
            full: [
                { command: 'python scripts/validate_model.py', name: 'Model validation', timeout: 600, optional: true },
            ],
        },
    },
    testing: {
        domain: 'testing',
        validation_type: 'automated',
        requires_auth: false,
        timeout_seconds: 120,
        validations: {
            quick: [
                { command: 'npx tsc --noEmit', name: 'TypeScript check', timeout: 30 },
            ],
            standard: [
                { command: 'npm test -- --watchAll=false', name: 'Run tests', timeout: 120 },
            ],
            full: [
                { command: 'npm test -- --coverage --watchAll=false', name: 'Tests with coverage', timeout: 180 },
            ],
        },
    },
};
/**
 * Skill Validator
 *
 * Runs validation strategies defined in skill VALIDATION.md files
 */
export class SkillValidator {
    constructor(projectRoot, options) {
        this.projectRoot = projectRoot;
        this.verbose = options?.verbose ?? false;
    }
    /**
     * Load validation config from skill or use defaults
     */
    async loadConfig(skillFqn) {
        // Try to find skill's VALIDATION.md or validation.yaml
        const possiblePaths = [
            path.join(this.projectRoot, '.specweave/skills', skillFqn, 'VALIDATION.md'),
            path.join(this.projectRoot, '.specweave/skills', skillFqn, 'validation.yaml'),
            // Check in installed plugins
            path.join(process.env.HOME || '', '.claude/plugins/marketplaces/specweave/plugins', skillFqn.split(':')[0], 'skills', skillFqn.split(':')[1], 'VALIDATION.md'),
        ];
        for (const configPath of possiblePaths) {
            if (fs.existsSync(configPath)) {
                return this.parseConfigFile(configPath);
            }
        }
        // Use default based on domain detection
        const domain = this.detectDomain(skillFqn);
        if (domain && DEFAULT_CONFIGS[domain]) {
            return DEFAULT_CONFIGS[domain];
        }
        return null;
    }
    /**
     * Parse VALIDATION.md or validation.yaml file
     */
    parseConfigFile(filePath) {
        try {
            const content = fs.readFileSync(filePath, 'utf-8');
            if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
                return yaml.load(content);
            }
            // Parse VALIDATION.md with YAML frontmatter
            const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
            if (frontmatterMatch) {
                const frontmatter = yaml.load(frontmatterMatch[1]);
                // Merge with defaults if domain specified
                if (frontmatter.domain && DEFAULT_CONFIGS[frontmatter.domain]) {
                    return { ...DEFAULT_CONFIGS[frontmatter.domain], ...frontmatter };
                }
                return frontmatter;
            }
            return null;
        }
        catch {
            return null;
        }
    }
    /**
     * Detect domain from skill FQN
     */
    detectDomain(skillFqn) {
        const fqnLower = skillFqn.toLowerCase();
        if (fqnLower.includes('frontend') || fqnLower.includes('react') || fqnLower.includes('vue')) {
            return 'frontend';
        }
        if (fqnLower.includes('backend') || fqnLower.includes('api') || fqnLower.includes('server')) {
            return 'backend';
        }
        if (fqnLower.includes('mobile') || fqnLower.includes('react-native') || fqnLower.includes('expo')) {
            return 'mobile';
        }
        if (fqnLower.includes('infra') || fqnLower.includes('terraform') || fqnLower.includes('k8s')) {
            return 'infrastructure';
        }
        if (fqnLower.includes('ml') || fqnLower.includes('ai') || fqnLower.includes('model')) {
            return 'ml';
        }
        if (fqnLower.includes('test') || fqnLower.includes('qa')) {
            return 'testing';
        }
        return null;
    }
    /**
     * Run validation at specified level
     */
    async validate(skillFqn, level = 'quick') {
        const startTime = Date.now();
        const config = await this.loadConfig(skillFqn);
        if (!config) {
            return {
                passed: true,
                level,
                steps: [],
                duration_ms: Date.now() - startTime,
                warnings: ['No validation config found, skipping'],
                summary: 'Skipped - no validation config',
            };
        }
        // Determine which steps to run
        const steps = [];
        if (level === 'quick') {
            steps.push(...(config.validations.quick || []));
        }
        else if (level === 'standard') {
            steps.push(...(config.validations.quick || []));
            steps.push(...(config.validations.standard || []));
        }
        else {
            steps.push(...(config.validations.quick || []));
            steps.push(...(config.validations.standard || []));
            steps.push(...(config.validations.full || []));
        }
        // Run steps
        const results = [];
        let allPassed = true;
        const warnings = [];
        for (const step of steps) {
            const result = await this.runStep(step);
            results.push(result);
            if (!result.passed && !step.optional) {
                allPassed = false;
            }
            if (!result.passed && step.optional) {
                warnings.push(`Optional step "${step.name}" failed: ${result.error}`);
            }
        }
        const duration_ms = Date.now() - startTime;
        return {
            passed: allPassed,
            level,
            steps: results,
            duration_ms,
            warnings,
            summary: this.generateSummary(results, allPassed, level),
        };
    }
    /**
     * Run a single validation step
     */
    async runStep(step) {
        const startTime = Date.now();
        // Check if command exists
        const commandParts = step.command.split(' ');
        const baseCommand = commandParts[0];
        // Skip if command not found
        if (!this.commandExists(baseCommand)) {
            return {
                name: step.name,
                command: step.command,
                passed: true,
                duration_ms: Date.now() - startTime,
                skipped: true,
                skip_reason: `Command "${baseCommand}" not found`,
            };
        }
        try {
            const { stdout, stderr } = await execAsync(step.command, {
                cwd: step.workdir || this.projectRoot,
                timeout: step.timeout * 1000,
                maxBuffer: 10 * 1024 * 1024, // 10MB
            });
            return {
                name: step.name,
                command: step.command,
                passed: true,
                duration_ms: Date.now() - startTime,
                output: (stdout + stderr).slice(0, 5000), // Truncate
            };
        }
        catch (error) {
            const err = error;
            return {
                name: step.name,
                command: step.command,
                passed: false,
                duration_ms: Date.now() - startTime,
                output: (err.stdout || '').slice(0, 2000),
                error: (err.stderr || err.message || 'Unknown error').slice(0, 2000),
            };
        }
    }
    /**
     * Check if a command exists on the system
     */
    commandExists(command) {
        try {
            // Handle npx commands
            if (command === 'npx')
                return true;
            if (command === 'npm')
                return true;
            if (command === 'node')
                return true;
            const { status } = spawnSync('which', [command], {
                stdio: 'pipe',
            });
            return status === 0;
        }
        catch {
            return false;
        }
    }
    /**
     * Generate human-readable summary
     */
    generateSummary(results, passed, level) {
        const passedCount = results.filter(r => r.passed).length;
        const skippedCount = results.filter(r => r.skipped).length;
        const failedCount = results.filter(r => !r.passed && !r.skipped).length;
        const emoji = passed ? '✅' : '❌';
        const status = passed ? 'PASSED' : 'FAILED';
        return `${emoji} ${level.toUpperCase()} validation ${status}: ${passedCount}/${results.length} steps passed` +
            (skippedCount > 0 ? `, ${skippedCount} skipped` : '') +
            (failedCount > 0 ? `, ${failedCount} failed` : '');
    }
    /**
     * Run quick validation (fastest, minimal checks)
     */
    async runQuickValidation(domain) {
        const skillFqn = domain ? `specweave-${domain}:validator` : 'specweave:generic';
        return this.validate(skillFqn, 'quick');
    }
    /**
     * Run standard validation (unit tests, integration tests)
     */
    async runStandardValidation(domain) {
        const skillFqn = domain ? `specweave-${domain}:validator` : 'specweave:generic';
        return this.validate(skillFqn, 'standard');
    }
    /**
     * Run full validation (E2E, visual regression, API tests)
     */
    async runFullValidation(domain) {
        const skillFqn = domain ? `specweave-${domain}:validator` : 'specweave:generic';
        return this.validate(skillFqn, 'full');
    }
    /**
     * Detect project domain from package.json and file structure
     */
    async detectProjectDomain() {
        const packageJsonPath = path.join(this.projectRoot, 'package.json');
        if (fs.existsSync(packageJsonPath)) {
            try {
                const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
                const deps = { ...pkg.dependencies, ...pkg.devDependencies };
                // Check for frameworks
                if (deps['next'] || deps['react'])
                    return 'frontend';
                if (deps['express'] || deps['fastify'] || deps['@nestjs/core'])
                    return 'backend';
                if (deps['expo'] || deps['react-native'])
                    return 'mobile';
                if (deps['@playwright/test'] || deps['vitest'] || deps['jest'])
                    return 'testing';
            }
            catch {
                // Ignore parse errors
            }
        }
        // Check for infrastructure files
        if (fs.existsSync(path.join(this.projectRoot, 'main.tf')))
            return 'infrastructure';
        if (fs.existsSync(path.join(this.projectRoot, 'requirements.txt')))
            return 'ml';
        return null;
    }
    /**
     * Format validation result for display
     */
    formatResult(result) {
        const lines = [];
        lines.push('');
        lines.push(chalk.bold(`Skill Validation (${result.level})`));
        lines.push('─'.repeat(40));
        for (const step of result.steps) {
            const icon = step.skipped ? '⏭️' : step.passed ? '✅' : '❌';
            const status = step.skipped ? chalk.gray('SKIPPED') : step.passed ? chalk.green('PASS') : chalk.red('FAIL');
            const duration = chalk.gray(`(${step.duration_ms}ms)`);
            lines.push(`${icon} ${step.name}: ${status} ${duration}`);
            if (step.skipped && step.skip_reason) {
                lines.push(chalk.gray(`   └─ ${step.skip_reason}`));
            }
            if (!step.passed && step.error && !step.skipped) {
                const errorLines = step.error.split('\n').slice(0, 3);
                for (const errLine of errorLines) {
                    lines.push(chalk.red(`   └─ ${errLine}`));
                }
            }
        }
        lines.push('─'.repeat(40));
        lines.push(result.summary);
        if (result.warnings.length > 0) {
            lines.push('');
            lines.push(chalk.yellow('Warnings:'));
            for (const warning of result.warnings) {
                lines.push(chalk.yellow(`  ⚠️ ${warning}`));
            }
        }
        return lines.join('\n');
    }
}
/**
 * Run validation from CLI or hook
 */
export async function validateSkill(skillFqn, level = 'quick', options) {
    const projectRoot = options?.projectRoot || process.cwd();
    const validator = new SkillValidator(projectRoot, { verbose: options?.verbose });
    return validator.validate(skillFqn, level);
}
/**
 * Auto-detect domain and run validation
 */
export async function validateProject(level = 'quick', options) {
    const projectRoot = options?.projectRoot || process.cwd();
    const validator = new SkillValidator(projectRoot, { verbose: options?.verbose });
    const domain = await validator.detectProjectDomain();
    if (!domain) {
        return {
            passed: true,
            level,
            steps: [],
            duration_ms: 0,
            warnings: ['Could not detect project domain'],
            summary: 'Skipped - unknown project type',
        };
    }
    return validator.validate(`specweave-${domain}:validator`, level);
}
//# sourceMappingURL=skill-validator.js.map