/**
 * ============================================================
 *  plugins/workflow-runner/src/runner.ts —— workflow 顺序执行器
 * ------------------------------------------------------------
 *  AsyncGenerator，按 yaml.steps[] 顺序执行：
 *    - when: 不满足 → yield workflow_step_skipped
 *    - 命中动作字段 → 调对应 step executor
 *    - capture: 把 result.raw 上的字段绑到 VarStack
 *    - onError: halt（默认）→ yield error 后 return；continue → 记录后跳过
 *    - 信号 abort → yield interrupted 后 return
 *
 *  支持的 step kind（loader 已校验）：
 *    - tool / llm / assert            —— 原子动作（execXxxStep 返回 StepResult）
 *    - agent / skill                  —— 有界 TAOR：miniReAct(agent) / 外部 SKILL.md(skill)；
 *                                        透传子事件（stage_change/text/reasoning/tool_*）
 *    - branch / loop / pause          —— 控制流，直接在 runSteps 内处理（不走 executor）
 *    - parallel / vote                —— 并发 / 投票门，也在 runSteps 内处理
 *
 *  控制流嵌套通过 runSteps 递归实现：branch.then/else / loop.do 子数组
 *  是 StepDef[]，调用方语义与顶层 def.steps 一致；变量栈用 VarStack.push/pop
 *  在 loop 迭代间隔离 `${as}` / `${as}_idx`。
 * ============================================================
 */
import { getWorkingDir } from '../../../src/plugin-sdk.js';
import { dlog } from '../../../src/utils/debugLog.js';
import { evalExpr, interpolate } from './expressions.js';
import { runRecovery } from './recovery.js';
import { execAgentStep } from './stepExecutors/agent.js';
import { execAssertStep } from './stepExecutors/assert.js';
import { execLlmStep } from './stepExecutors/llm.js';
import { execSkillStep } from './stepExecutors/skill.js';
import { execToolStep } from './stepExecutors/tool.js';
import { VarStack } from './types.js';
import { runVoter } from './voteHelpers.js';
import { WorkflowState } from './workflowState.js';
/**
 * 插件 ID：把私有 workflow_* 事件镜像成框架通用 plugin_progress 时挂这个 id。
 * LiveArea 状态行会渲染成 "[plugin:workflow-runner] i/total <message>"
 * （src/ui/LiveArea.tsx:123），让 TUI 里跑 workflow 不再"无声"。
 * （-p 非交互模式另由 src/cli/print.ts 把通用 warning 事件落 stderr。）
 */
const WF_PLUGIN_ID = 'workflow-runner';
function stepKind(step) {
    if (step.type)
        return step.type;
    if (step.tool)
        return 'tool';
    if (step.skill)
        return 'skill';
    if (step.agent)
        return 'agent';
    if (step.llm)
        return 'llm';
    return 'assert'; // 不会到这里（loader 已校验），兜底
}
function readPath(root, path) {
    const parts = path.split('.');
    let cur = root;
    for (const p of parts) {
        if (cur == null || typeof cur !== 'object')
            return undefined;
        cur = cur[p];
    }
    return cur;
}
function bindCapture(captureMap, result, vars) {
    for (const [resultField, varName] of Object.entries(captureMap)) {
        const value = resultField.includes('.')
            ? readPath(result.raw, resultField)
            : result.raw[resultField];
        vars.set(varName, value);
    }
}
/**
 * 分发"动作"型 step（tool / agent / llm / skill / assert）到对应 executor。
 * 控制流 step（branch / loop / pause / parallel / vote）由 runSteps 直接处理，不走这里。
 */
async function* dispatchActionStep(step, vars, ctx) {
    if (step.type === 'assert')
        return execAssertStep(step, vars);
    if (step.tool)
        return yield* execToolStep(step, vars, ctx);
    if (step.agent)
        return yield* execAgentStep(step, vars, ctx);
    if (step.llm)
        return yield* execLlmStep(step, vars, ctx);
    if (step.skill)
        return yield* execSkillStep(step, vars, ctx);
    throw new Error(`step ${step.id}: 无可执行字段（tool/agent/llm/skill/assert）`);
}
/**
 * 递归执行 steps[]。返回 true 表示该 block 已 halt（上游应中止）。
 *
 * yields 混合事件：WorkflowEvent + LoopEvent（skill 子循环转发 / error / interrupted）。
 * index/total 字段相对**当前 block**，嵌套 block 自有计数（branch.then 内 3 步就报 total=3）。
 */
