/**
 * Phase Detector - Auto-detect current workflow phase
 *
 * Analyzes user prompts, commands, file states, and context to determine
 * the current phase of the SpecWeave development workflow.
 *
 * Detection Algorithm:
 * - Keyword Analysis (40% weight): Match keywords in user prompt
 * - Command Analysis (30% weight): Analyze recent command history
 * - Context Analysis (20% weight): Check file states and increment status
 * - Hint Analysis (10% weight): Detect explicit user hints
 *
 * Part of increment 0039: Ultra-Smart Next Command (US-001)
 */
import { WorkflowPhase, EvidenceType } from './types.js';
import * as fs from 'fs';
import * as path from 'path';
import { MetadataManager } from '../increment/metadata-manager.js';
/**
 * Default configuration for PhaseDetector
 */
const DEFAULT_CONFIG = {
    keywordWeight: 0.4,
    commandWeight: 0.3,
    contextWeight: 0.2,
    hintWeight: 0.1,
    confidenceThreshold: 0.6,
    includeAlternatives: true
};
/**
 * Keyword mappings for each workflow phase
 */
const KEYWORD_MAPPINGS = [
    // SPEC_WRITING phase
    {
        phase: WorkflowPhase.SPEC_WRITING,
        keywords: [
            'spec', 'specification', 'requirements', 'user story', 'user stories',
            'acceptance criteria', 'AC', 'functional requirement', 'non-functional',
            'define', 'document', 'describe', 'outline', 'detail',
            'what should', 'how should', 'requirements gathering', 'product requirements',
            'write spec', 'update spec', 'edit spec', 'create spec'
        ],
        weight: 1.0
    },
    // PLAN_GENERATION phase
    {
        phase: WorkflowPhase.PLAN_GENERATION,
        keywords: [
            'plan', 'architecture', 'design', 'approach', 'strategy',
            'technical design', 'system design', 'component design',
            'how to implement', 'implementation plan', 'technical approach',
            'architect', 'module', 'component', 'service',
            'generate plan', 'create plan', 'plan.md'
        ],
        weight: 1.0
    },
    // TASK_BREAKDOWN phase
    {
        phase: WorkflowPhase.TASK_BREAKDOWN,
        keywords: [
            'task', 'tasks', 'todo', 'checklist', 'breakdown', 'split',
            'divide', 'organize', 'prioritize', 'order',
            'task list', 'work items', 'backlog items',
            'create tasks', 'generate tasks', 'tasks.md', 'task breakdown'
        ],
        weight: 1.0
    },
    // IMPLEMENTATION phase
    {
        phase: WorkflowPhase.IMPLEMENTATION,
        keywords: [
            'implement', 'code', 'develop', 'build', 'create', 'add',
            'write code', 'programming', 'coding', 'development',
            'feature', 'functionality', 'component implementation',
            'let\'s build', 'let\'s implement', 'let\'s code', 'let\'s develop',
            'start working', 'begin implementation', 'write', 'fix bug', 'bug fix'
        ],
        weight: 1.2 // Slightly higher weight as implementation is common
    },
    // TESTING phase
    {
        phase: WorkflowPhase.TESTING,
        keywords: [
            'test', 'testing', 'unit test', 'integration test', 'e2e test',
            'test case', 'test suite', 'coverage', 'tdd', 'test-driven',
            'write tests', 'run tests', 'test coverage', 'jest', 'mocha',
            'assertions', 'expect', 'verify', 'validate tests',
            'test plan', 'test strategy'
        ],
        weight: 1.1
    },
    // DOCUMENTATION phase
    {
        phase: WorkflowPhase.DOCUMENTATION,
        keywords: [
            'document', 'documentation', 'docs', 'readme', 'guide',
            'api documentation', 'user guide', 'developer guide',
            'write docs', 'update docs', 'sync docs', 'living docs',
            'markdown', 'comments', 'jsdoc', 'docstring'
        ],
        weight: 0.9
    },
    // REVIEW phase
    {
        phase: WorkflowPhase.REVIEW,
        keywords: [
            'review', 'code review', 'qa', 'quality', 'validate', 'check',
            'quality assurance', 'quality check', 'peer review',
            'inspect', 'audit', 'verify quality', 'quality gate',
            'pull request', 'pr', 'merge request', 'mr'
        ],
        weight: 0.9
    },
    // COMPLETION phase
    {
        phase: WorkflowPhase.COMPLETION,
        keywords: [
            'done', 'complete', 'finish', 'close', 'wrap up', 'finalize',
            'completed', 'finished', 'all done', 'ready to close',
            'increment done', 'increment complete', 'archive',
            'mark as done', 'mark complete', 'close increment'
        ],
        weight: 1.0
    }
];
/**
 * Command mappings for each workflow phase
 */
