"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.recoverState = recoverState;
const fs_1 = require("fs");
const path_1 = require("path");
const wal_js_1 = require("./wal.js");
const snapshot_js_1 = require("./snapshot.js");
function recoverState(runDir, allNodeIds) {
    const walPath = (0, path_1.join)(runDir, 'wal.jsonl');
    if (!(0, fs_1.existsSync)(walPath))
        return null;
    const wal = new wal_js_1.WAL(runDir);
    // Try snapshot for base state
    const snapshot = (0, snapshot_js_1.loadSnapshot)(runDir);
    let baseSeq = 0;
    const state = {
        runId: '',
        workflow: '',
        workflowYaml: '',
        args: '',
        workspacePath: '',
        artifactsPath: '',
        projectPath: '',
        branchName: undefined,
        startedAt: '',
        completedAt: undefined,
        pending: new Set(),
        completed: new Set(),
        failed: new Set(),
        skipped: new Set(),
        nodeResults: {},
        retryCounts: new Map(),
        walSeq: 0,
    };
    // Apply snapshot as base
    if (snapshot) {
        baseSeq = snapshot.walSeq;
        state.runId = snapshot.runId;
        state.workflow = snapshot.workflow;
        state.workflowYaml = snapshot.workflowYaml ?? state.workflowYaml;
        state.args = snapshot.args;
        state.workspacePath = snapshot.workspacePath;
        state.artifactsPath = snapshot.artifactsPath;
        state.projectPath = snapshot.projectPath;
        state.branchName = snapshot.branchName;
        state.startedAt = snapshot.startedAt;
        state.completedAt = snapshot.completedAt;
        if (snapshot.status === 'completed' || snapshot.status === 'failed') {
            state.runStatus = snapshot.status;
        }
        for (const [nodeId, nodeSnap] of Object.entries(snapshot.nodes)) {
            const outputStr = typeof nodeSnap.outputRef === 'string'
                ? nodeSnap.outputRef
                : nodeSnap.outputRef != null ? wal.resolveOutput(nodeSnap.outputRef) : undefined;
            const structuredOut = nodeSnap.structuredOutputRef != null
                ? (typeof nodeSnap.structuredOutputRef === 'object' && !('ref' in nodeSnap.structuredOutputRef) && !('__vsaf_ref' in nodeSnap.structuredOutputRef)
                    ? nodeSnap.structuredOutputRef
                    : wal.resolveStructuredOutput(nodeSnap.structuredOutputRef))
                : undefined;
            state.nodeResults[nodeId] = {
                status: nodeSnap.status,
                output: outputStr,
                structuredOutput: structuredOut,
                startedAt: nodeSnap.startedAt,
                completedAt: nodeSnap.completedAt,
                error: nodeSnap.error,
            };
            switch (nodeSnap.status) {
                case 'completed':
                    state.completed.add(nodeId);
                    break;
                case 'failed':
                    state.failed.add(nodeId);
                    break;
                case 'skipped':
                    state.skipped.add(nodeId);
                    break;
                default:
                    state.pending.add(nodeId);
                    break;
            }
            if (nodeSnap.retryCount) {
                state.retryCounts.set(nodeId, nodeSnap.retryCount);
            }
        }
    }
    // Replay WAL events after snapshot
    const events = wal.replay(baseSeq);
    // Track which nodes have "started" for crash detection
    const startedNodes = new Set();
    for (const event of events) {
        state.walSeq = event.seq;
        switch (event.type) {
            case 'run:create':
                state.workflow = event.data?.workflow ?? state.workflow;
                state.workflowYaml = event.data?.workflowYaml ?? state.workflowYaml;
                state.args = event.data?.args ?? state.args;
                state.workspacePath = event.data?.workspacePath ?? state.workspacePath;
                state.artifactsPath = event.data?.artifactsPath ?? state.artifactsPath;
                state.projectPath = event.data?.projectPath ?? state.projectPath;
                state.branchName = event.data?.branchName ?? state.branchName;
                state.startedAt = event.ts;
                break;
            case 'node:start':
                if (event.nodeId) {
                    startedNodes.add(event.nodeId);
                    // Clear previous terminal state — handles restart after fail (loop retry)
                    state.failed.delete(event.nodeId);
                    state.completed.delete(event.nodeId);
                    state.skipped.delete(event.nodeId);
                    state.nodeResults[event.nodeId] = {
                        ...state.nodeResults[event.nodeId],
                        status: 'running',
                        startedAt: event.data?.startedAt ?? event.ts,
                    };
                }
                break;
            case 'node:complete':
                if (event.nodeId) {
                    startedNodes.delete(event.nodeId);
                    state.completed.add(event.nodeId);
                    state.pending.delete(event.nodeId);
                    state.failed.delete(event.nodeId);
                    const output = event.data?.output != null
                        ? (typeof event.data.output === 'string'
                            ? event.data.output
                            : wal.resolveOutput(event.data.output))
                        : undefined;
                    const structured = event.data?.structuredOutput != null
                        ? (typeof event.data.structuredOutput === 'object' && !('ref' in event.data.structuredOutput) && !('__vsaf_ref' in event.data.structuredOutput)
                            ? event.data.structuredOutput
                            : wal.resolveStructuredOutput(event.data.structuredOutput))
                        : undefined;
                    state.nodeResults[event.nodeId] = {
                        status: 'completed',
                        output,
                        structuredOutput: structured,
                        startedAt: state.nodeResults[event.nodeId]?.startedAt,
                        completedAt: event.data?.completedAt ?? event.ts,
                        durationMs: event.data?.durationMs,
                    };
                }
                break;
            case 'node:fail':
                if (event.nodeId) {
                    startedNodes.delete(event.nodeId);
                    state.failed.add(event.nodeId);
                    state.pending.delete(event.nodeId);
                    state.completed.delete(event.nodeId);
                    state.nodeResults[event.nodeId] = {
                        status: 'failed',
                        error: event.data?.error,
                        startedAt: state.nodeResults[event.nodeId]?.startedAt,
                        completedAt: event.data?.completedAt ?? event.ts,
                        durationMs: event.data?.durationMs,
                    };
                    if (event.data?.retryCount !== undefined) {
                        state.retryCounts.set(event.nodeId, event.data.retryCount);
                    }
                }
                break;
            case 'node:skip':
                if (event.nodeId) {
                    startedNodes.delete(event.nodeId);
                    state.skipped.add(event.nodeId);
                    state.pending.delete(event.nodeId);
                    state.nodeResults[event.nodeId] = { status: 'skipped' };
                }
                break;
            case 'node:reset':
                if (event.nodeId) {
                    startedNodes.delete(event.nodeId);
                    state.pending.add(event.nodeId);
                    state.completed.delete(event.nodeId);
                    state.failed.delete(event.nodeId);
                    state.skipped.delete(event.nodeId);
                    delete state.nodeResults[event.nodeId];
                }
                break;
            case 'run:complete':
                state.runStatus = 'completed';
                state.completedAt = event.ts;
                break;
            case 'run:fail':
                state.runStatus = 'failed';
                state.completedAt = event.ts;
                break;
        }
    }
    // Crash recovery: nodes with start but no completion → pending
    for (const nodeId of startedNodes) {
        if (!state.completed.has(nodeId) && !state.failed.has(nodeId) && !state.skipped.has(nodeId)) {
            state.pending.add(nodeId);
        }
    }
    // Reconstruct pending set from graph when node list provided
    if (allNodeIds) {
        for (const nodeId of allNodeIds) {
            if (!state.completed.has(nodeId) && !state.failed.has(nodeId) && !state.skipped.has(nodeId)) {
                state.pending.add(nodeId);
            }
        }
    }
    return state;
}
//# sourceMappingURL=recovery.js.map