/**
 * Three File Structure Validator
 *
 * Validates that spec.md, plan.md, and tasks.md follow
 * the canonical structure defined in ADR-0047.
 *
 * Part of increment 0039: Ultra-Smart Next Command
 * Addresses architectural violation: ACs in tasks.md
 */
import * as fs from 'fs';
import * as path from 'path';
/**
 * Validation severity levels
 */
export var ValidationSeverity;
(function (ValidationSeverity) {
    ValidationSeverity["ERROR"] = "ERROR";
    ValidationSeverity["WARNING"] = "WARNING";
    ValidationSeverity["INFO"] = "INFO"; // Suggestion for improvement
})(ValidationSeverity || (ValidationSeverity = {}));
/**
 * Validation error codes
 */
export var ValidationErrorCode;
(function (ValidationErrorCode) {
    // tasks.md violations
    ValidationErrorCode["TASKS_CONTAINS_AC"] = "TASKS_CONTAINS_AC";
    ValidationErrorCode["TASKS_MISSING_IMPLEMENTATION"] = "TASKS_MISSING_IMPLEMENTATION";
    ValidationErrorCode["TASKS_MISSING_TEST_PLAN"] = "TASKS_MISSING_TEST_PLAN";
    ValidationErrorCode["TASKS_MISSING_AC_IDS"] = "TASKS_MISSING_AC_IDS";
    ValidationErrorCode["TASKS_CONTAINS_USER_STORY"] = "TASKS_CONTAINS_USER_STORY";
    // spec.md violations
    ValidationErrorCode["SPEC_CONTAINS_TASK_IDS"] = "SPEC_CONTAINS_TASK_IDS";
    ValidationErrorCode["SPEC_CONTAINS_TECHNICAL_DETAILS"] = "SPEC_CONTAINS_TECHNICAL_DETAILS";
    ValidationErrorCode["SPEC_MISSING_AC"] = "SPEC_MISSING_AC";
    ValidationErrorCode["SPEC_MISSING_PROJECT"] = "SPEC_MISSING_PROJECT";
    // plan.md violations
    ValidationErrorCode["PLAN_CONTAINS_AC"] = "PLAN_CONTAINS_AC";
    ValidationErrorCode["PLAN_CONTAINS_TASK_CHECKBOXES"] = "PLAN_CONTAINS_TASK_CHECKBOXES";
    // metadata.json violations
    ValidationErrorCode["METADATA_MISSING_PROJECT"] = "METADATA_MISSING_PROJECT";
    ValidationErrorCode["METADATA_UNKNOWN_PROJECT"] = "METADATA_UNKNOWN_PROJECT";
    // General violations
    ValidationErrorCode["FILE_NOT_FOUND"] = "FILE_NOT_FOUND";
})(ValidationErrorCode || (ValidationErrorCode = {}));
/**
 * Three File Structure Validator
 */