async function* runSteps(steps, vars, ctx, state) {
    for (let i = 0; i < steps.length; i++) {
        const step = steps[i];
        if (ctx.signal?.aborted) {
            yield { type: 'interrupted' };
            return true;
        }
        // when 条件
        if (step.when) {
            let pass = false;
            try {
                pass = Boolean(evalExpr(step.when, vars));
            }
            catch (e) {
                const errMsg = `when 表达式错误: ${e.message}`;
                yield {
                    type: 'workflow_step_end',
                    id: step.id,
                    ok: false,
                    error: errMsg,
                };
                yield {
                    type: 'error',
                    error: `Workflow halted at step "${step.id}": ${errMsg}`,
                };
                return true;
            }
            if (!pass) {
                yield {
                    type: 'workflow_step_skipped',
                    id: step.id,
                    reason: 'when=false',
                };
                await state.appendProgress(`- ${step.id} skipped (when=false)`);
                continue;
            }
        }
        const kind = stepKind(step);
        dlog('wf', `step ${i + 1}/${steps.length} START ${step.id} (${kind})`);
        yield {
            type: 'workflow_step_start',
            id: step.id,
            kind,
            index: i,
            total: steps.length,
        };
        // P1 TUI 可见：把私有 workflow_step_start 镜像成框架通用 plugin_progress，
        // LiveArea 状态行渲染成 "[plugin:workflow-runner] i/total step <id> (<kind>)"。
        // （嵌套 block 的 i/total 相对当前 block，足够给"跑到哪一步"的实时反馈。）
        yield {
            type: 'plugin_progress',
            pluginId: WF_PLUGIN_ID,
            current: i + 1,
            max: steps.length,
            message: `step ${step.id} (${kind})`,
        };
        // ---------- 控制流：branch ----------
        if (step.type === 'branch') {
            let cond = false;
            try {
                cond = Boolean(evalExpr(step.condition ?? 'false', vars));
            }
            catch (e) {
                const errMsg = `branch 条件错误: ${e.message}`;
                await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
                yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
                yield { type: 'error', error: `Workflow halted at step "${step.id}": ${errMsg}` };
                return true;
            }
            const branch = cond ? step.then : step.else;
            if (Array.isArray(branch) && branch.length > 0) {
                const childHalted = yield* runSteps(branch, vars, ctx, state);
                if (childHalted)
                    return true;
            }
            await state.appendProgress(`✓ ${step.id} (branch=${cond})`);
            await state.writeVars(vars.snapshot());
            yield {
                type: 'workflow_step_end',
                id: step.id,
                ok: true,
                output: `branch=${cond}`,
            };
            continue;
        }
        // ---------- 控制流：loop ----------
        if (step.type === 'loop') {
            let arr;
            try {
                arr = evalExpr(step.over ?? '[]', vars);
            }
            catch (e) {
                const errMsg = `loop over 表达式错误: ${e.message}`;
                await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
                yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
                yield { type: 'error', error: `Workflow halted at step "${step.id}": ${errMsg}` };
                return true;
            }
            if (!Array.isArray(arr)) {
                const errMsg = `loop over 必须求值为数组，得到 ${typeof arr}`;
                await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
                yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
                yield { type: 'error', error: `Workflow halted at step "${step.id}": ${errMsg}` };
                return true;
            }
            const asName = step.as ?? 'item';
            let iterCount = 0;
            for (let idx = 0; idx < arr.length; idx++) {
                if (ctx.signal?.aborted) {
                    yield { type: 'interrupted' };
                    return true;
                }
                vars.push();
                vars.set(asName, arr[idx]);
                vars.set(`${asName}_idx`, idx);
                const childHalted = yield* runSteps(step.do ?? [], vars, ctx, state);
                vars.pop();
                if (childHalted)
                    return true;
                iterCount++;
            }
            await state.appendProgress(`✓ ${step.id} (loop ×${iterCount})`);
            await state.writeVars(vars.snapshot());
            yield {
                type: 'workflow_step_end',
                id: step.id,
                ok: true,
                output: `loop ${iterCount} 次`,
            };
            continue;
        }
        // ---------- 控制流：pause ----------
        //
        // 非交互模式（CLI -p / 测试）下无法真的暂停等待人工，直接当成 skipped。
        // 交互模式（TUI）需要 UI 层接住 step_skipped 把 reason 渲染出来 —— P0 已
        // 走通这条 UI 路径。step.prompt 用于把"暂停理由"传给上层呈现。
        if (step.type === 'pause') {
            const msg = step.prompt
                ? interpolate(step.prompt, vars)
                : 'pause（非交互模式直接跳过）';
            await state.appendProgress(`- ${step.id} pause: ${msg}`);
            // P3：pause 在当前实现下**从不**真正等待人工（runner 是纯 AsyncGenerator，
            // 没有把用户输入回灌进来的通道）。把"静默放行"变"响亮放行"——发通用 warning
            // （-p 模式由 print.ts 落 stderr，权威通道）并镜像 plugin_progress（TUI 状态行可见），
            // 避免审批闸门被悄悄跳过。真·HITL pause 属 P4，本轮不做。
            const warnMsg = `⚠ pause "${step.id}" 在非交互模式被自动跳过，未真正等待人工确认——若这是审批闸门请勿依赖它。提示：${msg}`;
            yield { type: 'warning', message: warnMsg };
            yield {
                type: 'plugin_progress',
                pluginId: WF_PLUGIN_ID,
                current: 0,
                message: warnMsg,
            };
            yield {
                type: 'workflow_step_skipped',
                id: step.id,
                reason: `pause: ${msg}`,
            };
            yield {
                type: 'workflow_step_end',
                id: step.id,
                ok: true,
                output: 'paused (skipped in non-interactive mode)',
            };
            continue;
        }
        // ---------- 控制流：parallel ----------
        //
        // 每个 fork 独立 VarStack 副本 + 独立事件缓冲；Promise.all 并发执行。
        // 子 fork 的事件先收进 buffer，再按 fork 顺序一次性 yield，保 UI
        // 时间轴可读（并发 yield 会导致 UI 拿到乱序事件）。
        // 任一 fork halt → 整个 parallel halt。
        if (step.type === 'parallel') {
            const forks = step.fork ?? [];
            const results = await Promise.all(forks.map(async (forkStep) => {
                const forkVars = vars.fork();
                const events = [];
                let halted = false;
                try {
                    halted = await collectStepsToBuffer([forkStep], forkVars, ctx, state, events);
                }
                catch (e) {
                    halted = true;
                    events.push({
                        type: 'error',
                        error: `fork ${forkStep.id} 抛异常: ${e.message}`,
                    });
                }
                return {
                    id: forkStep.id,
                    events,
                    vars: forkVars.snapshot(),
                    halted,
                };
            }));
            // 顺序 yield 所有 fork 事件
            for (const r of results) {
                for (const ev of r.events)
                    yield ev;
            }
            if (results.some((r) => r.halted)) {
                await state.appendProgress(`✗ ${step.id}: parallel: 至少一个 fork 失败`);
                yield {
                    type: 'workflow_step_end',
                    id: step.id,
                    ok: false,
                    error: 'parallel: 至少一个 fork 失败',
                };
                yield {
                    type: 'error',
                    error: `Workflow halted at step "${step.id}": parallel: 至少一个 fork 失败`,
                };
                return true;
            }
            // 聚合：raw.fork 是 [{ id, vars }, ...]，capture 可走 fork.0.vars.xxx 这种点路径
            const aggResult = {
                raw: { fork: results.map((r) => ({ id: r.id, vars: r.vars })) },
                preview: `parallel: ${results.length} forks done`,
            };
            if (step.capture)
                bindCapture(step.capture, aggResult, vars);
            await state.appendProgress(`✓ ${step.id} (parallel ×${results.length})`);
            await state.writeVars(vars.snapshot());
            yield {
                type: 'workflow_step_end',
                id: step.id,
                ok: true,
                output: aggResult.preview,
            };
            continue;
        }
        // ---------- 控制流：vote ----------
        //
        // 对每个 input 数组项跑 N 个 voter（同 prompt + refuted: boolean），
        // refuted 票数 >= threshold 则视为驳回（item 不进 survived）。
        // 心理学定位：voter 倾向于 refuted=true（找漏洞），所以阈值算"驳回票数"。
        if (step.type === 'vote') {
            const voters_count = step.voters_count ?? 3;
            const threshold = step.threshold ?? Math.ceil(voters_count / 2);
            let inputArr;
            try {
                inputArr = evalExpr(step.input ?? '[]', vars);
            }
            catch (e) {
                const errMsg = `vote.input 求值失败: ${e.message}`;
                await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
                yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
                yield {
                    type: 'error',
                    error: `Workflow halted at step "${step.id}": ${errMsg}`,
                };
                return true;
            }
            if (!Array.isArray(inputArr)) {
                const errMsg = `vote.input 必须求值为数组，得到 ${typeof inputArr}`;
                await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
                yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
                yield {
                    type: 'error',
                    error: `Workflow halted at step "${step.id}": ${errMsg}`,
                };
                return true;
            }
            const survived = [];
            for (const item of inputArr) {
                if (ctx.signal?.aborted) {
                    yield { type: 'interrupted' };
                    return true;
                }
                const verdicts = await Promise.all(Array.from({ length: voters_count }, () => runVoter(step.rules ?? '', item, vars, ctx)));
                const refutedCount = verdicts.filter((v) => v.refuted === true).length;
                if (refutedCount < threshold) {
                    survived.push(item);
                }
            }
            const voteResult = {
                raw: {
                    survived,
                    total: inputArr.length,
                    kept: survived.length,
                },
                preview: `vote: ${survived.length}/${inputArr.length} survived (threshold=${threshold}/${voters_count})`,
            };
            if (step.capture)
                bindCapture(step.capture, voteResult, vars);
            await state.appendProgress(`✓ ${step.id} (vote ${survived.length}/${inputArr.length})`);
            await state.writeVars(vars.snapshot());
            yield {
                type: 'workflow_step_end',
                id: step.id,
                ok: true,
                output: voteResult.preview,
            };
            continue;
        }
        // ---------- 普通动作 step：tool / llm / skill / assert ----------
        try {
            const result = yield* dispatchActionStep(step, vars, ctx);
            if (step.capture)
                bindCapture(step.capture, result, vars);
            await state.appendProgress(`✓ ${step.id} (${kind})`);
            await state.writeVars(vars.snapshot());
            yield {
                type: 'workflow_step_end',
                id: step.id,
                ok: true,
                output: result.preview,
            };
        }
        catch (e) {
            const errMsg = e.message ?? String(e);
            await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
            // onError:'recover' —— 不立刻 halt，交给有界恢复 agent 随机应变把这一步救回来。
            // 「确定性工作流 × TAOR」的结合点：happy path 走刚性 DAG，撞上预料之外的就地
            // 降级为 agent 自适应；救回则继续 happy path，救不回才 honest halt。
            if (step.onError === 'recover') {
                yield {
                    type: 'warning',
                    message: `step "${step.id}" 失败：${errMsg}；尝试自适应恢复…`,
                };
                yield {
                    type: 'plugin_progress',
                    pluginId: WF_PLUGIN_ID,
                    current: i + 1,
                    max: steps.length,
                    message: `↻ 恢复 ${step.id}…`,
                };
                const rec = yield* runRecovery(step, vars, ctx, errMsg);
                if (ctx.signal?.aborted) {
                    yield { type: 'interrupted' };
                    return true;
                }
                // 验证闸门：只有恢复 agent 真正交付（rec.ok）才绑 capture 走下去；
                // 显式放弃 / 产出不可信（rec.ok=false）则带原因 honest halt，绝不盲绑脏值。
                if (rec.ok) {
                    if (step.capture)
                        bindCapture(step.capture, rec.value, vars);
                    await state.appendProgress(`⚠→✓ ${step.id} recovered`);
                    await state.writeVars(vars.snapshot());
                    yield {
                        type: 'workflow_step_end',
                        id: step.id,
                        ok: true,
                        output: rec.value.preview,
                    };
                    continue;
                }
                const combined = `${errMsg}（自适应恢复未成功：${rec.reason}）`;
                yield {
                    type: 'workflow_step_end',
                    id: step.id,
                    ok: false,
                    error: combined,
                };
                yield {
                    type: 'error',
                    error: `Workflow halted at step "${step.id}": ${combined}`,
                };
                return true;
            }
            if (step.onError === 'continue') {
                yield {
                    type: 'workflow_step_end',
                    id: step.id,
                    ok: false,
                    error: errMsg,
                };
                continue;
            }
            yield {
                type: 'workflow_step_end',
                id: step.id,
                ok: false,
                error: errMsg,
            };
            yield {
                type: 'error',
                error: `Workflow halted at step "${step.id}": ${errMsg}`,
            };
            return true;
        }
    }
    return false;
}
/**
 * 运行一个 workflow。
 *
 * yields 混合事件：本地 WorkflowEvent（workflow_* 系列，UI 静默忽略不渲染）
 * + 通用 LoopEvent（error / interrupted / 子 step 透传的 text/tool/stage_change 等，
 * UI 识别并渲染）。经 plugin.ts -> pluginRunner 透传给 UI；workflow_* 由 PluginEvent
 * 开放契约承载。
 *
 * 成功跑完以 `workflow_done` 收尾（携带 vars 快照）；中途失败/中断只 yield error/interrupted。
 * **对外流式协议的通用终结事件 `turn_done` 由命令层 index.ts 在成功后补发**（见那里）——
 * 本函数自身事件序列保持不变，直测它的 runner.test.ts / 单元测试仍以 workflow_done 为最后一条。
 */
