import { ParallelLiveRenderer, renderCompactParallelFrame } from "../../orchestration/parallel-ui.js";
import { omkCliHero, style, status, label, bullet } from "../../util/theme.js";
import { formatOmkVersionFooter } from "../../util/version.js";
import { captureTerminalInputState, restoreTerminalInputState } from "../../util/terminal-input.js";
import { createHarnessTaskRunner } from "../../harness/create-harness-task-runner.js";
import { executeHarnessRun } from "../../harness/execute-harness-run.js";
import { getActiveRuntimePreset } from "../../util/resource-profile.js";
import { createOmkSessionEnv } from "../../util/session.js";
import { t } from "../../util/i18n.js";
import readline from "readline";
import { isCapabilityAgentNode } from "../../orchestration/capability-agents.js";
import { renderActionDigest } from "../../goal/intent-frame.js";
export function normalizeWorkerCount(value, fallback) {
    const effective = (value ?? process.env.OMK_WORKERS)?.trim();
    if (!effective || effective === "auto")
        return fallback;
    if (!/^\d+$/.test(effective))
        return fallback;
    const parsed = Number(effective);
    if (!Number.isInteger(parsed) || parsed < 1)
        return fallback;
    return Math.min(parsed, 6);
}
function buildWorkerLabels(state) {
    const labels = {};
    const withProvider = (node, base) => {
        const provider = node.routing?.assignedProvider ?? node.routing?.provider;
        return provider && provider !== "auto" ? `${base}@${provider}` : base;
    };
    const coordinator = state.nodes.find((n) => n.id === "root-coordinator" || n.id === "architect");
    if (coordinator) {
        labels[coordinator.id] = withProvider(coordinator, coordinator.role === "architect" ? "architect" : "planner");
    }
    const reviewer = state.nodes.find((n) => n.id === "review-merge" || n.role === "reviewer" || n.role === "aggregator");
    if (reviewer) {
        labels[reviewer.id] = withProvider(reviewer, reviewer.role === "aggregator" ? "aggregator" : "reviewer");
    }
    const security = state.nodes.find((n) => n.id === "security-audit");
    if (security) {
        labels[security.id] = withProvider(security, "security");
    }
    const design = state.nodes.find((n) => n.id === "design-review");
    if (design) {
        labels[design.id] = withProvider(design, "design");
    }
    const qa = state.nodes.find((n) => n.id === "quality-check");
    if (qa) {
        labels[qa.id] = withProvider(qa, qa.role === "tester" ? "tester" : "qa");
    }
    const workers = state.nodes.filter((n) => n.id.startsWith("worker-"));
    const roleCounts = {};
    workers.forEach((n) => {
        const role = n.role ?? "coder";
        roleCounts[role] = (roleCounts[role] ?? 0) + 1;
        if (!labels[n.id]) {
            labels[n.id] = withProvider(n, `${role}-${roleCounts[role]}`);
        }
    });
    for (const node of state.nodes.filter((n) => n.id.startsWith("deepseek-"))) {
        labels[node.id] = node.routing?.providerModelTier
            ? `deepseek-${node.routing.providerModelTier}`
            : "deepseek";
    }
    for (const node of state.nodes.filter((n) => isCapabilityAgentNode(n))) {
        labels[node.id] = node.routing?.routeSource ? `capability-${node.routing.routeSource}` : "capability";
    }
    return labels;
}
function logRunStateTransitions(stateSnapshot, previousStatuses) {
    for (const node of stateSnapshot.nodes) {
        const previous = previousStatuses.get(node.id);
        previousStatuses.set(node.id, node.status);
        if (previous === node.status)
            continue;
        if (previous === undefined && node.status === "pending")
            continue;
        console.log(renderNodeTransition(node.id, node.name, node.status));
    }
}
function renderNodeTransition(id, name, nodeStatus) {
    const text = `${id}: ${name}`;
    if (nodeStatus === "running")
        return bullet(`running  ${text}`, "purple");
    if (nodeStatus === "done")
        return bullet(`done     ${text}`, "mint");
    if (nodeStatus === "failed")
        return bullet(`failed   ${text}`, "pink");
    if (nodeStatus === "blocked")
        return bullet(`blocked  ${text}`, "skin");
    return bullet(`${nodeStatus}  ${text}`, "blue");
}
export async function executeParallelRun(input) {
    const { runId, dag, routedState, statePath, options, root, runDir, resources, effectiveGoal, workerCount, approvalPolicy, providerPolicy, modelArg, mcpScope, agentFile, promptText, intentFrame, deepseekPromptPrefix, } = input;
    const abortController = new AbortController();
    if (options.signal) {
        options.signal.addEventListener("abort", () => abortController.abort(), { once: true });
    }
    let shuttingDown = false;
    let forceExitTimer;
    function handleSignal() {
        if (shuttingDown) {
            if (options.noPause !== true) {
                process.exit(1);
            }
            return;
        }
        shuttingDown = true;
        abortController.abort();
        // Force exit if graceful shutdown hangs (e.g. long-running node)
        // In programmatic mode (noPause=true) we skip force-exit so callers can handle abort.
        if (options.noPause !== true) {
            forceExitTimer = setTimeout(() => process.exit(1), 5000);
            forceExitTimer.unref?.();
        }
    }
    process.once("SIGINT", handleSignal);
    process.once("SIGTERM", handleSignal);
    console.log(omkCliHero(formatOmkVersionFooter()));
    console.log(style.phosphorBold("◈ omk parallel — DAG executor with live UI"));
    console.log(style.gray(t("parallel.agentsActivated")));
    // Determine TTY and mode defaults
    const isTTY = Boolean(process.stdout.isTTY);
    const useWatch = options.noWatch
        ? false
        : options.watch ?? isTTY;
    const useAlternateScreen = options.alternateScreen === true;
    const useCompact = options.compact === true || options.view === "compact";
    const shouldPause = isTTY && options.noPause !== true;
    // Build worker labels for the UI
    const workerLabels = buildWorkerLabels(routedState);
    // Enter alternate screen only when explicitly requested
    if (useAlternateScreen && isTTY) {
        process.stdout.write("\x1b[?1049h");
    }
    // ── Live parallel UI ──
    let latestState = routedState;
    const liveRenderer = new ParallelLiveRenderer({
        runId,
        approvalPolicy,
        workerCount,
        ensembleEnabled: resources.ensembleDefaultEnabled,
        refreshMs: 1500,
        goalTitle: effectiveGoal,
        mode: useWatch ? "watch" : "no-watch",
        workerLabels,
        statePath,
        view: options.view ?? (useCompact ? "compact" : "cockpit"),
    });
    const previousStatuses = new Map();
    const handleStateChange = (stateSnapshot) => {
        if (useWatch) {
            latestState = stateSnapshot;
            if (useCompact) {
                const line = renderCompactParallelFrame(stateSnapshot, {
                    runId,
                    approvalPolicy,
                    workerCount,
                    ensembleEnabled: resources.ensembleDefaultEnabled,
                    goalTitle: effectiveGoal,
                    mode: "watch",
                    workerLabels,
                    statePath,
                });
                console.log(line);
            }
            return;
        }
        logRunStateTransitions(stateSnapshot, previousStatuses);
    };
    if (useWatch) {
        if (!useCompact) {
            liveRenderer.start(() => latestState);
        }
    }
    const activePreset = await getActiveRuntimePreset();
    const harnessEnv = {
        ...createOmkSessionEnv(root, runId),
        OMK_RUN_ID: runId,
        OMK_FLOW: "parallel",
        OMK_GOAL: intentFrame.desiredOutcome,
        OMK_GOAL_CONTEXT: renderActionDigest(intentFrame),
        OMK_WORKERS: String(workerCount),
        OMK_DAG_ROUTING: "1",
        OMK_DAG_STATE_PATH: statePath,
        OMK_MCP_SCOPE: mcpScope,
        OMK_SKILLS_SCOPE: resources.skillsScope,
        OMK_APPROVAL_POLICY: approvalPolicy,
        ...(modelArg.model ? { OMK_PROVIDER_MODEL: modelArg.model } : {}),
    };
    const runner = await createHarnessTaskRunner({
        root,
        runId,
        mode: "parallel",
        providerPolicy,
        providerOptions: {
            eventRunDir: runDir,
            deepseekPromptPrefix,
            allowDeepSeekAdvisoryFileNodes: true,
            agentFile,
            promptPrefix: promptText,
            mcpScope,
            skillsScope: resources.skillsScope,
            hooksScope: resources.hooksScope,
            mcpNames: activePreset?.mcpServers ?? [],
            skillNames: activePreset?.skills ?? [],
            hookNames: activePreset?.hooks ?? [],
            toolNames: [],
        },
        env: harnessEnv,
    });
    let result;
    try {
        result = await executeHarnessRun({
            root,
            runId,
            dag,
            runner,
            env: harnessEnv,
            workers: workerCount,
            approvalPolicy: approvalPolicy,
            nodeTimeoutMs: options.timeoutPreset ? undefined : 600_000,
            timeoutPreset: options.timeoutPreset,
            resumeFromState: routedState,
            eventRunDir: runDir,
            ensemble: resources.ensembleDefaultEnabled ? {} : false,
            signal: abortController.signal,
            onStateChange: handleStateChange,
        });
    }
    finally {
        liveRenderer.stop();
        process.off("SIGINT", handleSignal);
        process.off("SIGTERM", handleSignal);
        if (forceExitTimer) {
            clearTimeout(forceExitTimer);
            forceExitTimer = undefined;
        }
        // Restore original terminal screen before printing results/errors.
        if (useAlternateScreen && isTTY) {
            process.stdout.write("\x1b[?1049l");
        }
    }
    console.log("");
    if (result.success) {
        console.log(status.ok("Parallel DAG run complete"));
    }
    else {
        console.log(status.error("Parallel DAG run failed"));
    }
    console.log(label("State", statePath));
    // ── Interactive handoff ──
    if (options.chat && result.success) {
        console.log(style.purple(t("parallel.complete")));
        const { chatCommand } = await import("../chat.js");
        await chatCommand({ runId });
        return { runId, success: true };
    }
    // Pause so the user can read the result before the process exits
    if (shouldPause) {
        console.log(style.gray("\n  Press Enter to continue..."));
        const terminalInputState = captureTerminalInputState(process.stdin);
        if (process.stdin.isTTY) {
            process.stdin.setRawMode(false);
        }
        const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
        await new Promise((resolve) => {
            rl.once("line", () => {
                resolve();
            });
        }).finally(() => {
            rl.close();
            restoreTerminalInputState(process.stdin, terminalInputState);
        });
    }
    return { runId, success: result.success };
}
