/**
 * InteractiveOrchestrator — 대화형 병렬 서브에이전트 오케스트레이터
 *
 * Goal을 입력받아 병렬 서브에이전트를 소환하고,
 * 각 에이전트에게 skills, hooks, MCP를 부여하며 총 관리합니다.
 *
 * Pipeline:
 *   Goal → Decompose → Assign Capabilities → Confirm → Spawn Parallel → Monitor → Merge
 */
import { EventEmitter } from "events";
import { assignSkills } from "./skill-assigner.js";
import { capabilityScopesFromRouting, mergeCapabilityScopes, } from "./capability-routing.js";
import { analyzeUserIntent } from "../goal/intake.js";
import { buildIntentFrame } from "../goal/intent-frame.js";
import { ParallelOrchestrator } from "./parallel-orchestrator.js";
import { createDag } from "./dag.js";
import { createRoutedRunState, routeRunState } from "./run-state.js";
// ─── InteractiveOrchestrator ──────────────────────────────────────
export class InteractiveOrchestrator extends EventEmitter {
    goal;
    options;
    state;
    dag;
    orchestrator;
    constructor(goal, options = {}) {
        super();
        this.goal = goal;
        this.options = options;
        const runId = options.runId ?? new Date().toISOString().replace(/[:.]/g, "-");
        this.state = {
            goal,
            phase: "analyzing",
            subAgents: [],
            runId,
            startedAt: new Date().toISOString(),
            progress: { completed: 0, total: 0, percentage: 0 },
        };
    }
    // ─── Main Pipeline ──────────────────────────────────────────
    async execute() {
        try {
            // Phase 1: Analyze goal
            this.setPhase("analyzing");
            const intent = analyzeUserIntent(this.goal.description);
            this.emit("log", `Intent: ${intent.taskType} (complexity: ${intent.complexity})`, "info");
            // Phase 2: Decompose into sub-agents
            this.setPhase("decomposing");
            const specs = this.decomposeGoal(intent);
            this.emit("log", `Decomposed into ${specs.length} sub-agents`, "info");
            // Phase 3: Assign capabilities
            this.setPhase("assigning");
            const enrichedSpecs = this.assignCapabilities(specs, intent);
            this.state.subAgents.push(...enrichedSpecs.map((spec) => ({
                spec,
                status: "pending",
            })));
            // Phase 4: Confirm with user
            this.setPhase("confirming");
            const confirmed = await this.confirmExecution(enrichedSpecs);
            if (!confirmed) {
                if (this.options.dryRun) {
                    this.state.phase = "completed";
                    this.state.completedAt = new Date().toISOString();
                    this.emit("log", "Dry run completed without execution", "info");
                    return this.state;
                }
                this.state.phase = "failed";
                this.state.error = "User cancelled orchestration";
                this.emit("log", "Orchestration cancelled by user", "warn");
                return this.state;
            }
            // Phase 5: Build DAG and spawn
            this.setPhase("spawning");
            this.dag = this.buildDag(enrichedSpecs, intent);
            this.emit("log", `Built DAG with ${this.dag.nodes.length} nodes`, "info");
            // Phase 6: Execute parallel
            this.setPhase("running");
            await this.executeParallel();
            // Phase 7: Merge results
            this.setPhase("merging");
            this.mergeResults();
            this.setPhase("completed");
            this.state.completedAt = new Date().toISOString();
            this.emit("log", "Execution finished; completion still requires diff + test + log artifacts plus a passing verify result.", "info");
        }
        catch (error) {
            this.state.phase = "failed";
            this.state.error = error instanceof Error ? error.message : String(error);
            this.state.completedAt = new Date().toISOString();
            this.emit("log", `Orchestration failed: ${this.state.error}`, "error");
        }
        return this.state;
    }
    // ─── Phase 2: Decompose Goal ────────────────────────────────
    decomposeGoal(intent) {
        const { description, maxWorkers = 4 } = this.goal;
        const taskType = intent.taskType;
        const roles = intent.requiredRoles ?? this.defaultRoles(taskType);
        const workerCount = Math.min(Math.max(2, intent.estimatedWorkers ?? 2), maxWorkers);
        const specs = [];
        const intentFrame = buildIntentFrame(description);
        const actionAtoms = intentFrame.actionAtoms;
        // Coordinator agent
        specs.push({
            id: "coordinator",
            name: `Coordinator (${taskType})`,
            role: "architect",
            skills: ["omk-plan-first", "omk-repo-explorer"],
            mcpServers: [],
            hooks: [],
            tools: [],
            task: `Coordinate and plan: ${description}`,
            dependsOn: [],
            timeoutMs: 60_000,
            maxRetries: 1,
            failurePolicy: "block",
            readOnly: true,
        });
        // Worker agents based on roles
        const workerRoles = roles.filter((r) => r !== "planner" && r !== "orchestrator" && r !== "architect");
        if (workerRoles.length === 0)
            workerRoles.push("coder");
        for (let i = 0; i < workerCount; i++) {
            const role = workerRoles[i % workerRoles.length];
            const atom = actionAtoms[(i + 1) % actionAtoms.length];
            const atomLabel = atom?.label ?? `task-${i + 1}`;
            specs.push({
                id: `worker-${i + 1}`,
                name: `Worker ${i + 1} (${role}): ${atomLabel}`,
                role: role,
                skills: this.defaultSkillsForRole(role),
                mcpServers: this.defaultMcpForRole(role),
                hooks: this.defaultHooksForRole(role),
                tools: this.defaultToolsForRole(role),
                task: atom?.verb
                    ? `${atom.verb} ${atom.object} — ${atom.doneCondition}`
                    : `Execute sub-task ${i + 1}: ${description}`,
                dependsOn: ["coordinator"],
                timeoutMs: 120_000,
                maxRetries: 1,
                failurePolicy: "skip",
            });
        }
        // Reviewer agent
        specs.push({
            id: "reviewer",
            name: "Reviewer: verify and merge outputs",
            role: "reviewer",
            skills: ["omk-code-review", "omk-quality-gate"],
            mcpServers: [],
            hooks: [],
            tools: [],
            task: `Review and verify all worker outputs for: ${description}`,
            dependsOn: specs.filter((s) => s.id.startsWith("worker-")).map((s) => s.id),
            timeoutMs: 60_000,
            maxRetries: 1,
            failurePolicy: "warn",
            readOnly: true,
        });
        return specs;
    }
    // ─── Phase 3: Assign Capabilities ───────────────────────────
    assignCapabilities(specs, _intent) {
        return specs.map((spec) => {
            // Use existing skill-assigner for additional capabilities
            const routing = {
                skills: [...spec.skills],
                mcpServers: [...spec.mcpServers],
                hooks: [...spec.hooks],
                tools: [...spec.tools],
                provider: spec.provider,
                providerModel: spec.model,
            };
            // Merge with auto-detected capabilities
            const autoAssigned = assignSkills({
                id: spec.id,
                name: spec.name,
                role: spec.role,
                routing,
            });
            const mergedScopes = mergeCapabilityScopes(autoAssigned, capabilityScopesFromRouting(routing));
            return {
                ...spec,
                skills: [...new Set([...spec.skills, ...mergedScopes.skills])],
                mcpServers: [...new Set([...spec.mcpServers, ...mergedScopes.mcpServers])],
                hooks: [...new Set([...spec.hooks, ...mergedScopes.hooks])],
                tools: [...new Set([...spec.tools, ...mergedScopes.tools])],
            };
        });
    }
    // ─── Phase 4: Confirm ───────────────────────────────────────
    async confirmExecution(specs) {
        if (this.options.autoConfirm)
            return true;
        if (this.options.onConfirm) {
            return this.options.onConfirm(specs);
        }
        // Default: emit and wait for external confirmation
        return new Promise((resolve) => {
            this.emit("confirm_required", specs);
            // Auto-confirm after emit if no listener handles it
            setTimeout(() => resolve(true), 100);
        });
    }
    // ─── Phase 5: Build DAG ─────────────────────────────────────
    buildDag(specs, _intent) {
        const nodes = specs.map((spec) => ({
            id: spec.id,
            name: spec.name,
            role: spec.role,
            dependsOn: [...spec.dependsOn],
            maxRetries: spec.maxRetries ?? 1,
            timeoutMs: spec.timeoutMs,
            failurePolicy: {
                retryable: (spec.maxRetries ?? 1) > 0,
                blockDependents: spec.failurePolicy === "block",
                skipOnFailure: spec.failurePolicy === "skip",
            },
            inputs: spec.dependsOn.map((dep) => ({
                name: `${dep} output`,
                ref: "state.json",
                from: dep,
            })),
            outputs: [{ name: `${spec.id} output`, gate: "none" }],
            routing: {
                skills: [...spec.skills],
                mcpServers: [...spec.mcpServers],
                hooks: [...spec.hooks],
                tools: [...spec.tools],
                provider: spec.provider ?? "auto",
                providerModel: spec.model,
                readOnly: spec.readOnly ?? false,
                assignedProviderCapabilities: this.capabilitiesForRole(spec.role),
                actionAtom: {
                    id: `atom-${spec.id}`,
                    label: spec.id,
                    verb: spec.role === "reviewer" ? "review" : spec.role === "researcher" ? "research" : "execute",
                    object: spec.task.slice(0, 80),
                    evidenceTarget: `${spec.id} output`,
                    doneCondition: `Task completed: ${spec.task.slice(0, 60)}`,
                    source: "runtime",
                },
            },
        }));
        return createDag({ nodes });
    }
    // ─── Phase 6: Execute Parallel ──────────────────────────────
    async executeParallel() {
        if (!this.dag)
            throw new Error("DAG not built");
        const maxWorkers = this.goal.maxWorkers ?? 4;
        const timeout = this.goal.timeoutMs ?? 600_000;
        const runId = this.state.runId;
        // Create run state
        const runState = createRoutedRunState({
            runId,
            startedAt: this.state.startedAt,
            nodes: this.dag.nodes.map((n) => ({
                id: n.id,
                name: n.name,
                role: n.role,
                dependsOn: n.dependsOn,
                maxRetries: n.maxRetries,
                timeoutMs: n.timeoutMs,
                failurePolicy: n.failurePolicy,
                inputs: n.inputs,
                outputs: n.outputs,
                routing: n.routing,
            })),
            workerCount: maxWorkers,
        });
        routeRunState(runState, maxWorkers);
        // Create and run orchestrator
        this.orchestrator = new ParallelOrchestrator({
            dag: this.dag,
            runId,
            maxWorkers,
            cwd: this.options.cwd ?? process.cwd(),
            timeout,
            onProgress: (state) => {
                this.state.progress = state.progress;
                this.emit("progress", this.state);
                // Update sub-agent states from worker states
                for (const worker of state.workers) {
                    const subAgent = this.state.subAgents.find((a) => a.spec.id === worker.nodeId);
                    if (subAgent) {
                        subAgent.status =
                            worker.status === "completed"
                                ? "done"
                                : worker.status === "running"
                                    ? "running"
                                    : worker.status === "failed"
                                        ? "failed"
                                        : "pending";
                    }
                }
            },
            onLog: (entry) => {
                const level = entry.level === "error"
                    ? "error"
                    : entry.level === "warn"
                        ? "warn"
                        : "info";
                this.emit("log", `[${entry.workerId}] ${entry.message}`, level);
            },
        });
        const result = await this.orchestrator.execute();
        // Update final states
        if (!result.success) {
            throw new Error(result.error ?? "Orchestration failed");
        }
    }
    // ─── Phase 7: Merge Results ─────────────────────────────────
    mergeResults() {
        const completed = this.state.subAgents.filter((a) => a.status === "done").length;
        const total = this.state.subAgents.length;
        this.state.progress = {
            completed,
            total,
            percentage: total > 0 ? (completed / total) * 100 : 0,
        };
    }
    // ─── Helpers ────────────────────────────────────────────────
    setPhase(phase) {
        this.state.phase = phase;
        this.emit("phase", phase);
    }
    defaultRoles(taskType) {
        const roleMap = {
            implement: ["coder", "coder", "reviewer"],
            bugfix: ["debugger", "coder", "reviewer"],
            refactor: ["coder", "coder", "reviewer"],
            explore: ["researcher", "explorer", "reviewer"],
            review: ["reviewer", "reviewer", "aggregator"],
            test: ["tester", "coder", "reviewer"],
            plan: ["planner", "researcher", "architect"],
            migrate: ["coder", "coder", "integrator"],
            security: ["security", "coder", "reviewer"],
            document: ["researcher", "coder", "reviewer"],
            general: ["coder", "researcher", "reviewer"],
        };
        return roleMap[taskType] ?? roleMap.general;
    }
    defaultSkillsForRole(role) {
        const skillMap = {
            coder: ["omk-plan-first", "karpathy-guidelines"],
            reviewer: ["omk-code-review", "omk-quality-gate"],
            tester: ["omk-test-debug-loop", "omk-quality-gate"],
            researcher: ["omk-repo-explorer", "omk-context-broker"],
            planner: ["omk-plan-first", "omk-repo-explorer"],
            explorer: ["omk-repo-explorer", "omk-context-broker"],
            security: ["omk-security-review", "omk-secret-guard"],
            architect: ["omk-plan-first", "omk-repo-explorer"],
            integrator: ["omk-quality-gate", "omk-git-commit-pr"],
            debugger: ["omk-test-debug-loop", "omk-troubleshooting"],
        };
        return skillMap[role] ?? skillMap.coder;
    }
    defaultMcpForRole(role) {
        const mcpMap = {
            coder: [],
            reviewer: [],
            tester: [],
            researcher: ["context7"],
            planner: [],
            explorer: ["context7"],
            security: [],
            architect: [],
            integrator: [],
            debugger: [],
        };
        return mcpMap[role] ?? [];
    }
    defaultHooksForRole(_role) {
        return []; // Hooks are project-specific, assigned via skill-assigner
    }
    defaultToolsForRole(role) {
        const toolMap = {
            coder: ["read", "write", "edit", "bash"],
            reviewer: ["read", "bash"],
            tester: ["read", "bash"],
            researcher: ["read", "grep", "glob"],
            planner: ["read", "grep", "glob"],
            explorer: ["read", "grep", "glob"],
            security: ["read", "grep", "bash"],
            architect: ["read", "grep", "glob"],
            integrator: ["read", "write", "bash"],
            debugger: ["read", "bash", "grep"],
        };
        return toolMap[role] ?? toolMap.coder;
    }
    capabilitiesForRole(role) {
        const capMap = {
            coder: ["write", "shell", "mcp"],
            reviewer: ["read", "review", "advisory"],
            tester: ["read", "shell", "review"],
            researcher: ["read", "advisory"],
            planner: ["read", "plan", "advisory"],
            explorer: ["read", "advisory"],
            security: ["read", "review", "shell"],
            architect: ["read", "plan", "write"],
            integrator: ["read", "write", "shell", "merge"],
            debugger: ["read", "shell", "write"],
        };
        return capMap[role] ?? capMap.coder;
    }
    // ─── Public Accessors ───────────────────────────────────────
    getState() {
        return { ...this.state };
    }
    getSubAgents() {
        return [...this.state.subAgents];
    }
    getPhase() {
        return this.state.phase;
    }
}
// ─── Factory ──────────────────────────────────────────────────────
export function createInteractiveOrchestrator(goal, options) {
    return new InteractiveOrchestrator(goal, options);
}
// ─── Utility: Format for display ──────────────────────────────────
export function formatSubAgentPlan(specs) {
    const lines = [
        "# Parallel Sub-Agent Plan\n",
        `Total agents: ${specs.length}\n`,
        "",
        "| ID | Role | Skills | MCP | Hooks | Task |",
        "|----|------|--------|-----|-------|------|",
    ];
    for (const spec of specs) {
        lines.push(`| ${spec.id} | ${spec.role} | ${spec.skills.join(", ") || "—"} | ${spec.mcpServers.join(", ") || "—"} | ${spec.hooks.join(", ") || "—"} | ${spec.task.slice(0, 50)} |`);
    }
    lines.push("");
    lines.push("## Dependency Graph\n");
    lines.push("```mermaid");
    lines.push("graph TD");
    for (const spec of specs) {
        if (spec.dependsOn.length === 0) {
            lines.push(`  ${spec.id}[${spec.name}]`);
        }
        else {
            for (const dep of spec.dependsOn) {
                lines.push(`  ${dep} --> ${spec.id}[${spec.name}]`);
            }
        }
    }
    lines.push("```");
    return lines.join("\n");
}
