/**
 * ExecutionPipeline - Composable stage-based execution engine.
 *
 * Replaces the monolithic Bash.exec() method with an ordered sequence
 * of self-contained stages: normalize -> parse -> transform -> sandbox ->
 * interpret -> persist -> error-handle.
 *
 * Each stage either continues (passing context forward) or short-circuits
 * with a final BashExecResult.
 */
import { categorizeError } from "./stages/error-stage.js";
/**
 * Merge a pending WARN-policy destructive warning (from DestructiveStage) into
 * the interpreter-produced result. Returns a NEW result object (immutability —
 * never mutates the interpreter's result) with the warning observation appended
 * and the warning stderr line PREPENDED so it surfaces before command output.
 * No pending warning → returns the result unchanged.
 */
function mergePendingWarning(context) {
    const result = context.result;
    if (!result) {
        // Unreachable when called from the result-present branch; satisfies types.
        return {
            stdout: "",
            stderr: "",
            exitCode: 0,
            env: Object.create(null),
        };
    }
    const pending = context.pendingDestructiveWarning;
    if (!pending) {
        return result;
    }
    return {
        ...result,
        stderr: pending.stderr + result.stderr,
        observations: [...(result.observations ?? []), pending.observation],
    };
}
export class ExecutionPipeline {
    stages = [];
    finalize;
    constructor(finalize) {
        // Default to identity so existing direct usage / tests keep working.
        this.finalize = finalize ?? ((result) => result);
    }
    addStage(stage) {
        this.stages.push(stage);
    }
    async run(script, options, bash, services, execState, 
    /**
     * The PARENT Bash instance's interpreter state. Used ONLY for error-path
     * env semantics: the monolith builds all 11 error/guard results from
     * `this.state.env` (unchanged on error), NOT the possibly-mutated execState.
     * Defaults to execState when omitted (direct/test usage without a parent).
     */
    parentState) {
        const errorEnvState = parentState ?? execState;
        const context = {
            rawScript: script,
            normalizedScript: script,
            options,
            bash,
            services,
            execState,
            ast: undefined,
            metadata: undefined,
            defenseHandle: undefined,
            result: undefined,
            pendingDestructiveWarning: undefined,
        };
        try {
            for (const stage of this.stages) {
                const stageResult = await stage.execute(context);
                if (!stageResult.continue) {
                    return this.finalize(stageResult.result);
                }
            }
            // If all stages completed but no result was set, this is a logic error.
            // The interpret stage should always produce a result.
            if (context.result) {
                return this.finalize(mergePendingWarning(context));
            }
            // Fallback: should never reach here with a properly configured pipeline.
            return this.finalize({
                stdout: "",
                stderr: "",
                exitCode: 0,
                env: Object.create(null),
            });
        }
        catch (error) {
            // Match the monolith's catch (Bash.ts:959-1057): map known interpreter
            // errors into a structured BashExecResult. Critically, env semantics use
            // the PARENT bash.state (which is UNCHANGED on error — persist only runs
            // on exit 0), NOT the possibly-mutated execState. categorizeError returns
            // undefined for unrecognized errors, which we re-throw exactly as the
            // monolith does.
            const categorized = categorizeError(error, errorEnvState, options?.env);
            if (categorized) {
                return this.finalize(categorized);
            }
            throw error;
        }
        finally {
            // Always clean up the defense-in-depth handle
            context.defenseHandle?.deactivate();
        }
    }
}