export async function* runWorkflow(def, inputs, ctx) {
    const vars = new VarStack();
    vars.setGlobal('inputs', inputs);
    // onError:'recover' 用：把工作流总目标（description）透传给恢复 agent，
    // 让它知道「这条工作流到底想干什么」。已有 goal 则不覆盖。
    const runCtx = { ...ctx, goal: ctx.goal ?? def.description };
    const state = new WorkflowState(getWorkingDir());
    await state.init(def.name, inputs);
    yield {
        type: 'workflow_start',
        name: def.name,
        totalSteps: def.steps.length,
    };
    // P1 TUI 可见：开工时给 LiveArea 状态行一条 plugin_progress（0/total）。
    yield {
        type: 'plugin_progress',
        pluginId: WF_PLUGIN_ID,
        current: 0,
        max: def.steps.length,
        message: `▶ workflow ${def.name}`,
    };
    let succeeded = false;
    try {
        const halted = yield* runSteps(def.steps, vars, runCtx, state);
        if (halted)
            return;
        // 完成态也给一条 plugin_progress（放在 workflow_done 之前，保证
        // workflow_done 仍是事件流最后一条——e2e 测试依赖 events[last]===workflow_done）。
        yield {
            type: 'plugin_progress',
            pluginId: WF_PLUGIN_ID,
            current: def.steps.length,
            max: def.steps.length,
            message: `✓ workflow ${def.name} 完成`,
        };
        yield { type: 'workflow_done', name: def.name, vars: vars.snapshot() };
        succeeded = true;
    }
    finally {
        // 只在「干净跑完」时清现场；halt / 中断 / 抛错都**保留** .minimal-agent-workflow/
        // （current.json / inputs.json / vars.json / progress.md，含失败那步的 ✗ 行）供事后排查
        // ——这正是这些文件的设计目的（workflowState.ts 头注）；失败时删掉等于自废武功。
        // 残留不会永久堆积：下次同名 workflow 的 init() 会先 rm，/new 也按 .minimal-agent-* 前缀统一清。
        if (succeeded)
            await state.cleanup();
    }
}
/**
 * parallel fork 用：把 runSteps 的 yield 流收集到 buffer 数组，
 * 让外层 Promise.all 拿到完整事件后再顺序 yield。
 *
 * 注：for-await 无法读取 generator 的 return value（JS 限制），
 * 所以"是否 halted"靠"buffer 里有 error event"近似判断 —— 这符合
 * runSteps 的语义（halt 一定会 yield error / interrupted）。
 */
async function collectStepsToBuffer(steps, vars, ctx, state, buffer) {
    for await (const ev of runSteps(steps, vars, ctx, state)) {
        buffer.push(ev);
    }
    return buffer.some((ev) => ev.type === 'error' || ev.type === 'interrupted');
}
