/**
 * ============================================================
 *  plugins/ralph-wiggum/plugin.ts —— ralph-wiggum 真·插件入口
 * ------------------------------------------------------------
 *  把原 src/plugins/pluginRunner.ts 的 do-while 循环驱动整段搬来。
 *  对外通过 PluginApi.runCommand 暴露 /ralph-loop。
 *
 *  循环契约：
 *    - 每轮把 history 重置成进入循环前的快照（fresh context）
 *    - 用 GoalState.composeContext() 拼 PLAN/BUILD/VERIFY/HEAL 阶段信息
 *    - 跑 runQuery
 *    - 检测 <promise>DONE</promise> → runVerification → 通过则退出
 *    - 检测 <PROMISE>NEED_REPLAN</PROMISE> → forceSetPhase(PLAN)
 *    - executeStopHook 是咨询式：block 才把 reason 注入下一轮，pass 不退出
 *
 *  终止（独占）：sentinel + verify 通过 / 达 max-iterations / abort / 安全天花板
 *
 *  Windows 上 hooks/stop-hook.sh 不可用，但循环不依赖 hook，功能完整，
 *  只是 hook 的咨询通道失效。
 * ============================================================
 */
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { runQuery, getWorkingDir, } from '../../src/plugin-sdk.js';
import { GoalState, Phase } from './src/goalState.js';
import { parseVerifyArgs, runVerification } from './src/verificationGate.js';
import { makeCompletionMatcher, hasNeedReplanSentinel, hasNeedGoalRevisionSentinel, extractLearnings, } from './src/sentinels.js';
import { executeStopHook } from './src/stopHookRunner.js';
const PLUGIN_NAME = 'ralph-wiggum';
const DEFAULT_MAX_ITERATIONS = 50;
const SAFETY_CEILING = 200;
const PLUGIN_ROOT = dirname(fileURLToPath(import.meta.url));
function extractMaxIterations(args) {
    const match = args.match(/--max-iterations\s+(\d+)/i);
    return match ? parseInt(match[1], 10) : undefined;
}
/** 解析 --completion-promise "TEXT"（带引号优先）或 --completion-promise TOKEN。 */
function extractCompletionPromise(args) {
    const quoted = args.match(/--completion-promise\s+"([^"]*)"/i);
    if (quoted)
        return quoted[1].trim() || undefined;
    const token = args.match(/--completion-promise\s+(\S+)/i);
    return token ? token[1].trim() || undefined : undefined;
}
/** 从 args 里剥掉控制 flag，得到喂给 LLM 的"干净目标文本"（避免 --verify 等泄漏进 prompt）。 */
function stripControlFlags(args) {
    return args
        .replace(/--max-iterations\s+\d+/gi, '')
        .replace(/--completion-promise\s+"[^"]*"/gi, '')
        .replace(/--completion-promise\s+\S+/gi, '')
        .replace(/--verify\s+"[^"]*"/gi, '')
        .replace(/--verify\s+\S+/gi, '')
        .replace(/\s{2,}/g, ' ')
        .trim();
}
async function* runRalphLoop(args, ctx) {
    const { provider, history, signal } = ctx;
    const maxIter = Math.min(extractMaxIterations(args) ?? DEFAULT_MAX_ITERATIONS, SAFETY_CEILING);
    // 没有 args（用户只敲 /ralph-loop）→ 留个最小 goal placeholder，避免 GoalState.init 拒空串
    // 控制 flag（--max-iterations / --verify / --completion-promise）都从原始 args 解析，
    // userGoal 才用剥过的干净文本，避免 flag 泄漏进喂给 LLM 的 prompt
    const userGoal = stripControlFlags(args) || '(未提供目标)';
    yield {
        type: 'plugin_progress',
        pluginId: PLUGIN_NAME,
        current: 0,
        max: maxIter,
        message: 'Ralph Wiggum copy-task loop 启动',
    };
    const checks = parseVerifyArgs(args);
    // 完成哨兵判定器：不传 --completion-promise 只认写死集合 DONE|COMPLETE|GOAL_COMPLETE，
    // 传了则在写死集合基础上叠加自定义短语（超集，向后兼容）
    const completePhrase = extractCompletionPromise(args);
    const isComplete = makeCompletionMatcher(completePhrase);
    // sessionTag = 插件名 → 多插件可并发不打架，/new 也能扫到清掉
    const goalState = new GoalState(getWorkingDir(), PLUGIN_NAME);
    await goalState.reset();
    await goalState.init(userGoal, checks);
    await goalState.appendProgress(`=== Loop 启动 === 目标: ${userGoal.slice(0, 120)}...`);
    // 进循环前快照 history —— 每轮 runQuery 前用它重置，保证 fresh context
    const baseHistory = history.slice();
    let iterationCount = 0;
    let consecutiveFailures = 0;
    let currentInput = userGoal;
    let finalAssistantMsg = null;
    try {
        do {
            iterationCount++;
            if (iterationCount > maxIter) {
                await goalState.forceSetPhase(Phase.DONE, `达到迭代上限 ${maxIter}`);
                await goalState.appendLearning(`[迭代上限] 循环在 ${iterationCount - 1} 轮后强制终止，可能目标过大或陷入死循环`);
                yield {
                    type: 'error',
                    error: `Loop 已达迭代上限 ${maxIter}，自动停止`,
                    code: 'max_turns',
                };
                return;
            }
            if (signal?.aborted) {
                yield { type: 'interrupted' };
                return;
            }
            yield {
                type: 'plugin_progress',
                pluginId: PLUGIN_NAME,
                current: iterationCount,
                max: maxIter,
            };
            // fresh context：清空 history，重置为入循环前的快照
            history.length = 0;
            history.push(...baseHistory);
            const freshContext = goalState.composeContext(iterationCount);
            const enhancedInput = `${freshContext}\n\n${currentInput}`;
            yield* runQuery(enhancedInput, {
                provider,
                history,
                signal,
                maxTurns: ctx.maxTurns,
                sessionState: ctx.sessionState,
            });
            if (signal?.aborted) {
                yield { type: 'interrupted' };
                return;
            }
            // 从本轮 history 抓最后一个 assistant 消息（runQuery 已 push 进去）
            const lastAssistantIdx = (() => {
                for (let i = history.length - 1; i >= 0; i--) {
                    if (history[i].role === 'assistant')
                        return i;
                }
                return -1;
            })();
            finalAssistantMsg =
                lastAssistantIdx >= 0 ? history[lastAssistantIdx] : null;
            const lastAssistantText = finalAssistantMsg
                ? typeof finalAssistantMsg.content === 'string'
                    ? finalAssistantMsg.content
                    : JSON.stringify(finalAssistantMsg.content)
                : '';
            // 每轮提取 <learning> 并落盘，让经验跨 fresh-context 迭代存活
            for (const lesson of extractLearnings(lastAssistantText)) {
                await goalState.appendLearning(`[迭代 ${iterationCount}] ${lesson}`);
            }
            if (isComplete(lastAssistantText)) {
                // 哨兵：可能来自任意阶段（包括 iter 1 的 PLAN），用 force 跳到 VERIFY
                await goalState.forceSetPhase(Phase.VERIFY, '检测到完成哨兵，进入验证');
                await goalState.appendProgress(`迭代 ${iterationCount}: 检测到完成哨兵，运行验证门...`);
                if (checks.length > 0) {
                    const vResult = await runVerification(checks);
                    if (!vResult.passed) {
                        consecutiveFailures++;
                        await goalState.appendLearning(`[迭代 ${iterationCount}] 声称完成但验证未通过: ${vResult.summary}`);
                        // 验证未通过不是致命错误：降级为 warning（不映射 stop_reason），
                        // 再补一条进度保活进度条，避免 UI 误以为循环停了
                        yield {
                            type: 'warning',
                            message: `⚠️ 验证未通过: ${vResult.summary}。继续尝试...`,
                        };
                        yield {
                            type: 'plugin_progress',
                            pluginId: PLUGIN_NAME,
                            current: iterationCount,
                            max: maxIter,
                            message: '验证未通过，回到构建修复',
                        };
                        // 跨平台决策记录（不再只靠 POSIX stop-hook）
                        await goalState.recordDecision({
                            iteration: iterationCount,
                            phase: Phase.VERIFY,
                            summary: '验证门',
                        }, ['判定完成', '回退继续'], '回退继续', vResult.summary.slice(0, 200));
                        if (consecutiveFailures >= 3) {
                            await goalState.forceSetPhase(Phase.HEAL, `连续 ${consecutiveFailures} 次验证失败`);
                        }
                        else {
                            // 语义诚实：VERIFY→BUILD 不是合法边，用 force 而非借合法边
                            await goalState.forceSetPhase(Phase.BUILD, '验证未通过，回到构建修复');
                        }
                        continue;
                    }
                    await goalState.appendProgress(`✅ 验证通过: ${vResult.summary}`);
                }
                await goalState.setPhase(Phase.DONE, 'goal complete & verified');
                // 成功路径决策记录（vResult 定义在 checks.length>0 块内、此处作用域不可见，
                // 故 reasoning 统一写 '目标完成'，不引用 vResult）
                await goalState.recordDecision({
                    iteration: iterationCount,
                    phase: Phase.VERIFY,
                    summary: '验证门',
                }, ['判定完成', '回退继续'], '判定完成', '目标完成');
                yield {
                    type: 'plugin_progress',
                    pluginId: PLUGIN_NAME,
                    current: iterationCount,
                    max: maxIter,
                    message: '✅ 完成并通过验证',
                };
                return;
            }
            // NEED_GOAL_REVISION 逃生门：目标本身不可达，干净终止请人工修订
            // （区别于 NEED_REPLAN —— 那是换方案，这是换目标）
            if (hasNeedGoalRevisionSentinel(lastAssistantText)) {
                await goalState.forceSetPhase(Phase.DONE, 'agent 报告目标本身需修订');
                await goalState.appendLearning('[NEED_GOAL_REVISION] Agent 认为目标本身有问题，需人工修订');
                await goalState.recordDecision({
                    iteration: iterationCount,
                    phase: goalState.currentPhase,
                    summary: 'NEED_GOAL_REVISION',
                }, ['继续硬上', '停止请人工改目标'], '停止请人工改目标', '目标本身不可达');
                yield {
                    type: 'warning',
                    message: '🛑 Agent 判断目标本身需要人工修订，循环已停止。请检查并调整目标后重试。',
                };
                return;
            }
            if (hasNeedReplanSentinel(lastAssistantText)) {
                await goalState.forceSetPhase(Phase.PLAN, 'agent 请求重新规划');
                await goalState.appendLearning('[NEED_REPLAN] Agent 认为当前方案不可行，需要重新规划');
                await goalState.appendProgress('Agent 请求 NEED_REPLAN，回 PLAN 阶段');
                await goalState.recordDecision({
                    iteration: iterationCount,
                    phase: goalState.currentPhase,
                    summary: 'NEED_REPLAN',
                }, ['继续当前方案', '回 PLAN 重规划'], '回 PLAN 重规划', 'agent 认为方案不可行');
                consecutiveFailures = 0;
                continue;
            }
            // PLAN 阶段跑过一轮还没哨兵，认为规划已完成，自动推进 BUILD
            if (goalState.currentPhase === Phase.PLAN && iterationCount >= 2) {
                await goalState.setPhase(Phase.BUILD, '规划阶段已完成，进入构建');
            }
            // Stop-hook 咨询式调用：block 才把 reason 注入下一轮 prompt
            // pass / 报错都不再终止循环；终止由 sentinel/maxIter/abort/NEED_REPLAN 独占
            const hookResult = await executeStopHook(PLUGIN_ROOT, lastAssistantText);
            if (hookResult.decision === 'block' && hookResult.reason) {
                currentInput = hookResult.reason;
                consecutiveFailures = 0;
                await goalState.recordDecision({
                    iteration: iterationCount,
                    phase: goalState.currentPhase,
                    summary: 'stop-hook 反馈',
                }, ['继续循环', '终止'], '继续循环', hookResult.reason.slice(0, 200));
                if (hookResult.systemMessage) {
                    baseHistory.push({
                        role: 'user',
                        content: `[Plugin Stop Hook] ${hookResult.systemMessage}`,
                    });
                }
                await goalState.appendProgress(`迭代 ${iterationCount}: Stop hook block，注入反馈继续`);
            }
            else {
                await goalState.appendProgress(`迭代 ${iterationCount}: 无哨兵 / hook pass，继续下一轮`);
            }
        } while (true);
    }
    finally {
        // 收尾：把循环里临时累积的 history 还原成 baseHistory + 最后一轮 assistant
        // 这样 TUI 上看到的就是"一次问答"，而不是 N 轮重复
        history.length = 0;
        history.push(...baseHistory);
        if (finalAssistantMsg) {
            history.push(finalAssistantMsg);
        }
        await goalState.cleanup();
    }
}
const api = {
    async *runCommand(commandName, args, ctx) {
        if (commandName === 'ralph-loop') {
            yield* runRalphLoop(args, ctx);
            return;
        }
        // 其它命令（help / cancel-ralph）→ 不接管，让框架走声明式 fallback
        yield {
            type: 'error',
            error: `ralph-wiggum: 命令 /${commandName} 未由 plugin.ts 接管`,
        };
    },
};
export default api;
