/**
 * @fileoverview Orchestrator plan generation — converts goals into phased plans.
 *
 * Wraps PlanOrchestrator for AI-powered plan generation, then groups the
 * resulting PlanItems into sequential phases with team strategies and
 * verification criteria.
 *
 * Phase grouping algorithm:
 * 1. Topological sort by dependencies (Kahn's algorithm)
 * 2. Group into dependency layers
 * 3. Sub-group by TDD phase within layers
 * 4. Merge small adjacent phases
 * 5. Assign team strategies based on parallelism potential
 *
 * Key exports:
 * - `OrchestratorPlanner` class — plan generation + phase grouping
 *
 * @dependencies plan-orchestrator (AI plan generation), types (OrchestratorPlan, PlanItem)
 * @consumedby orchestrator-loop
 *
 * @module orchestrator-planner
 */
import { v4 as uuidv4 } from 'uuid';
import { PlanOrchestrator } from './plan-orchestrator.js';
// ═══════════════════════════════════════════════════════════════
// Constants
// ═══════════════════════════════════════════════════════════════
/** Maximum number of phases (prevents runaway plans) */
const MAX_PHASES = 10;
/** Maximum total tasks across all phases */
const MAX_TOTAL_TASKS = 50;
/** Default task timeout (10 minutes) */
const DEFAULT_TASK_TIMEOUT_MS = 10 * 60 * 1000;
/** Minimum tasks in a phase before it gets merged with adjacent */
const MIN_PHASE_TASKS = 2;
/** TDD phase ordering for grouping */
const TDD_PHASE_ORDER = {
    setup: 0,
    test: 1,
    impl: 2,
    verify: 3,
    review: 4,
};
// ═══════════════════════════════════════════════════════════════
// OrchestratorPlanner
// ═══════════════════════════════════════════════════════════════
export class OrchestratorPlanner {
    mux;
    workingDir;
    config;
    orchestrator = null;
    constructor(mux, workingDir, config) {
        this.mux = mux;
        this.workingDir = workingDir;
        this.config = config;
    }
    /**
     * Generate a phased plan from a user goal.
     *
     * Uses PlanOrchestrator for AI plan generation, then groups results into phases.
     */
    async generatePlan(goal, onProgress) {
        const startTime = Date.now();
        // Create a PlanOrchestrator for this plan generation
        this.orchestrator = new PlanOrchestrator(this.mux, this.workingDir, undefined, {
            defaultModel: this.config.plannerModel,
        });
        try {
            onProgress?.('planning', 'Generating detailed plan...');
            const result = await this.orchestrator.generateDetailedPlan(goal, onProgress);
            if (!result.success || !result.items || result.items.length === 0) {
                throw new Error(result.error || 'Plan generation returned no items');
            }
            // Cap total tasks
            const items = result.items.slice(0, MAX_TOTAL_TASKS);
            onProgress?.('grouping', 'Organizing plan into phases...');
            // Group items into phases
            const phases = this.groupIntoPhases(items, goal);
            // Assign team strategies
            this.assignTeamStrategies(phases);
            // Generate unique completion phrases
            this.generateCompletionPhrases(phases);
            const plan = {
                id: uuidv4(),
                goal,
                createdAt: Date.now(),
                phases,
                metadata: {
                    totalTasks: phases.reduce((sum, p) => sum + p.tasks.length, 0),
                    estimatedComplexity: this.estimateComplexity(items),
                    modelUsed: this.config.plannerModel,
                    planDurationMs: Date.now() - startTime,
                },
            };
            return plan;
        }
        finally {
            this.orchestrator = null;
        }
    }
    /** Cancel in-progress plan generation. */
    async cancel() {
        if (this.orchestrator) {
            await this.orchestrator.cancel();
            this.orchestrator = null;
        }
    }
    // ═══════════════════════════════════════════════════════════════
    // Phase Grouping
    // ═══════════════════════════════════════════════════════════════
    /**
     * Group PlanItems into sequential phases.
     *
     * Algorithm:
     * 1. Build dependency graph and assign IDs to items without them
     * 2. Topological sort into dependency layers (Kahn's algorithm)
     * 3. Sub-group within each layer by TDD phase
     * 4. Merge small phases with their neighbors
     */
    groupIntoPhases(items, _goal) {
        // Ensure all items have IDs
        const indexedItems = items.map((item, i) => ({
            ...item,
            id: item.id || `task-${i}`,
        }));
        // Build adjacency and in-degree for Kahn's algorithm
        const idSet = new Set(indexedItems.map((item) => item.id));
        const inDegree = new Map();
        const dependents = new Map(); // id → items that depend on it
        for (const item of indexedItems) {
            inDegree.set(item.id, 0);
            dependents.set(item.id, []);
        }
        for (const item of indexedItems) {
            const deps = (item.dependencies || []).filter((d) => idSet.has(d));
            inDegree.set(item.id, deps.length);
            for (const dep of deps) {
                dependents.get(dep).push(item.id);
            }
        }
        // Kahn's algorithm — produce dependency layers
        const layers = [];
        const remaining = new Set(indexedItems.map((item) => item.id));
        while (remaining.size > 0) {
            // Find items with no remaining dependencies (in-degree 0)
            const layer = [];
            for (const id of remaining) {
                if (inDegree.get(id) === 0) {
                    layer.push(indexedItems.find((item) => item.id === id));
                }
            }
            if (layer.length === 0) {
                // Circular dependency — add all remaining items as a single layer
                for (const id of remaining) {
                    layer.push(indexedItems.find((item) => item.id === id));
                }
            }
            layers.push(layer);
            // Remove this layer's items and update in-degrees
            for (const item of layer) {
                remaining.delete(item.id);
                for (const dep of dependents.get(item.id) || []) {
                    if (remaining.has(dep)) {
                        inDegree.set(dep, Math.max(0, inDegree.get(dep) - 1));
                    }
                }
            }
        }
        // Sub-group each layer by TDD phase
        const rawPhases = [];
        for (const layer of layers) {
            const byPhase = new Map();
            for (const item of layer) {
                const phase = item.tddPhase || 'impl';
                if (!byPhase.has(phase))
                    byPhase.set(phase, []);
                byPhase.get(phase).push(item);
            }
            // Sort sub-groups by TDD phase order
            const sorted = [...byPhase.entries()].sort(([a], [b]) => (TDD_PHASE_ORDER[a] ?? 2) - (TDD_PHASE_ORDER[b] ?? 2));
            for (const [, items] of sorted) {
                rawPhases.push(items);
            }
        }
        // Merge small phases with their previous neighbor
        const mergedPhases = [];
        for (const phase of rawPhases) {
            if (mergedPhases.length > 0 && phase.length < MIN_PHASE_TASKS) {
                const prev = mergedPhases[mergedPhases.length - 1];
                if (prev.length < MIN_PHASE_TASKS) {
                    // Merge with previous
                    prev.push(...phase);
                    continue;
                }
            }
            mergedPhases.push([...phase]);
        }
        // Cap at MAX_PHASES by merging tail phases
        while (mergedPhases.length > MAX_PHASES) {
            const last = mergedPhases.pop();
            mergedPhases[mergedPhases.length - 1].push(...last);
        }
        // Convert to OrchestratorPhase objects
        return mergedPhases.map((phaseItems, index) => this.createPhase(phaseItems, index));
    }
    createPhase(items, order) {
        // Derive phase name from TDD phases and priorities
        const tddPhases = [...new Set(items.map((i) => i.tddPhase).filter(Boolean))];
        const name = this.generatePhaseName(items, tddPhases, order);
        const description = items.map((i) => i.content).join('; ');
        const tasks = items.map((item, i) => ({
            id: `phase-${order + 1}-task-${i + 1}`,
            phaseId: `phase-${order + 1}`,
            prompt: item.content,
            status: 'pending',
            assignedSessionId: null,
            queueTaskId: null,
            parallel: items.length > 1, // Tasks within a phase are parallel by default
            completionPhrase: '', // Assigned later
            timeoutMs: DEFAULT_TASK_TIMEOUT_MS,
            startedAt: null,
            completedAt: null,
            error: null,
            retries: 0,
        }));
        // Extract verification criteria and test commands from items
        const verificationCriteria = items
            .map((i) => i.verificationCriteria)
            .filter((v) => v != null && v.length > 0);
        const testCommands = items.map((i) => i.testCommand).filter((t) => t != null && t.length > 0);
        return {
            id: `phase-${order + 1}`,
            name,
            description,
            order,
            status: 'pending',
            tasks,
            verificationCriteria,
            testCommands,
            maxAttempts: this.config.maxPhaseRetries,
            attempts: 0,
            startedAt: null,
            completedAt: null,
            durationMs: null,
            teamStrategy: { type: 'single' }, // Assigned later
        };
    }
    generatePhaseName(items, tddPhases, order) {
        // Try to create a meaningful name based on content
        const priorities = [...new Set(items.map((i) => i.priority).filter(Boolean))];
        if (tddPhases.length === 1) {
            const phaseNames = {
                setup: 'Setup & Configuration',
                test: 'Test Definition',
                impl: 'Implementation',
                verify: 'Verification',
                review: 'Review & Polish',
            };
            return `Phase ${order + 1}: ${phaseNames[tddPhases[0]]}`;
        }
        if (priorities.includes('P0') && priorities.length === 1) {
            return `Phase ${order + 1}: Critical Foundation`;
        }
        return `Phase ${order + 1}: ${items.length > 1 ? 'Parallel Tasks' : items[0].content.slice(0, 50)}`;
    }
    // ═══════════════════════════════════════════════════════════════
    // Team Strategy Assignment
    // ═══════════════════════════════════════════════════════════════
    assignTeamStrategies(phases) {
        for (const phase of phases) {
            phase.teamStrategy = this.computeTeamStrategy(phase);
        }
    }
    computeTeamStrategy(phase) {
        const taskCount = phase.tasks.length;
        const parallelTasks = phase.tasks.filter((t) => t.parallel).length;
        // Single task or no parallel potential → single session
        if (taskCount <= 2 || parallelTasks <= 1) {
            return { type: 'single' };
        }
        // If team agents are disabled, use parallel sessions instead
        if (!this.config.enableTeamAgents) {
            return {
                type: 'parallel',
                maxSessions: Math.min(parallelTasks, this.config.maxParallelSessions),
            };
        }
        // 4+ parallel tasks with team agents enabled → team mode
        if (parallelTasks >= 4) {
            return {
                type: 'team',
                config: {
                    leadPrompt: this.buildTeamLeadPrompt(phase),
                    suggestedTeammates: phase.tasks.slice(0, 4).map((t) => `Specialist for: ${t.prompt.slice(0, 80)}`),
                    maxTeammates: Math.min(parallelTasks, 4),
                },
            };
        }
        // 3 parallel tasks → parallel sessions
        return {
            type: 'parallel',
            maxSessions: Math.min(parallelTasks, this.config.maxParallelSessions),
        };
    }
    buildTeamLeadPrompt(phase) {
        const taskList = phase.tasks.map((t, i) => `${i + 1}. ${t.prompt}`).join('\n');
        return [
            `You are the team lead for "${phase.name}".`,
            `Create teammates and delegate the following tasks for parallel execution:`,
            '',
            taskList,
            '',
            `Each teammate should focus on one task area.`,
            `When all tasks are complete, verify the results and output: <promise>${phase.id.toUpperCase()}_COMPLETE</promise>`,
        ].join('\n');
    }
    // ═══════════════════════════════════════════════════════════════
    // Completion Phrases
    // ═══════════════════════════════════════════════════════════════
    generateCompletionPhrases(phases) {
        for (const phase of phases) {
            for (const task of phase.tasks) {
                // Generate a unique, deterministic completion phrase per task
                task.completionPhrase = `ORCH_P${phase.order + 1}_T${phase.tasks.indexOf(task) + 1}`;
            }
        }
    }
    // ═══════════════════════════════════════════════════════════════
    // Helpers
    // ═══════════════════════════════════════════════════════════════
    estimateComplexity(items) {
        const total = items.length;
        const highComplexity = items.filter((i) => i.complexity === 'high').length;
        const p0Count = items.filter((i) => i.priority === 'P0').length;
        if (total > 20 || highComplexity > 5 || p0Count > 8)
            return 'high';
        if (total > 10 || highComplexity > 2 || p0Count > 4)
            return 'medium';
        return 'low';
    }
}
//# sourceMappingURL=orchestrator-planner.js.map