"use strict";
/**
 * test_workflow_engine — Story 4.2 regression suite.
 *
 * Asserts the WorkflowEngine contract via 6 scenarios:
 *   1. Happy path — workflow with 4 sequential executable steps + phase
 *      markers runs to `completed` with all steps marked, artifacts
 *      accumulated, checkpoint cleared at the end.
 *   2. Elicit pause — step with elicit:true sets run status to
 *      `awaiting_input`; provideInput() resumes the run to completion.
 *   3. Resume after simulated crash — run halted by injected executor
 *      failure on step 3 is resumable; subsequent resume() with a
 *      passing executor finishes all remaining steps.
 *   4. Abort during run — abort() sets status=aborted and clears the
 *      checkpoint; idempotent on re-abort.
 *   5. Optional + condition — optional step whose condition is not met
 *      in context is `skipped`, not `completed`; runs around it succeed.
 *   6. Status / state file durability — status(runId) returns null for
 *      unknown ids; state file lives under <stateDir>/workflows/.
 *
 * Prints TEST_WORKFLOW_ENGINE_OK on full pass.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const WorkflowEngine_1 = require("./orchestrator/workflow/WorkflowEngine");
function assert(cond, msg) {
    if (!cond)
        throw new Error(`assertion failed: ${msg}`);
}
function mkTmp() {
    return fs.mkdtempSync(path.join(os.tmpdir(), 'wf-engine-test-'));
}
function rmTmp(dir) {
    try {
        fs.rmSync(dir, { recursive: true, force: true });
    }
    catch { /* ignore */ }
}
function makeWorkflow(extra = {}) {
    return {
        id: 'test-wf',
        name: 'Test',
        type: 'custom',
        version: '1.0',
        sequence: [
            { phase: 0, name: 'Setup' },
            { id: 's1', agent: 'dev', action: 'do-a', creates: ['a.md'] },
            { id: 's2', agent: 'dev', action: 'do-b', creates: ['b.md'], requires: 's1' },
            { phase: 1, name: 'Cleanup' },
            { id: 's3', agent: 'dev', action: 'do-c', creates: ['c.md'], requires: 's2' },
            { id: 's4', agent: 'dev', action: 'do-d', updates: ['c.md'], requires: 's3' },
        ],
        ...extra,
    };
}
class ScriptedExecutor {
    rules;
    constructor(rules) {
        this.rules = rules;
    }
    async execute(inv) {
        const r = this.rules[inv.step.id];
        if (!r) {
            const artifacts = [];
            if (Array.isArray(inv.step.creates))
                artifacts.push(...inv.step.creates);
            if (Array.isArray(inv.step.updates))
                artifacts.push(...inv.step.updates);
            return { status: 'completed', artifacts, result: 'default' };
        }
        return r;
    }
}
// ─────────────────────────────────────────────────────────────
// Scenario 1 — happy path
// ─────────────────────────────────────────────────────────────
async function scenarioHappyPath() {
    const tmp = mkTmp();
    const prev = process.env.OPENLIFE_STATE_DIR;
    try {
        process.env.OPENLIFE_STATE_DIR = path.join(tmp, '.openlife');
        const engine = new WorkflowEngine_1.WorkflowEngine();
        const wf = makeWorkflow();
        const state = await engine.run(wf);
        assert(state.status === 'completed', `expected completed, got ${state.status}`);
        assert(state.completedSteps.length === 4, `expected 4 completed, got ${state.completedSteps.length}`);
        assert(state.artifacts.includes('a.md') && state.artifacts.includes('c.md'), 'artifacts must accumulate');
        // state file written
        const stateFile = path.join(engine._stateDir(), 'workflows', `${state.runId}.json`);
        assert(fs.existsSync(stateFile), 'state file must exist');
        // events log written
        const eventsFile = path.join(engine._stateDir(), 'workflows', 'events', `${state.runId}.jsonl`);
        assert(fs.existsSync(eventsFile), 'events log must exist');
        const events = fs.readFileSync(eventsFile, 'utf-8').split('\n').filter((l) => l.length > 0);
        assert(events.length >= 6, `expected at least 6 events (run.started + 4 step + run.completed), got ${events.length}`);
        console.log('✅ scenario 1: happy path');
    }
    finally {
        if (prev === undefined)
            delete process.env.OPENLIFE_STATE_DIR;
        else
            process.env.OPENLIFE_STATE_DIR = prev;
        rmTmp(tmp);
    }
}
// ─────────────────────────────────────────────────────────────
// Scenario 2 — elicit pause + provideInput resumes
// ─────────────────────────────────────────────────────────────
async function scenarioElicit() {
    const tmp = mkTmp();
    const prev = process.env.OPENLIFE_STATE_DIR;
    try {
        process.env.OPENLIFE_STATE_DIR = path.join(tmp, '.openlife');
        const engine = new WorkflowEngine_1.WorkflowEngine();
        const wf = {
            id: 'elicit-wf',
            name: 'Elicit',
            type: 'custom',
            version: '1.0',
            sequence: [
                { id: 'a', agent: 'dev' },
                { id: 'gate', agent: 'pm', elicit: true },
                { id: 'b', agent: 'dev' },
            ],
        };
        const state = await engine.run(wf);
        assert(state.status === 'awaiting_input', `expected awaiting_input, got ${state.status}`);
        assert(state.currentStep === 'gate', `expected currentStep=gate, got ${state.currentStep ?? 'none'}`);
        assert(state.completedSteps.length === 1, 'only `a` should be done before elicit');
        const resumed = await engine.provideInput(wf, state.runId, 'gate', { answer: 'yes' });
        assert(resumed.status === 'completed', `expected completed after provideInput, got ${resumed.status}`);
        assert(resumed.completedSteps.length === 3, `expected 3 completed, got ${resumed.completedSteps.length}`);
        console.log('✅ scenario 2: elicit pause + provideInput');
    }
    finally {
        if (prev === undefined)
            delete process.env.OPENLIFE_STATE_DIR;
        else
            process.env.OPENLIFE_STATE_DIR = prev;
        rmTmp(tmp);
    }
}
// ─────────────────────────────────────────────────────────────
// Scenario 3 — resume after injected step failure
// ─────────────────────────────────────────────────────────────
async function scenarioResume() {
    const tmp = mkTmp();
    const prev = process.env.OPENLIFE_STATE_DIR;
    try {
        process.env.OPENLIFE_STATE_DIR = path.join(tmp, '.openlife');
        // First run: executor fails on step 3
        const failing = new WorkflowEngine_1.WorkflowEngine({
            executor: new ScriptedExecutor({
                s3: { status: 'failed', error: 'simulated network drop' },
            }),
        });
        const wf = makeWorkflow();
        const first = await failing.run(wf);
        assert(first.status === 'failed', `expected failed, got ${first.status}`);
        assert(first.completedSteps.length === 2, `expected 2 done before failure, got ${first.completedSteps.length}`);
        // Mark as recoverable by flipping status back to running (in real life
        // a resume would happen after process restart; we simulate by setting
        // status to running and re-invoking resume with a passing executor).
        // resume() expects non-terminal status; we mutate state for the test.
        const stateFile = path.join(failing._stateDir(), 'workflows', `${first.runId}.json`);
        const raw = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
        raw.status = 'running';
        raw.failureReason = undefined;
        raw.completedAt = undefined;
        fs.writeFileSync(stateFile, JSON.stringify(raw, null, 2));
        // Resume with passing executor
        const passing = new WorkflowEngine_1.WorkflowEngine({ executor: new ScriptedExecutor({}) });
        const resumed = await passing.resume(wf, first.runId);
        assert(resumed.status === 'completed', `expected completed on resume, got ${resumed.status}`);
        assert(resumed.completedSteps.length === 4, `expected 4 done after resume, got ${resumed.completedSteps.length}`);
        assert(resumed.completedSteps.includes('s3'), 'resumed run must complete the previously-failed step');
        console.log('✅ scenario 3: resume after injected failure');
    }
    finally {
        if (prev === undefined)
            delete process.env.OPENLIFE_STATE_DIR;
        else
            process.env.OPENLIFE_STATE_DIR = prev;
        rmTmp(tmp);
    }
}
// ─────────────────────────────────────────────────────────────
// Scenario 4 — abort
// ─────────────────────────────────────────────────────────────
async function scenarioAbort() {
    const tmp = mkTmp();
    const prev = process.env.OPENLIFE_STATE_DIR;
    try {
        process.env.OPENLIFE_STATE_DIR = path.join(tmp, '.openlife');
        const engine = new WorkflowEngine_1.WorkflowEngine();
        const wf = {
            id: 'abort-wf',
            name: 'Abort',
            type: 'custom',
            version: '1.0',
            sequence: [
                { id: 'a', agent: 'dev', elicit: true }, // pauses immediately
            ],
        };
        const state = await engine.run(wf);
        assert(state.status === 'awaiting_input', 'must pause on elicit');
        const aborted = engine.abort(state.runId, 'test reason');
        assert(aborted !== null && aborted.status === 'aborted', 'must mark as aborted');
        assert(aborted.failureReason === 'test reason', 'reason must be recorded');
        // Idempotent on second call
        const again = engine.abort(state.runId, 'another reason');
        assert(again?.status === 'aborted' && again.failureReason === 'test reason', 'abort must be idempotent (preserves first reason)');
        console.log('✅ scenario 4: abort + idempotency');
    }
    finally {
        if (prev === undefined)
            delete process.env.OPENLIFE_STATE_DIR;
        else
            process.env.OPENLIFE_STATE_DIR = prev;
        rmTmp(tmp);
    }
}
// ─────────────────────────────────────────────────────────────
// Scenario 5 — optional + condition not met → skipped
// ─────────────────────────────────────────────────────────────
async function scenarioOptionalCondition() {
    const tmp = mkTmp();
    const prev = process.env.OPENLIFE_STATE_DIR;
    try {
        process.env.OPENLIFE_STATE_DIR = path.join(tmp, '.openlife');
        const engine = new WorkflowEngine_1.WorkflowEngine();
        const wf = {
            id: 'cond-wf',
            name: 'Cond',
            type: 'custom',
            version: '1.0',
            sequence: [
                { id: 'always', agent: 'dev' },
                { id: 'maybe', agent: 'dev', optional: true, condition: 'feature_enabled' },
                { id: 'always-2', agent: 'dev' },
            ],
        };
        const state = await engine.run(wf, { other_flag: true });
        assert(state.status === 'completed', `expected completed, got ${state.status}`);
        assert(state.steps['maybe'].status === 'skipped', 'maybe must be skipped when condition unmet');
        assert(state.steps['always'].status === 'completed', 'always must complete');
        assert(state.steps['always-2'].status === 'completed', 'always-2 must complete');
        console.log('✅ scenario 5: optional + condition (skipped when unmet)');
    }
    finally {
        if (prev === undefined)
            delete process.env.OPENLIFE_STATE_DIR;
        else
            process.env.OPENLIFE_STATE_DIR = prev;
        rmTmp(tmp);
    }
}
// ─────────────────────────────────────────────────────────────
// Scenario 6 — status() returns null for unknown id
// ─────────────────────────────────────────────────────────────
function scenarioStatusUnknown() {
    const tmp = mkTmp();
    const prev = process.env.OPENLIFE_STATE_DIR;
    try {
        process.env.OPENLIFE_STATE_DIR = path.join(tmp, '.openlife');
        const engine = new WorkflowEngine_1.WorkflowEngine();
        assert(engine.status('does-not-exist') === null, 'status must return null for unknown id');
        console.log('✅ scenario 6: status(unknown) returns null');
    }
    finally {
        if (prev === undefined)
            delete process.env.OPENLIFE_STATE_DIR;
        else
            process.env.OPENLIFE_STATE_DIR = prev;
        rmTmp(tmp);
    }
}
// ─────────────────────────────────────────────────────────────
// Driver
// ─────────────────────────────────────────────────────────────
async function main() {
    console.log('🧪 test_workflow_engine — Story 4.2 regression suite');
    await scenarioHappyPath();
    await scenarioElicit();
    await scenarioResume();
    await scenarioAbort();
    await scenarioOptionalCondition();
    scenarioStatusUnknown();
    console.log('');
    console.log('TEST_WORKFLOW_ENGINE_OK');
}
main().catch((err) => {
    console.error('❌ test_workflow_engine FAILED:', err instanceof Error ? err.message : err);
    process.exit(1);
});