const COMMAND_MAPPINGS = [
    // SPEC_WRITING
    {
        phase: WorkflowPhase.SPEC_WRITING,
        commandPattern: '/sw:increment',
        weight: 1.2
    },
    {
        phase: WorkflowPhase.SPEC_WRITING,
        commandPattern: '/sw:validate',
        weight: 0.8
    },
    // PLAN_GENERATION
    {
        phase: WorkflowPhase.PLAN_GENERATION,
        commandPattern: '/sw:plan',
        weight: 1.5
    },
    // IMPLEMENTATION
    {
        phase: WorkflowPhase.IMPLEMENTATION,
        commandPattern: '/sw:do',
        weight: 1.5
    },
    {
        phase: WorkflowPhase.IMPLEMENTATION,
        commandPattern: '/sw:progress',
        weight: 1.0
    },
    // TESTING
    {
        phase: WorkflowPhase.TESTING,
        commandPattern: '/sw:test',
        weight: 1.3
    },
    {
        phase: WorkflowPhase.TESTING,
        commandPattern: 'npm test',
        weight: 1.2
    },
    {
        phase: WorkflowPhase.TESTING,
        commandPattern: 'jest',
        weight: 1.2
    },
    // DOCUMENTATION
    {
        phase: WorkflowPhase.DOCUMENTATION,
        commandPattern: '/sw:sync-docs',
        weight: 1.4
    },
    // REVIEW
    {
        phase: WorkflowPhase.REVIEW,
        commandPattern: '/sw:qa',
        weight: 1.3
    },
    // COMPLETION
    {
        phase: WorkflowPhase.COMPLETION,
        commandPattern: '/sw:done',
        weight: 1.5
    }
];
/**
 * PhaseDetector - Detect current workflow phase
 */
