import fs from 'fs';
import path from 'path';
import { spawn } from 'child_process';
import { loadSquad, findSquadDir } from '../../../core/squad-loader.js';
import { createInitialState, readState, writeState, updateAgentStatus, updateStep, setHandoff, setSquadStatus, HANDOFF_DELAY_MS, } from '../../../core/state-manager.js';
import { loadSkills } from '../../../core/skills-loader.js';
import { getCoreAsset } from '../utils/config.js';
import { runWithProvider } from '../runners/provider-runner.js';
import { estimateCost, formatCost } from '../runners/cost-tracker.js';
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
function getNextVersionDir(squadDir) {
    const outputBase = path.join(squadDir, 'output');
    if (!fs.existsSync(outputBase)) {
        fs.mkdirSync(outputBase, { recursive: true });
        const v1 = path.join(outputBase, 'v1');
        fs.mkdirSync(v1);
        return v1;
    }
    const existing = fs.readdirSync(outputBase)
        .filter(d => d.startsWith('v'))
        .map(d => parseInt(d.slice(1)))
        .filter(n => !isNaN(n))
        .sort((a, b) => b - a);
    const next = (existing[0] || 0) + 1;
    const dir = path.join(outputBase, `v${next}`);
    fs.mkdirSync(dir, { recursive: true });
    return dir;
}
function buildAgentPrompt(config, step, stepIndex, squadDir, skills, runnerPrompt) {
    // Load company context (user data — always from cwd)
    const companyPath = path.join(process.cwd(), '_expxagents', '_memory', 'company.md');
    let companyContext = '';
    if (fs.existsSync(companyPath)) {
        companyContext = fs.readFileSync(companyPath, 'utf-8');
    }
    // Load squad memory
    const memoryPath = path.join(squadDir, '_memory', 'memories.md');
    let squadMemory = '';
    if (fs.existsSync(memoryPath)) {
        squadMemory = fs.readFileSync(memoryPath, 'utf-8');
    }
    // Load best-practice if format specified (from package assets)
    let bestPractice = '';
    const format = config.squad.format;
    if (format) {
        const bpPath = getCoreAsset(`best-practices/${format}.md`);
        if (fs.existsSync(bpPath)) {
            bestPractice = fs.readFileSync(bpPath, 'utf-8');
        }
    }
    const agent = config.squad.agents.find(a => a.id === step.agent);
    let agentInstructions = '';
    const agentPromptPath = path.join(squadDir, agent.prompt);
    if (fs.existsSync(agentPromptPath)) {
        agentInstructions = fs.readFileSync(agentPromptPath, 'utf-8');
    }
    let previousOutput = '';
    if (step.deliverFrom) {
        const prevStepIdx = config.squad.pipeline.steps.findIndex(s => s.agent === step.deliverFrom);
        if (prevStepIdx !== -1) {
            const prevOutputPath = path.join(squadDir, 'output', `step-${String(prevStepIdx + 1).padStart(2, '0')}.md`);
            if (fs.existsSync(prevOutputPath)) {
                previousOutput = fs.readFileSync(prevOutputPath, 'utf-8');
            }
        }
    }
    const squadSkills = config.squad.skills || [];
    const skillInstructions = skills
        .filter(s => squadSkills.includes(s.name))
        .map(s => `### Skill: ${s.name}\n${s.instructions}`)
        .join('\n\n');
    const parts = [
        runnerPrompt,
        '',
        `## Current Task`,
        `- **Squad:** ${config.squad.name} (${config.squad.code})`,
        `- **Agent:** ${agent.name} (${agent.id})`,
        `- **Step ${stepIndex}/${config.squad.pipeline.steps.length}:** ${step.label}`,
        '',
    ];
    if (agentInstructions) {
        parts.push('## Agent Instructions', '', agentInstructions, '');
    }
    if (previousOutput) {
        parts.push(`## Input from ${step.deliverFrom}`, '', previousOutput, '');
    }
    if (skillInstructions) {
        parts.push('## Available Skills', '', skillInstructions, '');
    }
    if (companyContext && !companyContext.includes('NOT CONFIGURED')) {
        parts.push('## Company Context', '', companyContext, '');
    }
    if (squadMemory) {
        parts.push('## Squad Memory (past learnings)', '', squadMemory, '');
    }
    if (bestPractice) {
        parts.push('## Format Best Practices', '', bestPractice, '');
    }
    parts.push('## Output', '', `Write your complete output below. It will be saved to output/step-${String(stepIndex).padStart(2, '0')}.md`);
    return parts.join('\n');
}
function spawnClaudeCode(prompt, cwd) {
    return new Promise((resolve, reject) => {
        const child = spawn('claude', ['-p', '--verbose', prompt], {
            cwd,
            stdio: ['pipe', 'pipe', 'pipe'],
        });
        let stdout = '';
        let stderr = '';
        child.stdout?.on('data', (data) => {
            const chunk = data.toString();
            stdout += chunk;
            process.stdout.write(chunk);
        });
        child.stderr?.on('data', (data) => {
            stderr += data.toString();
        });
        child.on('error', (err) => {
            reject(new Error(`Failed to spawn Claude Code: ${err.message}`));
        });
        child.on('exit', (code) => {
            if (code === 0) {
                resolve(stdout);
            }
            else {
                reject(new Error(`Claude Code exited with code ${code}: ${stderr}`));
            }
        });
    });
}
export async function runCommand(name) {
    const cwd = process.cwd();
    const squadsDir = path.join(cwd, 'squads');
    const squadDir = findSquadDir(squadsDir, name);
    const skillsDir = path.join(cwd, 'skills');
    if (!squadDir) {
        console.error(`Squad "${name}" not found. Run \`expxagents list\` to see available squads.`);
        process.exit(1);
    }
    let config;
    try {
        config = loadSquad(squadDir);
    }
    catch (err) {
        console.error(`Failed to load squad config: ${err.message}`);
        process.exit(1);
    }
    const skills = loadSkills(skillsDir);
    const runnerPromptPath = getCoreAsset('runner.pipeline.md');
    let runnerPrompt = '';
    if (fs.existsSync(runnerPromptPath)) {
        runnerPrompt = fs.readFileSync(runnerPromptPath, 'utf-8');
    }
    const existingState = readState(squadDir);
    if (existingState && existingState.status === 'running') {
        console.error(`Squad "${name}" is already running. Use \`expxagents stop ${name}\` first.`);
        process.exit(1);
    }
    const outputDir = getNextVersionDir(squadDir);
    const version = path.basename(outputDir).slice(1); // e.g. "v3" -> "3"
    let state = createInitialState(config);
    state = setSquadStatus(state, 'running');
    state = { ...state, startedAt: new Date().toISOString() };
    writeState(squadDir, state);
    const spendControl = config.squad.spendControl;
    let totalCost = 0;
    let spendLimitHit = false;
    if (spendControl?.maxCostPerRun) {
        console.log(`\nRunning squad "${config.squad.name}" (${config.squad.code})`);
        console.log(`Pipeline: ${config.squad.pipeline.steps.length} steps | Spend limit: ${formatCost(spendControl.maxCostPerRun)} (${spendControl.mode ?? 'graceful'})\n`);
    }
    else {
        console.log(`\nRunning squad "${config.squad.name}" (${config.squad.code})`);
        console.log(`Pipeline: ${config.squad.pipeline.steps.length} steps\n`);
    }
    const steps = config.squad.pipeline.steps;
    for (let i = 0; i < steps.length; i++) {
        const step = steps[i];
        const stepNumber = i + 1;
        const agent = config.squad.agents.find(a => a.id === step.agent);
        // Spend control — check before running the step
        if (spendControl?.maxCostPerRun && totalCost >= spendControl.maxCostPerRun) {
            if (spendControl.mode === 'strict') {
                console.error(`\n[spend-control] Limit ${formatCost(spendControl.maxCostPerRun)} reached (used ${formatCost(totalCost)}). Halting (strict mode).`);
                state = setSquadStatus(state, 'idle');
                writeState(squadDir, state);
                process.exit(1);
            }
            else if (!spendLimitHit) {
                spendLimitHit = true;
                console.warn(`\n[spend-control] ⚠ Limit ${formatCost(spendControl.maxCostPerRun)} reached (used ${formatCost(totalCost)}). Continuing in graceful mode.`);
            }
        }
        console.log(`--- Step ${stepNumber}/${steps.length}: ${step.label} (${agent.name}) ---\n`);
        state = updateAgentStatus(state, step.agent, 'working');
        state = updateStep(state, stepNumber, step.label);
        writeState(squadDir, state);
        const prompt = buildAgentPrompt(config, step, stepNumber, squadDir, skills, runnerPrompt);
        let output;
        try {
            output = await runWithProvider(prompt, agent, squadDir, spawnClaudeCode);
        }
        catch (err) {
            console.error(`\nStep ${stepNumber} failed: ${err.message}`);
            state = updateAgentStatus(state, step.agent, 'idle');
            state = setSquadStatus(state, 'idle');
            writeState(squadDir, state);
            process.exit(1);
        }
        // Track estimated cost for this step
        const stepCost = estimateCost(prompt, output, agent.model);
        totalCost += stepCost;
        if (spendControl?.maxCostPerRun) {
            console.log(`[spend-control] Step cost: ${formatCost(stepCost)} | Run total: ${formatCost(totalCost)} / ${formatCost(spendControl.maxCostPerRun)}`);
        }
        const outputPath = path.join(outputDir, `step-${String(stepNumber).padStart(2, '0')}.md`);
        fs.writeFileSync(outputPath, output, 'utf-8');
        const outputLines = output.trim().split('\n').filter(l => l.trim().length > 0);
        const agentMessage = outputLines[outputLines.length - 1]?.trim() || `Completed ${step.label}`;
        const agentIdx = state.agents.findIndex(a => a.id === step.agent);
        state = {
            ...state,
            agents: state.agents.map((a, idx) => idx === agentIdx ? { ...a, message: agentMessage } : a),
        };
        const nextStep = steps[i + 1];
        const hasHandoff = nextStep && nextStep.deliverFrom === step.agent;
        if (hasHandoff) {
            state = updateAgentStatus(state, step.agent, 'delivering');
            state = setHandoff(state, step.agent, nextStep.agent, agentMessage);
            writeState(squadDir, state);
            console.log(`\nHandoff: ${agent.name} -> ${config.squad.agents.find(a => a.id === nextStep.agent).name}`);
            console.log(`Message: ${agentMessage}\n`);
            await delay(HANDOFF_DELAY_MS);
            state = updateAgentStatus(state, step.agent, 'done');
            writeState(squadDir, state);
        }
        else {
            state = updateAgentStatus(state, step.agent, 'done');
            writeState(squadDir, state);
        }
        console.log(`\nStep ${stepNumber} completed.\n`);
    }
    // Update squad memory after pipeline completion
    const memoryPath = path.join(squadDir, '_memory', 'memories.md');
    if (fs.existsSync(path.dirname(memoryPath))) {
        const timestamp = new Date().toISOString();
        const entry = `\n## Run ${timestamp}\n\n- Steps completed: ${steps.length}\n- Output: output/v${version}/\n`;
        fs.appendFileSync(memoryPath, entry, 'utf-8');
    }
    state = setSquadStatus(state, 'completed');
    state = updateStep(state, steps.length, 'Completed');
    writeState(squadDir, state);
    console.log(`\nSquad "${config.squad.name}" pipeline completed!`);
    console.log(`Output files saved to squads/${name}/output/v${version}/`);
    if (totalCost > 0) {
        console.log(`Estimated run cost: ${formatCost(totalCost)}`);
    }
}
