/**
 * Task Validator Module
 * Validates tasks.md format compliance before approval
 */
/**
 * Validate tasks.md content against required format
 * @param content The markdown content to validate
 * @returns ValidationResult with errors, warnings, and summary
 */
export function validateTasksMarkdown(content) {
    const lines = content.split('\n');
    const errors = [];
    const warnings = [];
    let taskCount = 0;
    let validTaskCount = 0;
    // Find all checkbox lines and their ranges (including malformed ones with asterisks)
    const checkboxIndices = [];
    for (let i = 0; i < lines.length; i++) {
        if (lines[i].match(/^\s*[-*]\s*\[/)) {
            checkboxIndices.push(i);
        }
    }
    // 依存関係バリデーション用: 各タスクのID、依存先、行番号、Phase情報を収集
    const taskDependencyInfo = [];
    // Phase見出しの行番号とPhase名を収集
    const phaseHeadingLines = [];
    for (let i = 0; i < lines.length; i++) {
        const phaseMatch = lines[i].match(/^##\s+(Phase\s+\d+\s*:.+)/i);
        if (phaseMatch) {
            phaseHeadingLines.push({ line: i, name: phaseMatch[1].trim() });
        }
    }
    // Process each checkbox task
    for (let idx = 0; idx < checkboxIndices.length; idx++) {
        const lineIndex = checkboxIndices[idx];
        const endLine = idx < checkboxIndices.length - 1 ? checkboxIndices[idx + 1] : lines.length;
        const line = lines[lineIndex];
        const lineNum = lineIndex + 1; // 1-based for user-friendly messages
        taskCount++;
        let taskValid = true;
        let taskId;
        // 1. Validate checkbox format: must be "- [ ]", "- [-]", or "- [x]"
        const checkboxMatch = line.match(/^\s*-\s+\[([ x\-])\]\s+(.+)/);
        if (!checkboxMatch) {
            // Check for common malformed patterns
            const malformedPatterns = [
                { pattern: /^\s*-\s*\[\]\s/, message: 'Empty checkbox brackets', suggestion: 'Use "- [ ]" with a space inside brackets' },
                { pattern: /^\s*-\s*\[([^x \-])\]/, message: 'Invalid checkbox character', suggestion: 'Use space for pending, x for completed, - for in-progress' },
                { pattern: /^\s*-\[\s*[x \-]?\s*\]/, message: 'Missing space after hyphen', suggestion: 'Use "- [ ]" with space between - and [' },
                { pattern: /^\s*\*\s+\[/, message: 'Wrong bullet character', suggestion: 'Use hyphen (-) instead of asterisk (*)' },
            ];
            let foundMalformed = false;
            for (const { pattern, message, suggestion } of malformedPatterns) {
                if (line.match(pattern)) {
                    errors.push({
                        line: lineNum,
                        field: 'checkbox',
                        message,
                        suggestion,
                        severity: 'error'
                    });
                    foundMalformed = true;
                    taskValid = false;
                    break;
                }
            }
            if (!foundMalformed) {
                errors.push({
                    line: lineNum,
                    field: 'checkbox',
                    message: 'Invalid checkbox format',
                    suggestion: 'Expected format: "- [ ] 1. Task description"',
                    severity: 'error'
                });
                taskValid = false;
            }
            continue;
        }
        const statusChar = checkboxMatch[1];
        const taskText = checkboxMatch[2];
        // 2. Validate task ID: must have numeric ID like "1.", "1.1", "2.3"
        const taskIdMatch = taskText.match(/^(\d+(?:\.\d+)*)\s*\.?\s+(.+)/);
        if (!taskIdMatch) {
            errors.push({
                line: lineNum,
                field: 'taskId',
                message: 'Missing task ID number',
                suggestion: 'Add numeric ID like "1." or "1.1" after checkbox',
                severity: 'error'
            });
            taskValid = false;
        }
        else {
            taskId = taskIdMatch[1];
        }
        // 3. Validate metadata in following lines
        let hasRequirements = false;
        let hasLeverage = false;
        let hasPrompt = false;
        let dependsOnIds = [];
        let hasFiles = false;
        let promptHasClosingUnderscore = false;
        let promptSections = [];
        let inPromptBlock = false;
        for (let lineIdx = lineIndex + 1; lineIdx < endLine; lineIdx++) {
            const contentLine = lines[lineIdx];
            const trimmedLine = contentLine.trim();
            if (!trimmedLine)
                continue;
            // _Prompt: ブロック内のメタデータ検出を抑制
            // _Prompt: は複数行にわたるため、開始と終了を追跡する
            if (trimmedLine.includes('_Prompt:')) {
                inPromptBlock = true;
                // 単一行の _Prompt: ... _ の場合はすぐにリセット
                if (trimmedLine.match(/_Prompt:\s*.+_$/)) {
                    inPromptBlock = false;
                }
            }
            else if (inPromptBlock) {
                // _Prompt: ブロックの終了を検出（新しいメタデータフィールドの出現で終了）
                if (trimmedLine.match(/^_[A-Z][a-zA-Z]+:/) || trimmedLine.match(/^Files?:/i)) {
                    inPromptBlock = false;
                }
            }
            // Check for _Requirements:_ format（_Prompt: ブロック内はスキップ）
            if (trimmedLine.includes('Requirements:') && !inPromptBlock) {
                hasRequirements = true;
                // Check for proper underscore delimiters
                if (!trimmedLine.match(/_Requirements:\s*[^_]+_/)) {
                    if (trimmedLine.match(/Requirements:\s*\S/) && !trimmedLine.includes('_Requirements:')) {
                        warnings.push({
                            line: lineIdx + 1,
                            taskId,
                            field: 'requirements',
                            message: 'Requirements field missing underscore delimiters',
                            suggestion: 'Use "_Requirements: ..._" format for proper parsing',
                            severity: 'warning'
                        });
                    }
                }
            }
            // Check for _Leverage:_ format（_Prompt: ブロック内はスキップ）
            if (trimmedLine.includes('Leverage:') && !inPromptBlock) {
                hasLeverage = true;
                // Check for proper underscore delimiters
                if (!trimmedLine.match(/_Leverage:\s*[^_]+_/)) {
                    if (trimmedLine.match(/Leverage:\s*\S/) && !trimmedLine.includes('_Leverage:')) {
                        warnings.push({
                            line: lineIdx + 1,
                            taskId,
                            field: 'leverage',
                            message: 'Leverage field missing underscore delimiters',
                            suggestion: 'Use "_Leverage: ..._" format for proper parsing',
                            severity: 'warning'
                        });
                    }
                }
            }
            // Check for _DependsOn:_ format（_Prompt: ブロック内はスキップ）
            if (trimmedLine.includes('DependsOn:') && !inPromptBlock) {
                const depMatch = trimmedLine.match(/_DependsOn:\s*([^_]+?)_/);
                if (depMatch) {
                    dependsOnIds = depMatch[1].split(',').map(d => d.trim()).filter(d => d);
                }
                if (!trimmedLine.match(/_DependsOn:\s*[^_]+_/)) {
                    if (trimmedLine.match(/DependsOn:\s*\S/) && !trimmedLine.includes('_DependsOn:')) {
                        warnings.push({
                            line: lineIdx + 1,
                            taskId,
                            field: 'dependsOn',
                            message: 'DependsOn field missing underscore delimiters',
                            suggestion: 'Use "_DependsOn: 1.1, 1.2_" format for proper parsing',
                            severity: 'warning'
                        });
                    }
                }
            }
            // Check for Files: field
            if (trimmedLine.match(/Files?:/i)) {
                hasFiles = true;
            }
            // Check for _Prompt:_ format
            if (trimmedLine.includes('_Prompt:')) {
                hasPrompt = true;
                // Check for closing underscore
                if (trimmedLine.match(/_Prompt:\s*.+_$/)) {
                    promptHasClosingUnderscore = true;
                }
                else {
                    // Check if prompt continues on multiple lines - look for closing underscore
                    let foundClosing = false;
                    for (let j = lineIdx; j < endLine; j++) {
                        if (lines[j].trim().endsWith('_') && !lines[j].trim().match(/^_[A-Z]/)) {
                            foundClosing = true;
                            break;
                        }
                    }
                    promptHasClosingUnderscore = foundClosing;
                }
                // Extract prompt content and check structure
                const promptContent = trimmedLine.replace(/_Prompt:\s*/, '').replace(/_$/, '');
                // Check for required prompt sections: Role, Task, Restrictions, Success
                const requiredSections = ['Role', 'Task', 'Restrictions', 'Success'];
                for (const section of requiredSections) {
                    if (promptContent.toLowerCase().includes(section.toLowerCase() + ':')) {
                        promptSections.push(section);
                    }
                }
            }
        }
        // Validate prompt requirements
        if (hasPrompt) {
            if (!promptHasClosingUnderscore) {
                warnings.push({
                    line: lineNum,
                    taskId,
                    field: 'prompt',
                    message: 'Prompt field may be missing closing underscore',
                    suggestion: 'Ensure _Prompt: ..._ ends with underscore',
                    severity: 'warning'
                });
            }
            // Check for missing prompt sections
            const requiredSections = ['Role', 'Task', 'Restrictions', 'Success'];
            const missingSections = requiredSections.filter(s => !promptSections.includes(s));
            if (missingSections.length > 0) {
                warnings.push({
                    line: lineNum,
                    taskId,
                    field: 'prompt_structure',
                    message: `Prompt missing sections: ${missingSections.join(', ')}`,
                    suggestion: 'Format: Role: ... | Task: ... | Restrictions: ... | Success: ...',
                    severity: 'warning'
                });
            }
        }
        // Check for _TestFocus field
        let hasTestFocus = false;
        let hasPhaseReview = false;
        for (let lineIdx = lineIndex + 1; lineIdx < endLine; lineIdx++) {
            const trimmedLine = lines[lineIdx].trim();
            if (trimmedLine.includes('_TestFocus:'))
                hasTestFocus = true;
            if (trimmedLine.includes('_PhaseReview:'))
                hasPhaseReview = true;
        }
        // Warn if non-phase-review task is missing _TestFocus
        if (!hasPhaseReview && !hasTestFocus && taskId) {
            warnings.push({
                line: lineNum,
                taskId,
                field: 'testFocus',
                message: 'Task missing _TestFocus field for TDD guidance',
                suggestion: 'Add "_TestFocus: ..." to describe what tests should cover in the RED phase',
                severity: 'warning'
            });
        }
        // Warn if _PhaseReview task is missing _Prompt
        if (hasPhaseReview && !hasPrompt && taskId) {
            warnings.push({
                line: lineNum,
                taskId,
                field: 'prompt',
                message: 'Phase review task missing _Prompt field',
                suggestion: 'Add a _Prompt field with review and commit instructions',
                severity: 'warning'
            });
        }
        // Detect standalone test tasks (TDD makes them unnecessary)
        const standaloneTestPattern = /\b(write\s+tests?|create\s+unit\s+tests?|add\s+tests?|write\s+.*\s+tests?)\b/i;
        if (taskId && taskText && standaloneTestPattern.test(taskText)) {
            warnings.push({
                line: lineNum,
                taskId,
                field: 'taskDesign',
                message: 'Standalone test task detected — TDD handles testing automatically',
                suggestion: 'Merge test requirements into the implementation task and use _TestFocus to guide the RED phase',
                severity: 'warning'
            });
        }
        // 依存関係情報を収集
        if (taskId && dependsOnIds.length > 0) {
            // タスクが属する Phase を特定
            let taskPhase;
            for (let p = phaseHeadingLines.length - 1; p >= 0; p--) {
                if (lineIndex > phaseHeadingLines[p].line) {
                    taskPhase = phaseHeadingLines[p].name;
                    break;
                }
            }
            taskDependencyInfo.push({ taskId, dependsOnIds, lineNum, phase: taskPhase });
        }
        // Track if task is valid (only errors affect validity, not warnings)
        if (taskValid) {
            validTaskCount++;
        }
    }
    // --- 依存関係バリデーション ---
    // 全タスクIDを収集
    const allTaskIds = new Set();
    // PhaseReview タスクIDを収集
    const phaseReviewTaskIds = new Set();
    // タスクID → Phase のマッピング
    const taskPhaseMap = new Map();
    for (let idx = 0; idx < checkboxIndices.length; idx++) {
        const lineIndex = checkboxIndices[idx];
        const line = lines[lineIndex];
        const checkboxMatch = line.match(/^\s*-\s+\[([ x\-])\]\s+(.+)/);
        if (checkboxMatch) {
            const taskIdMatch = checkboxMatch[2].match(/^(\d+(?:\.\d+)*)\s*\.?\s+/);
            if (taskIdMatch) {
                const tid = taskIdMatch[1];
                allTaskIds.add(tid);
                // PhaseReview 判定
                const endLine = idx < checkboxIndices.length - 1 ? checkboxIndices[idx + 1] : lines.length;
                for (let j = lineIndex + 1; j < endLine; j++) {
                    if (lines[j].trim().includes('_PhaseReview:')) {
                        phaseReviewTaskIds.add(tid);
                        break;
                    }
                }
                // Phase 帰属
                for (let p = phaseHeadingLines.length - 1; p >= 0; p--) {
                    if (lineIndex > phaseHeadingLines[p].line) {
                        taskPhaseMap.set(tid, phaseHeadingLines[p].name);
                        break;
                    }
                }
            }
        }
    }
    for (const dep of taskDependencyInfo) {
        for (const refId of dep.dependsOnIds) {
            // 自己参照チェック
            if (refId === dep.taskId) {
                errors.push({
                    line: dep.lineNum,
                    taskId: dep.taskId,
                    field: 'dependsOn',
                    message: `Self-dependency detected: task ${dep.taskId} depends on itself`,
                    severity: 'error'
                });
                continue;
            }
            // 参照先タスクID存在チェック
            if (!allTaskIds.has(refId)) {
                errors.push({
                    line: dep.lineNum,
                    taskId: dep.taskId,
                    field: 'dependsOn',
                    message: `Dependency references non-existent task ID: ${refId}`,
                    severity: 'error'
                });
                continue;
            }
            // PhaseReview タスクへの依存チェック
            if (phaseReviewTaskIds.has(refId)) {
                errors.push({
                    line: dep.lineNum,
                    taskId: dep.taskId,
                    field: 'dependsOn',
                    message: `Dependency references a PhaseReview task: ${refId}. PhaseReview tasks are excluded from wave computation and cannot be depended on.`,
                    severity: 'error'
                });
                continue;
            }
            // クロスフェーズ依存チェック
            const depPhase = taskPhaseMap.get(dep.taskId);
            const refPhase = taskPhaseMap.get(refId);
            if (depPhase && refPhase && depPhase !== refPhase) {
                errors.push({
                    line: dep.lineNum,
                    taskId: dep.taskId,
                    field: 'dependsOn',
                    message: `Cross-phase dependency detected: task ${dep.taskId} (${depPhase}) depends on ${refId} (${refPhase}). _DependsOn: must reference tasks within the same Phase.`,
                    severity: 'error'
                });
            }
        }
    }
    // 循環依存検出 (DFS)
    if (taskDependencyInfo.length > 0) {
        const depMap = new Map();
        for (const dep of taskDependencyInfo) {
            depMap.set(dep.taskId, dep.dependsOnIds);
        }
        const lineMap = new Map();
        for (const dep of taskDependencyInfo) {
            lineMap.set(dep.taskId, dep.lineNum);
        }
        const visiting = new Set();
        const visited = new Set();
        function detectCycle(taskId, path) {
            if (visiting.has(taskId)) {
                const cycleStart = path.indexOf(taskId);
                const cyclePath = [...path.slice(cycleStart), taskId];
                errors.push({
                    line: lineMap.get(cyclePath[0]) || 0,
                    taskId: cyclePath[0],
                    field: 'dependsOn',
                    message: `Circular dependency detected: ${cyclePath.join(' → ')}`,
                    severity: 'error'
                });
                return true;
            }
            if (visited.has(taskId))
                return false;
            visiting.add(taskId);
            const deps = depMap.get(taskId) || [];
            for (const d of deps) {
                if (depMap.has(d) || visiting.has(d)) {
                    detectCycle(d, [...path, taskId]);
                }
            }
            visiting.delete(taskId);
            visited.add(taskId);
            return false;
        }
        for (const dep of taskDependencyInfo) {
            if (!visited.has(dep.taskId)) {
                detectCycle(dep.taskId, []);
            }
        }
    }
    // Phase-level validations
    let phaseCount = 0;
    let phaseReviewCount = 0;
    const hasPhaseHeadings = lines.some(l => /^##\s+Phase\s+\d+/i.test(l));
    if (hasPhaseHeadings) {
        // Count phases
        for (const line of lines) {
            if (/^##\s+Phase\s+\d+/i.test(line))
                phaseCount++;
        }
        // Count phase review tasks
        for (let idx = 0; idx < checkboxIndices.length; idx++) {
            const lineIndex = checkboxIndices[idx];
            const endLine = idx < checkboxIndices.length - 1 ? checkboxIndices[idx + 1] : lines.length;
            for (let j = lineIndex + 1; j < endLine; j++) {
                if (lines[j].trim().includes('_PhaseReview:')) {
                    phaseReviewCount++;
                    break;
                }
            }
        }
        // Warn if phases exist but no phase review tasks
        if (phaseReviewCount === 0) {
            warnings.push({
                line: 1,
                field: 'phaseReview',
                message: 'Phase headings found but no phase review tasks',
                suggestion: 'Add a "_PhaseReview: true_" task at the end of each phase for review and commit',
                severity: 'warning'
            });
        }
    }
    return {
        valid: errors.length === 0,
        errors,
        warnings,
        summary: {
            totalTasks: taskCount,
            validTasks: validTaskCount,
            invalidTasks: taskCount - validTaskCount,
            phases: phaseCount,
            phaseReviewTasks: phaseReviewCount
        }
    };
}
/**
 * Format validation errors for display
 * @param result The validation result
 * @returns Formatted string array of errors and warnings
 */
export function formatValidationErrors(result) {
    const messages = [];
    if (result.errors.length > 0) {
        messages.push(`Found ${result.errors.length} error(s):`);
        for (const error of result.errors) {
            const taskInfo = error.taskId ? ` (Task ${error.taskId})` : '';
            messages.push(`  Line ${error.line}${taskInfo}: ${error.message}`);
            if (error.suggestion) {
                messages.push(`    Suggestion: ${error.suggestion}`);
            }
        }
    }
    if (result.warnings.length > 0) {
        messages.push(`Found ${result.warnings.length} warning(s):`);
        for (const warning of result.warnings) {
            const taskInfo = warning.taskId ? ` (Task ${warning.taskId})` : '';
            messages.push(`  Line ${warning.line}${taskInfo}: ${warning.message}`);
            if (warning.suggestion) {
                messages.push(`    Suggestion: ${warning.suggestion}`);
            }
        }
    }
    return messages;
}
//# sourceMappingURL=task-validator.js.map