export class PhaseDetector {
    constructor(config = {}) {
        this.DEFAULT_CACHE_TTL = 5000; // 5 seconds
        // Performance tracking
        this.detectionCount = 0;
        this.cacheHits = 0;
        this.cacheMisses = 0;
        this.totalDetectionTime = 0;
        this.config = { ...DEFAULT_CONFIG, ...config };
        this.fileStateCache = new Map();
    }
    /**
     * Get performance statistics
     */
    getPerformanceStats() {
        return {
            detectionCount: this.detectionCount,
            cacheHits: this.cacheHits,
            cacheMisses: this.cacheMisses,
            cacheHitRate: this.cacheHits / Math.max(this.cacheHits + this.cacheMisses, 1),
            averageDetectionTime: this.totalDetectionTime / Math.max(this.detectionCount, 1)
        };
    }
    /**
     * Reset performance statistics
     */
    resetPerformanceStats() {
        this.detectionCount = 0;
        this.cacheHits = 0;
        this.cacheMisses = 0;
        this.totalDetectionTime = 0;
    }
    /**
     * Clear the file state cache
     */
    clearCache() {
        this.fileStateCache.clear();
    }
    /**
     * Get cached file states if available and not expired
     */
    getCachedFileStates(incrementDir) {
        const cached = this.fileStateCache.get(incrementDir);
        if (!cached || Date.now() - cached.timestamp > cached.ttl) {
            if (cached)
                this.fileStateCache.delete(incrementDir);
            this.cacheMisses++;
            return null;
        }
        this.cacheHits++;
        return cached.states;
    }
    /**
     * Cache file states for future use
     */
    cacheFileStates(incrementDir, states) {
        this.fileStateCache.set(incrementDir, {
            states,
            timestamp: Date.now(),
            ttl: this.DEFAULT_CACHE_TTL
        });
    }
    /**
     * Detect the current workflow phase from context
     */
    async detect(context) {
        const startTime = Date.now();
        const evidence = [];
        // 1. Keyword Analysis (40% weight)
        const keywordEvidence = this.analyzeKeywords(context.userPrompt);
        evidence.push(...keywordEvidence);
        // 2. Command Analysis (30% weight)
        if (context.recentCommands && context.recentCommands.length > 0) {
            const commandEvidence = this.analyzeCommands(context.recentCommands);
            evidence.push(...commandEvidence);
        }
        // 3. Context Analysis (20% weight)
        const contextEvidence = await this.analyzeContext(context);
        evidence.push(...contextEvidence);
        // 4. Hint Analysis (10% weight)
        if (context.explicitHint) {
            const hintEvidence = this.analyzeHint(context.explicitHint);
            evidence.push(...hintEvidence);
        }
        // Calculate phase scores
        const phaseScores = this.calculatePhaseScores(evidence);
        // Get top phase and alternatives
        const sortedPhases = Object.entries(phaseScores)
            .map(([phase, score]) => ({ phase: phase, score }))
            .sort((a, b) => b.score - a.score);
        const topPhase = sortedPhases[0];
        const alternatives = sortedPhases
            .slice(1, 4) // Top 3 alternatives
            .map(p => ({
            phase: p.phase,
            confidence: p.score,
            reason: `Score: ${p.score.toFixed(2)} (lower than primary phase)`
        }));
        // Generate suggestion
        const suggestion = this.generateSuggestion(topPhase.phase, topPhase.score);
        // Track performance
        const elapsedTime = Date.now() - startTime;
        this.detectionCount++;
        this.totalDetectionTime += elapsedTime;
        return {
            phase: topPhase.phase,
            confidence: topPhase.score,
            evidence: evidence.filter(e => e.weight > 0), // Filter out zero-weight evidence
            alternatives: this.config.includeAlternatives ? alternatives : [],
            suggestedCommand: suggestion?.command,
            suggestionReason: suggestion?.reason
        };
    }
    /**
     * Analyze keywords in user prompt
     */
    analyzeKeywords(userPrompt) {
        const evidence = [];
        const promptLower = userPrompt.toLowerCase();
        for (const mapping of KEYWORD_MAPPINGS) {
            const matchedKeywords = [];
            for (const keyword of mapping.keywords) {
                const keywordLower = keyword.toLowerCase();
                if (promptLower.includes(keywordLower)) {
                    matchedKeywords.push(keyword);
                }
            }
            if (matchedKeywords.length > 0) {
                // Weight = (base weight) * (mapping weight) * (number of matches)
                const baseWeight = this.config.keywordWeight;
                const weight = baseWeight * (mapping.weight || 1.0) * Math.min(matchedKeywords.length, 3) / 3;
                evidence.push({
                    type: EvidenceType.KEYWORD,
                    description: `Keywords found: ${matchedKeywords.join(', ')}`,
                    weight,
                    source: `keyword: ${matchedKeywords[0]}`
                });
            }
        }
        return evidence;
    }
    /**
     * Analyze recent command history
     */
    analyzeCommands(recentCommands) {
        const evidence = [];
        // Most recent command has highest weight
        const weights = [1.0, 0.7, 0.5, 0.3, 0.2]; // Decay for older commands
        for (let i = 0; i < Math.min(recentCommands.length, 5); i++) {
            const command = recentCommands[i];
            const recencyWeight = weights[i];
            for (const mapping of COMMAND_MAPPINGS) {
                if (command.includes(mapping.commandPattern)) {
                    const baseWeight = this.config.commandWeight;
                    const weight = baseWeight * (mapping.weight || 1.0) * recencyWeight;
                    evidence.push({
                        type: EvidenceType.COMMAND,
                        description: `Recent command: ${command}`,
                        weight,
                        source: `command: ${mapping.commandPattern}`
                    });
                }
            }
        }
        return evidence;
    }
    /**
     * Analyze explicit user hint
     */
    analyzeHint(hint) {
        const evidence = [];
        const hintLower = hint.toLowerCase();
        // Check for phase names directly mentioned
        for (const phase of Object.values(WorkflowPhase)) {
            if (hintLower.includes(phase) || hintLower.includes(phase.replace('-', ' '))) {
                evidence.push({
                    type: EvidenceType.USER_HINT,
                    description: `Explicit phase hint: ${phase}`,
                    weight: this.config.hintWeight * 2.0, // Strong signal
                    source: `hint: ${phase}`
                });
            }
        }
        return evidence;
    }
    /**
     * Analyze context (file states and increment status)
     */
    async analyzeContext(context) {
        const evidence = [];
        // Determine working directory
        const workingDir = context.workingDirectory || process.cwd();
        // Check if we're in a SpecWeave project
        const specweaveDir = path.join(workingDir, '.specweave');
        if (!fs.existsSync(specweaveDir)) {
            // Not in a SpecWeave project, no context evidence
            return evidence;
        }
        // If incrementId provided, analyze increment-specific context
        if (context.incrementId) {
            const incrementDir = path.join(specweaveDir, 'increments', context.incrementId);
            if (fs.existsSync(incrementDir)) {
                // Analyze file states
                const fileStates = this.getFileStates(incrementDir);
                const fileEvidence = this.analyzeFileStates(fileStates);
                evidence.push(...fileEvidence);
                // Analyze increment metadata
                try {
                    const metadata = MetadataManager.read(context.incrementId);
                    const statusEvidence = this.analyzeIncrementStatus(metadata.status);
                    evidence.push(...statusEvidence);
                }
                catch (error) {
                    // Metadata not found or invalid, skip status evidence
                }
            }
        }
        return evidence;
    }
    /**
     * Get file states for key increment files (with caching)
     */
    getFileStates(incrementDir) {
        // Check cache first
        const cached = this.getCachedFileStates(incrementDir);
        if (cached) {
            return cached;
        }
        // Cache miss, compute file states
        const files = ['spec.md', 'plan.md', 'tasks.md'];
        const states = [];
        for (const file of files) {
            const filePath = path.join(incrementDir, file);
            const exists = fs.existsSync(filePath);
            if (exists) {
                const stats = fs.statSync(filePath);
                const isEmpty = stats.size === 0;
                states.push({
                    path: file,
                    exists: true,
                    empty: isEmpty,
                    lastModified: stats.mtime
                });
            }
            else {
                states.push({
                    path: file,
                    exists: false
                });
            }
        }
        // Cache the result
        this.cacheFileStates(incrementDir, states);
        return states;
    }
    /**
     * Analyze file states to determine likely phase
     */
    analyzeFileStates(fileStates) {
        const evidence = [];
        const specExists = fileStates.find(f => f.path === 'spec.md')?.exists || false;
        const planExists = fileStates.find(f => f.path === 'plan.md')?.exists || false;
        const tasksExist = fileStates.find(f => f.path === 'tasks.md')?.exists || false;
        const specEmpty = fileStates.find(f => f.path === 'spec.md')?.empty || false;
        const planEmpty = fileStates.find(f => f.path === 'plan.md')?.empty || false;
        // Spec exists but empty → SPEC_WRITING
        if (specExists && specEmpty) {
            evidence.push({
                type: EvidenceType.FILE_STATE,
                description: 'spec.md exists but is empty',
                weight: this.config.contextWeight * 1.3,
                source: 'file-state: spec-empty'
            });
        }
        // Spec exists but plan doesn't → PLAN_GENERATION
        if (specExists && !specEmpty && !planExists) {
            evidence.push({
                type: EvidenceType.FILE_STATE,
                description: 'spec.md exists, plan.md missing',
                weight: this.config.contextWeight * 1.5,
                source: 'file-state: plan-missing'
            });
        }
        // Plan exists but empty → PLAN_GENERATION
        if (planExists && planEmpty) {
            evidence.push({
                type: EvidenceType.FILE_STATE,
                description: 'plan.md exists but is empty',
                weight: this.config.contextWeight * 1.2,
                source: 'file-state: plan-empty'
            });
        }
        // Plan exists but tasks don't → TASK_BREAKDOWN
        if (planExists && !planEmpty && !tasksExist) {
            evidence.push({
                type: EvidenceType.FILE_STATE,
                description: 'plan.md exists, tasks.md missing',
                weight: this.config.contextWeight * 1.4,
                source: 'file-state: tasks-missing'
            });
        }
        // All files exist → likely IMPLEMENTATION or TESTING
        if (specExists && planExists && tasksExist && !specEmpty && !planEmpty) {
            evidence.push({
                type: EvidenceType.FILE_STATE,
                description: 'All increment files present',
                weight: this.config.contextWeight * 1.0,
                source: 'file-state: all-present'
            });
        }
        return evidence;
    }
    /**
     * Analyze increment status to determine likely phase
     */
    analyzeIncrementStatus(status) {
        const STATUS_WEIGHTS = {
            planning: 1.3,
            active: 1.2,
            paused: 0.8,
            completed: 1.5
        };
        const normalizedStatus = status.toLowerCase();
        const weightMultiplier = STATUS_WEIGHTS[normalizedStatus];
        if (!weightMultiplier)
            return [];
        return [{
                type: EvidenceType.INCREMENT_STATUS,
                description: `Increment status: ${normalizedStatus.toUpperCase()}`,
                weight: this.config.contextWeight * weightMultiplier,
                source: `status: ${normalizedStatus}`
            }];
    }
    /**
     * Calculate aggregated phase scores from evidence
     */
    calculatePhaseScores(evidence) {
        const scores = {};
        // Initialize all phases to 0
        for (const phase of Object.values(WorkflowPhase)) {
            scores[phase] = 0;
        }
        // Group evidence by phase
        const evidenceByPhase = this.groupEvidenceByPhase(evidence);
        // Calculate score for each phase
        for (const [phase, phaseEvidence] of Object.entries(evidenceByPhase)) {
            const totalWeight = phaseEvidence.reduce((sum, e) => sum + e.weight, 0);
            scores[phase] = Math.min(totalWeight, 1.0); // Cap at 1.0
        }
        return scores;
    }
    /**
     * Group evidence by the phase it suggests
     */
    groupEvidenceByPhase(evidence) {
        const grouped = {};
        for (const ev of evidence) {
            // Extract phase from source (e.g., "keyword: implement" -> IMPLEMENTATION)
            const phase = this.inferPhaseFromSource(ev.source);
            if (phase) {
                if (!grouped[phase]) {
                    grouped[phase] = [];
                }
                grouped[phase].push(ev);
            }
        }
        return grouped;
    }
    /**
     * Infer workflow phase from evidence source
     */
    inferPhaseFromSource(source) {
        const [type, value] = source.split(': ');
        if (type === 'keyword') {
            const mapping = KEYWORD_MAPPINGS.find(m => m.keywords.some(k => k.toLowerCase() === value.toLowerCase()));
            return mapping?.phase ?? null;
        }
        if (type === 'command') {
            const mapping = COMMAND_MAPPINGS.find(m => value.includes(m.commandPattern));
            return mapping?.phase ?? null;
        }
        if (type === 'hint') {
            return value;
        }
        const FILE_STATE_MAP = {
            'spec-empty': WorkflowPhase.SPEC_WRITING,
            'plan-missing': WorkflowPhase.PLAN_GENERATION,
            'plan-empty': WorkflowPhase.PLAN_GENERATION,
            'tasks-missing': WorkflowPhase.TASK_BREAKDOWN,
            'all-present': WorkflowPhase.IMPLEMENTATION
        };
        const STATUS_MAP = {
            'planning': WorkflowPhase.PLAN_GENERATION,
            'active': WorkflowPhase.IMPLEMENTATION,
            'completed': WorkflowPhase.COMPLETION
        };
        if (type === 'file-state') {
            return FILE_STATE_MAP[value] ?? null;
        }
        if (type === 'status') {
            return STATUS_MAP[value] ?? null;
        }
        return null;
    }
    /**
     * Generate command suggestion based on detected phase
     */
    generateSuggestion(phase, confidence) {
        if (confidence < this.config.confidenceThreshold) {
            return null;
        }
        const PHASE_SUGGESTIONS = {
            [WorkflowPhase.SPEC_WRITING]: { command: '/sw:increment', reason: 'Create new increment with spec.md template' },
            [WorkflowPhase.PLAN_GENERATION]: { command: '/sw:plan', reason: 'Generate implementation plan from spec.md' },
            [WorkflowPhase.TASK_BREAKDOWN]: { command: '/sw:plan', reason: 'Generate tasks.md with embedded test plans' },
            [WorkflowPhase.IMPLEMENTATION]: { command: '/sw:do', reason: 'Execute tasks from tasks.md' },
            [WorkflowPhase.TESTING]: { command: '/sw:test', reason: 'Run test suite and validate coverage' },
            [WorkflowPhase.DOCUMENTATION]: { command: '/sw:sync-docs', reason: 'Sync increment to living documentation' },
            [WorkflowPhase.REVIEW]: { command: '/sw:qa', reason: 'Run quality assessment on increment' },
            [WorkflowPhase.COMPLETION]: { command: '/sw:done', reason: 'Close increment and update status' },
            [WorkflowPhase.UNKNOWN]: null
        };
        return PHASE_SUGGESTIONS[phase] ?? null;
    }
}
//# sourceMappingURL=phase-detector.js.map