/**
 * Implement Node — Phase-Level Orchestrator
 *
 * Reads plan.yaml and tasks.yaml, then executes each phase by building
 * a focused implementation prompt and calling the executor. Parallel
 * phases spawn concurrent executor calls. Progress is tracked in
 * feature.yaml between phases.
 */
import yaml from 'js-yaml';
import { writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { interrupt, isGraphBubbleUp } from '@langchain/langgraph';
import { createNodeLogger, readSpecFile, buildExecutorOptions, shouldInterrupt, } from './node-helpers.js';
import { reportNodeStart } from '../heartbeat.js';
import { buildImplementPhasePrompt, } from './prompts/implement.prompt.js';
/**
 * Update feature.yaml with current implementation progress.
 * Silently no-ops if the file is missing or unparseable.
 */
function updateFeatureProgress(specDir, completedCount, totalCount, currentPhase, currentTaskId, log) {
    try {
        const content = readSpecFile(specDir, 'feature.yaml');
        if (!content)
            return;
        const data = yaml.load(content);
        if (!data || typeof data !== 'object')
            return;
        const status = (data.status ?? {});
        data.status = {
            ...status,
            phase: currentPhase,
            progress: {
                completed: completedCount,
                total: totalCount,
                percentage: totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0,
            },
            currentTask: currentTaskId,
            lastUpdated: new Date().toISOString(),
            lastUpdatedBy: 'feature-agent:implement',
        };
        writeFileSync(join(specDir, 'feature.yaml'), yaml.dump(data, { indent: 2, lineWidth: -1 }), 'utf-8');
    }
    catch (err) {
        log.error(`Failed to update feature.yaml: ${err instanceof Error ? err.message : String(err)}`);
    }
}
export function createImplementNode(executor) {
    const log = createNodeLogger('implement');
    return async (state) => {
        log.info('Starting implementation phase orchestration');
        reportNodeStart('implement');
        const startTime = Date.now();
        const messages = [];
        try {
            // --- Parse plan & tasks ---
            const planContent = readSpecFile(state.specDir, 'plan.yaml');
            const tasksContent = readSpecFile(state.specDir, 'tasks.yaml');
            if (!planContent || !tasksContent) {
                const msg = 'Missing plan.yaml or tasks.yaml — cannot implement';
                log.error(msg);
                return { currentNode: 'implement', error: msg, messages: [`[implement] Error: ${msg}`] };
            }
            const planData = yaml.load(planContent);
            const tasksData = yaml.load(tasksContent);
            if (!planData?.phases?.length || !tasksData?.tasks?.length) {
                const msg = 'Empty phases or tasks — nothing to implement';
                log.error(msg);
                return { currentNode: 'implement', error: msg, messages: [`[implement] Error: ${msg}`] };
            }
            const taskMap = new Map(tasksData.tasks.map((t) => [t.id, t]));
            const totalTasks = tasksData.tasks.length;
            let completedTasks = 0;
            const totalPhases = planData.phases.length;
            // --- Execute phases in order ---
            for (let i = 0; i < totalPhases; i++) {
                const phase = planData.phases[i];
                const isLastPhase = i === totalPhases - 1;
                // Resolve tasks: use explicit taskIds if present, otherwise match by phaseId
                const phaseTasks = phase.taskIds?.length
                    ? phase.taskIds
                        .map((id) => taskMap.get(id))
                        .filter((t) => t !== undefined)
                    : tasksData.tasks.filter((t) => t.phaseId === phase.id);
                if (phaseTasks.length === 0) {
                    log.info(`Phase ${phase.id} "${phase.name}" — no tasks, skipping`);
                    continue;
                }
                log.info(`Phase ${phase.id} "${phase.name}" — ${phaseTasks.length} task(s), parallel: ${phase.parallel}`);
                updateFeatureProgress(state.specDir, completedTasks, totalTasks, `implementing-${phase.id}`, phaseTasks[0].id, log);
                const options = buildExecutorOptions(state);
                const promptContext = { isLastPhase, phaseIndex: i, totalPhases };
                if (phase.parallel && phaseTasks.length > 1) {
                    // Parallel: one executor call per task
                    log.info(`Spawning ${phaseTasks.length} parallel executor calls`);
                    const results = await Promise.all(phaseTasks.map((task) => {
                        const singleTaskPhase = {
                            ...phase,
                            taskIds: [task.id],
                        };
                        const prompt = buildImplementPhasePrompt(state, singleTaskPhase, [task], promptContext);
                        log.info(`  [parallel] Task ${task.id}: "${task.title}" — ${prompt.length} chars`);
                        return executor.execute(prompt, options);
                    }));
                    for (let j = 0; j < results.length; j++) {
                        log.info(`  [parallel] Task ${phaseTasks[j].id} complete (${results[j].result.length} chars)`);
                    }
                }
                else {
                    // Sequential: single executor call with all phase tasks
                    const prompt = buildImplementPhasePrompt(state, phase, phaseTasks, promptContext);
                    log.info(`Executing phase prompt — ${prompt.length} chars`);
                    const result = await executor.execute(prompt, options);
                    log.info(`Phase complete (${result.result.length} chars)`);
                }
                completedTasks += phaseTasks.length;
                const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
                messages.push(`[implement] Phase ${phase.id} "${phase.name}" — ${phaseTasks.length} task(s) done (${elapsed}s)`);
                updateFeatureProgress(state.specDir, completedTasks, totalTasks, `implementing-${phase.id}`, null, log);
            }
            // --- Final ---
            const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
            log.info(`All phases complete — ${completedTasks}/${totalTasks} tasks (${elapsed}s)`);
            updateFeatureProgress(state.specDir, totalTasks, totalTasks, 'implementation-complete', null, log);
            messages.push(`[implement] Complete: ${totalTasks} tasks across ${totalPhases} phases (${elapsed}s)`);
            if (shouldInterrupt('implement', state.approvalMode)) {
                log.info('Interrupting for human approval');
                interrupt({
                    node: 'implement',
                    message: `Implementation complete: ${totalTasks} tasks across ${totalPhases} phases. Approve to finish.`,
                });
            }
            return { currentNode: 'implement', messages };
        }
        catch (err) {
            if (isGraphBubbleUp(err))
                throw err;
            const message = err instanceof Error ? err.message : String(err);
            const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
            log.error(`${message} (after ${elapsed}s)`);
            return {
                currentNode: 'implement',
                error: message,
                messages: [...messages, `[implement] Error: ${message}`],
            };
        }
    };
}