export class ThreeFileValidator {
    /**
     * Validate an increment's three core files
     *
     * @param incrementDir - Path to increment directory
     * @param projectRoot - Optional project root for config-based validation
     */
    validateIncrement(incrementDir, projectRoot) {
        const issues = [];
        // Validate each file
        issues.push(...this.validateSpecFile(incrementDir));
        issues.push(...this.validatePlanFile(incrementDir));
        issues.push(...this.validateTasksFile(incrementDir));
        issues.push(...this.validateMetadataProject(incrementDir, projectRoot));
        // Calculate summary
        const errors = issues.filter(i => i.severity === ValidationSeverity.ERROR).length;
        const warnings = issues.filter(i => i.severity === ValidationSeverity.WARNING).length;
        const infos = issues.filter(i => i.severity === ValidationSeverity.INFO).length;
        return {
            valid: errors === 0,
            issues,
            summary: { errors, warnings, infos }
        };
    }
    /**
     * Validate spec.md structure
     */
    validateSpecFile(incrementDir) {
        const issues = [];
        const specPath = path.join(incrementDir, 'spec.md');
        if (!fs.existsSync(specPath)) {
            issues.push({
                code: ValidationErrorCode.FILE_NOT_FOUND,
                severity: ValidationSeverity.ERROR,
                file: 'spec.md',
                message: 'spec.md not found in increment directory',
                fix: 'Create spec.md with business requirements and acceptance criteria'
            });
            return issues;
        }
        const content = fs.readFileSync(specPath, 'utf-8');
        const lines = content.split('\n');
        // Rule 1: spec.md should NOT contain task IDs (T-001, T-002, T-1000, etc.)
        lines.forEach((line, index) => {
            if (/T-\d{3,}/.test(line)) {
                issues.push({
                    code: ValidationErrorCode.SPEC_CONTAINS_TASK_IDS,
                    severity: ValidationSeverity.ERROR,
                    file: 'spec.md',
                    line: index + 1,
                    message: `spec.md contains task ID (${line.match(/T-\d{3,}/)?.[0]}). Task IDs belong in tasks.md only.`,
                    fix: 'Remove task references from spec.md. Link tasks to ACs instead.'
                });
            }
        });
        // Rule 2: spec.md should NOT contain technical class names
        const technicalPatterns = [
            /class\s+\w+/,
            /interface\s+\w+/,
            /function\s+\w+\(/,
            /\.ts\b/,
            /\.tsx\b/,
            /\.js\b/
        ];
        lines.forEach((line, index) => {
            for (const pattern of technicalPatterns) {
                if (pattern.test(line) && !line.includes('```')) { // Ignore code blocks
                    issues.push({
                        code: ValidationErrorCode.SPEC_CONTAINS_TECHNICAL_DETAILS,
                        severity: ValidationSeverity.WARNING,
                        file: 'spec.md',
                        line: index + 1,
                        message: 'spec.md contains technical implementation details. Keep business-focused.',
                        fix: 'Move technical details to plan.md'
                    });
                    break;
                }
            }
        });
        // Rule 3: Every ### US-NNN block must have a **Project**: field
        const usHeaders = lines
            .map((line, index) => ({ line, index }))
            .filter(({ line }) => /^###\s+US-(?:[A-Z]+-)*\d+:/.test(line));
        for (const { line: usLine, index: usIndex } of usHeaders) {
            // Find the next US header or end of file
            const nextUsIndex = usHeaders.find(h => h.index > usIndex)?.index ?? lines.length;
            const usBlock = lines.slice(usIndex, nextUsIndex).join('\n');
            if (!usBlock.includes('**Project**:')) {
                const usId = usLine.match(/US-(?:[A-Z]+-)*\d+/)?.[0] ?? 'unknown';
                issues.push({
                    code: ValidationErrorCode.SPEC_MISSING_PROJECT,
                    severity: ValidationSeverity.ERROR,
                    file: 'spec.md',
                    line: usIndex + 1,
                    message: `Missing **Project**: field in ${usId}. Every user story must specify a project.`,
                    fix: `Add **Project**: <project-name> after the ### ${usId} heading`
                });
            }
        }
        // Rule 4: spec.md should contain Acceptance Criteria section
        if (!content.includes('## Acceptance Criteria') && !content.includes('### Acceptance Criteria')) {
            issues.push({
                code: ValidationErrorCode.SPEC_MISSING_AC,
                severity: ValidationSeverity.WARNING,
                file: 'spec.md',
                message: 'spec.md missing "Acceptance Criteria" section',
                fix: 'Add "## Acceptance Criteria" section with AC-XXX-YY criteria'
            });
        }
        return issues;
    }
    /**
     * Validate plan.md structure
     */
    validatePlanFile(incrementDir) {
        const issues = [];
        const planPath = path.join(incrementDir, 'plan.md');
        if (!fs.existsSync(planPath)) {
            // plan.md is optional for simple increments
            return issues;
        }
        const content = fs.readFileSync(planPath, 'utf-8');
        const lines = content.split('\n');
        // Rule 1: plan.md should NOT contain "Acceptance Criteria" sections
        if (content.includes('## Acceptance Criteria') || content.includes('### Acceptance Criteria') || content.includes('**Acceptance Criteria**')) {
            const lineIndex = lines.findIndex(l => l.includes('## Acceptance Criteria') ||
                l.includes('### Acceptance Criteria') ||
                l.includes('**Acceptance Criteria**'));
            issues.push({
                code: ValidationErrorCode.PLAN_CONTAINS_AC,
                severity: ValidationSeverity.ERROR,
                file: 'plan.md',
                line: lineIndex + 1,
                message: 'plan.md contains "Acceptance Criteria" section. ACs belong in spec.md only.',
                fix: 'Remove ACs from plan.md. Define them in spec.md instead.'
            });
        }
        // Rule 2: plan.md should NOT contain task checkboxes
        const taskCheckboxPattern = /^- \[ \]/;
        lines.forEach((line, index) => {
            if (taskCheckboxPattern.test(line.trim())) {
                issues.push({
                    code: ValidationErrorCode.PLAN_CONTAINS_TASK_CHECKBOXES,
                    severity: ValidationSeverity.WARNING,
                    file: 'plan.md',
                    line: index + 1,
                    message: 'plan.md contains task checkboxes. Checkable tasks belong in tasks.md.',
                    fix: 'Move task checkboxes to tasks.md. Keep plan.md at architecture level.'
                });
            }
        });
        return issues;
    }
    /**
     * Validate tasks.md structure (MOST IMPORTANT)
     */
    validateTasksFile(incrementDir) {
        const issues = [];
        const tasksPath = path.join(incrementDir, 'tasks.md');
        if (!fs.existsSync(tasksPath)) {
            // tasks.md is required for all increments
            issues.push({
                code: ValidationErrorCode.FILE_NOT_FOUND,
                severity: ValidationSeverity.ERROR,
                file: 'tasks.md',
                message: 'tasks.md not found in increment directory',
                fix: 'Create tasks.md with task breakdown and embedded tests'
            });
            return issues;
        }
        const content = fs.readFileSync(tasksPath, 'utf-8');
        const lines = content.split('\n');
        // Rule 1: tasks.md should NOT contain "Acceptance Criteria" sections
        // This is the CRITICAL violation we're fixing!
        if (content.includes('**Acceptance Criteria**:')) {
            const lineIndex = lines.findIndex(l => l.includes('**Acceptance Criteria**:'));
            issues.push({
                code: ValidationErrorCode.TASKS_CONTAINS_AC,
                severity: ValidationSeverity.ERROR,
                file: 'tasks.md',
                line: lineIndex + 1,
                message: '🚨 CRITICAL: tasks.md contains "**Acceptance Criteria**:" section. ACs belong in spec.md ONLY!',
                fix: 'Replace "**Acceptance Criteria**:" with "**Implementation**:" and add AC-ID references: "**AC-IDs**: AC-US7-01"'
            });
        }
        // Rule 2: Each task should have "Implementation" section (T-001, T-1000, etc.)
        const taskHeaders = lines
            .map((line, index) => ({ line, index }))
            .filter(({ line }) => /^###\s+T-\d{3,}/.test(line));
        taskHeaders.forEach(({ line: taskLine, index: taskIndex }) => {
            // Find next task or end of file
            const nextTaskIndex = taskHeaders.find(h => h.index > taskIndex)?.index ?? lines.length;
            // Extract task content
            const taskContent = lines.slice(taskIndex, nextTaskIndex).join('\n');
            // Check for Implementation section
            if (!taskContent.includes('**Implementation**:')) {
                issues.push({
                    code: ValidationErrorCode.TASKS_MISSING_IMPLEMENTATION,
                    severity: ValidationSeverity.WARNING,
                    file: 'tasks.md',
                    line: taskIndex + 1,
                    message: `Task ${taskLine.match(/T-\d{3,}/)?.[0]} missing "**Implementation**:" section`,
                    fix: 'Add "**Implementation**:" section with checkable technical steps'
                });
            }
            // Check for Test Plan
            if (!taskContent.includes('**Test Plan**') && !taskContent.includes('**Tests**')) {
                issues.push({
                    code: ValidationErrorCode.TASKS_MISSING_TEST_PLAN,
                    severity: ValidationSeverity.WARNING,
                    file: 'tasks.md',
                    line: taskIndex + 1,
                    message: `Task ${taskLine.match(/T-\d{3,}/)?.[0]} missing embedded test plan (BDD format)`,
                    fix: 'Add "**Test Plan** (BDD):" with Given-When-Then scenarios'
                });
            }
            // Check for AC-IDs references
            if (!taskContent.includes('**AC-IDs**:') && !taskContent.includes('**AC**:')) {
                issues.push({
                    code: ValidationErrorCode.TASKS_MISSING_AC_IDS,
                    severity: ValidationSeverity.INFO,
                    file: 'tasks.md',
                    line: taskIndex + 1,
                    message: `Task ${taskLine.match(/T-\d{3,}/)?.[0]} should reference which ACs it satisfies`,
                    fix: 'Add "**AC-IDs**: AC-US7-01, AC-US7-02" to link task to business requirements'
                });
            }
        });
        // Rule 3: tasks.md should NOT contain "As a user" language
        lines.forEach((line, index) => {
            if (line.includes('As a ') && line.includes(' I want ')) {
                issues.push({
                    code: ValidationErrorCode.TASKS_CONTAINS_USER_STORY,
                    severity: ValidationSeverity.WARNING,
                    file: 'tasks.md',
                    line: index + 1,
                    message: 'tasks.md contains user story language ("As a user..."). User stories belong in spec.md.',
                    fix: 'Move user story to spec.md. Keep tasks.md technical.'
                });
            }
        });
        return issues;
    }
    /**
     * Validate metadata.json project field for sync routing
     */
    validateMetadataProject(incrementDir, projectRoot) {
        const issues = [];
        if (!projectRoot)
            return issues;
        // Load config
        let config;
        try {
            const configPath = path.join(projectRoot, '.specweave', 'config.json');
            if (!fs.existsSync(configPath))
                return issues;
            config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
        }
        catch {
            return issues;
        }
        // Load metadata
        const metadataPath = path.join(incrementDir, 'metadata.json');
        let metadata;
        try {
            if (!fs.existsSync(metadataPath))
                return issues;
            metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
        }
        catch {
            return issues;
        }
        // Use workspace.repos (new) or fall back to umbrella.childRepos (legacy)
        const repos = config.workspace?.repos ?? config.umbrella?.childRepos ?? [];
        const workspaceName = config.workspace?.name ?? config.umbrella?.projectName;
        // Rule: Missing project with multiple repos
        if (repos.length > 1 && !metadata.project) {
            issues.push({
                code: ValidationErrorCode.METADATA_MISSING_PROJECT,
                severity: ValidationSeverity.WARNING,
                file: 'metadata.json',
                message: 'Increment has no `project` field — sync will use global fallback. Consider re-creating with `--project`',
            });
        }
        // Rule: Unknown project name
        if (metadata.project) {
            const knownIds = repos.map((r) => r.id);
            if (!knownIds.includes(metadata.project) && metadata.project !== workspaceName) {
                issues.push({
                    code: ValidationErrorCode.METADATA_UNKNOWN_PROJECT,
                    severity: ValidationSeverity.WARNING,
                    file: 'metadata.json',
                    message: `Project '${metadata.project}' not found in workspace.repos or workspace.name`,
                });
            }
        }
        return issues;
    }
    /**
     * Generate a formatted report of validation issues
     */
    formatValidationReport(result) {
        if (result.valid && result.issues.length === 0) {
            return '✅ All files pass validation! Perfect structure.';
        }
        const lines = [];
        lines.push('# Three File Structure Validation Report\n');
        lines.push(`**Status**: ${result.valid ? '✅ PASS' : '❌ FAIL'}`);
        lines.push(`**Errors**: ${result.summary.errors}`);
        lines.push(`**Warnings**: ${result.summary.warnings}`);
        lines.push(`**Infos**: ${result.summary.infos}\n`);
        if (result.issues.length > 0) {
            lines.push('## Issues Found\n');
            // Group by severity
            const errors = result.issues.filter(i => i.severity === ValidationSeverity.ERROR);
            const warnings = result.issues.filter(i => i.severity === ValidationSeverity.WARNING);
            const infos = result.issues.filter(i => i.severity === ValidationSeverity.INFO);
            const formatIssues = (issues, header) => {
                if (issues.length === 0)
                    return;
                lines.push(`${header}\n`);
                for (const issue of issues) {
                    lines.push(`**${issue.file}:${issue.line ?? '?'}** - ${issue.code}`);
                    lines.push(`- **Message**: ${issue.message}`);
                    if (issue.fix) {
                        lines.push(`- **Fix**: ${issue.fix}`);
                    }
                    lines.push('');
                }
            };
            formatIssues(errors, '### ❌ Errors (Must Fix)');
            formatIssues(warnings, '### ⚠️ Warnings (Should Fix)');
            formatIssues(infos, '### ℹ️ Info (Nice to Have)');
        }
        return lines.join('\n');
    }
}
//# sourceMappingURL=three-file-validator.js.map