import { mkdir, appendFile, writeFile, unlink, rmdir } from 'node:fs/promises';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
export var Phase;
(function (Phase) {
    Phase["PLAN"] = "plan";
    Phase["BUILD"] = "build";
    Phase["VERIFY"] = "verify";
    Phase["HEAL"] = "heal";
    Phase["DONE"] = "done";
})(Phase || (Phase = {}));
const VALID_PHASES = new Set(Object.values(Phase));
/**
 * 阶段转移表使用说明（诚实注释）：
 *
 * 这张表里的 event 名（plan_complete / tests_failing / all_pass 等）是**描述性的**，
 * 阶段实际由驱动（plugin.ts）用启发式推进或 `forceSetPhase` 直接设置——
 * **不是 LLM 驱动的事件状态机**。
 *
 * `setPhase` 只用这张表做「目标阶段是否可达」的合法性校验：
 * 只要 PHASE_TRANSITIONS[from] 的 values 里包含 to，就视为合法，跟传入的 `reason` 字符串无关。
 * `reason` 仅是给人看的日志文本，不参与任何匹配或校验。
 *
 * 想跳过校验强制切阶段，用 `forceSetPhase`。
 */
const PHASE_TRANSITIONS = {
    [Phase.PLAN]: {
        plan_complete: Phase.BUILD,
        stuck: Phase.PLAN,
    },
    [Phase.BUILD]: {
        task_complete: Phase.VERIFY,
        need_replan: Phase.PLAN,
        tests_failing: Phase.HEAL,
    },
    [Phase.VERIFY]: {
        all_pass: Phase.BUILD,
        failures: Phase.HEAL,
        goal_complete: Phase.DONE,
    },
    [Phase.HEAL]: {
        fixed: Phase.VERIFY,
        cannot_fix_locally: Phase.PLAN,
    },
    [Phase.DONE]: {},
};
function isLegalTransition(from, to) {
    if (from === to)
        return true;
    const allowed = PHASE_TRANSITIONS[from];
    if (!allowed)
        return false;
    return Object.values(allowed).includes(to);
}
const LEARNINGS_TAIL_LINES = 20;
/** 每个阶段给 LLM 的一行行动指令（fresh-context 下 LLM 看不到 SKILL.md，靠这个 steer 行为）。 */
const PHASE_DIRECTIVES = {
    [Phase.PLAN]: '分析任务、拆解步骤、识别风险与未知项；先想清楚再动手。',
    [Phase.BUILD]: '按计划实现，一次只聚焦一个子任务，落到具体文件改动。',
    [Phase.VERIFY]: '运行测试 / 检查输出 / 核对验收标准，用证据确认，别凭感觉。',
    [Phase.HEAL]: '已多次验证失败：停止猜测，定位根因（读报错、加日志、做最小复现）再修，避免回归。',
    [Phase.DONE]: '目标已达成。',
};
export class GoalState {
    dir;
    constructor(workspaceDir, sessionTag) {
        const suffix = sessionTag ? `-${sessionTag}` : '';
        this.dir = join(workspaceDir, `.minimal-agent${suffix}`);
    }
    async reset() {
        const files = ['goal.md', 'completion.md', 'phase.md', 'progress.md', 'learnings.md', 'decisions.md'];
        for (const f of files) {
            try {
                await unlink(join(this.dir, f));
            }
            catch {
            }
        }
    }
    async init(goal, criteria) {
        await mkdir(this.dir, { recursive: true });
        const files = {
            goal: `# 不可变目标 (IMMUTABLE GOAL)\n\n${goal}\n`,
            completion: JSON.stringify(criteria, null, 2),
            phase: Phase.PLAN,
            progress: '',
            learnings: '',
            decisions: '',
        };
        for (const [name, content] of Object.entries(files)) {
            const path = join(this.dir, `${name}.md`);
            if (!existsSync(path)) {
                await writeFile(path, content);
            }
        }
    }
    get goal() {
        try {
            return readFileSync(join(this.dir, 'goal.md'), 'utf8').trim();
        }
        catch {
            return '';
        }
    }
    get completionCriteria() {
        try {
            const raw = readFileSync(join(this.dir, 'completion.md'), 'utf8').trim();
            return JSON.parse(raw);
        }
        catch {
            return [];
        }
    }
    get currentPhase() {
        try {
            const raw = readFileSync(join(this.dir, 'phase.md'), 'utf8').trim();
            if (VALID_PHASES.has(raw)) {
                return raw;
            }
        }
        catch {
        }
        return Phase.PLAN;
    }
    /**
     * 切换阶段。`reason` 是给人看的日志文本，不参与校验。
     * 校验只看 from→to 是否在 PHASE_TRANSITIONS 表里有路径（任意 event 通向 to 即合法）。
     * 想绕开 FSM 用 forceSetPhase。
     */
    async setPhase(phase, reason) {
        if (!VALID_PHASES.has(phase)) {
            throw new Error(`Invalid phase: ${phase}`);
        }
        const current = this.currentPhase;
        if (!isLegalTransition(current, phase)) {
            throw new Error(`非法阶段切换: ${current} → ${phase}。该目标阶段不在 PHASE_TRANSITIONS[${current}] 的可达集合内，需要走 forceSetPhase。`);
        }
        await writeFile(join(this.dir, 'phase.md'), phase);
        await this.appendProgress(`PHASE → ${phase}: ${reason}`);
    }
    async forceSetPhase(phase, reason) {
        if (!VALID_PHASES.has(phase)) {
            throw new Error(`Invalid phase: ${phase}`);
        }
        await writeFile(join(this.dir, 'phase.md'), phase);
        await this.appendProgress(`PHASE → ${phase}: ${reason}`);
    }
    async appendProgress(line) {
        const timestamp = new Date().toLocaleTimeString('en-US', { hour12: false });
        await appendFile(join(this.dir, 'progress.md'), `[${timestamp}] ${line}\n`);
    }
    tailProgress(n) {
        try {
            const content = readFileSync(join(this.dir, 'progress.md'), 'utf8');
            const lines = content.trim().split('\n').filter(Boolean);
            return lines.slice(-n).join('\n');
        }
        catch {
            return '';
        }
    }
    async appendLearning(lesson) {
        await appendFile(join(this.dir, 'learnings.md'), `- ${lesson}\n`);
    }
    get learnings() {
        try {
            const raw = readFileSync(join(this.dir, 'learnings.md'), 'utf8').trim();
            if (!raw)
                return '';
            const lines = raw.split('\n').filter(Boolean);
            return lines.slice(-LEARNINGS_TAIL_LINES).join('\n');
        }
        catch {
            return '';
        }
    }
    async recordDecision(ctx, options, chosen, reasoning) {
        const entry = {
            iteration: ctx.iteration,
            phase: ctx.phase,
            contextSummary: ctx.summary,
            options,
            chosen,
            reasoning,
        };
        const line = JSON.stringify(entry);
        await appendFile(join(this.dir, 'decisions.md'), `${line}\n`);
    }
    summarizeDecisions(maxEntries = 5) {
        try {
            const content = readFileSync(join(this.dir, 'decisions.md'), 'utf8');
            const lines = content.trim().split('\n').filter(Boolean);
            const entries = [];
            for (const line of lines.slice(-maxEntries * 2)) {
                try {
                    entries.push(JSON.parse(line));
                }
                catch {
                }
            }
            const sliced = entries.slice(-maxEntries);
            // 无有效条目（文件存在但为空）时也统一返回占位文本
            if (sliced.length === 0)
                return '(无决策记录)';
            return sliced
                .map((e) => `迭代 ${e.iteration}（${e.phase}）: 在 [${e.options.join(', ')}] 中选择了 **${e.chosen}**，原因：${e.reasoning.slice(0, 80)}`)
                .join('\n');
        }
        catch {
            return '(无决策记录)';
        }
    }
    composeContext(iteration) {
        const sections = [];
        // 不可变目标段：去掉 NEED_GOAL_REVISION 提示（已移到通信协议段统一说明）
        sections.push(`# 不可变目标 (IMMUTABLE GOAL)\n${this.goal}\n⚠️ 你不能修改或扩大上述目标。`);
        // 当前阶段 + 对应的一行行动指令
        sections.push(`# 当前阶段: ${this.currentPhase.toUpperCase()}\n${PHASE_DIRECTIVES[this.currentPhase]}`);
        // 与循环驱动的通信协议：哨兵约定 + learning 标记
        sections.push([
            '# 与循环驱动的通信协议（务必遵守）',
            '- 任务**真正完成**时输出 `<promise>DONE</promise>`，驱动会运行验证门确认。',
            '- 当前方案走不通、要换打法（目标不变）时输出 `<PROMISE>NEED_REPLAN</PROMISE>`。',
            '- **目标本身**有问题、不改目标就无法完成时输出 `<PROMISE>NEED_GOAL_REVISION</PROMISE>`，驱动会停止并请人工修订。',
            '- 踩到的坑 / 教训用 `<learning>一句话</learning>` 标记，驱动会自动存档供后续迭代参考。',
            '⚠️ 切勿为逃出循环而谎报完成——只有陈述完全为真时才输出完成哨兵。',
        ].join('\n'));
        const lrn = this.learnings;
        if (lrn) {
            sections.push(`# 关键教训（必须遵守，避免重复踩坑）\n${lrn}`);
        }
        const decisions = this.summarizeDecisions(3);
        if (decisions !== '(无决策记录)') {
            sections.push(`# 历史关键决策（请参考，避免重复试错）\n${decisions}`);
        }
        const recentProgress = this.tailProgress(10);
        if (recentProgress) {
            sections.push(`# 最近进度\n${recentProgress}`);
        }
        sections.push(`---\n\n# 本轮任务 (迭代 ${iteration})`);
        return sections.join('\n\n---\n\n');
    }
    async cleanup() {
        const files = ['goal.md', 'completion.md', 'phase.md', 'progress.md', 'learnings.md', 'decisions.md'];
        for (const f of files) {
            try {
                await unlink(join(this.dir, f));
            }
            catch {
            }
        }
        try {
            await rmdir(this.dir);
        }
        catch {
        }
    }
}
