package com.gzzm.lobster.guardrails;

import com.gzzm.lobster.llm.ToolCall;

import java.util.ArrayList;
import java.util.List;

/**
 * AgentLoopDetector —— Agent 循环检测 / Agent loop detection.
 *
 * <p>两类循环：
 * <ol>
 *   <li>连续 {@value #MAX_IDENTICAL_CALLS} 次相同工具 + 相同参数 → {@code loop_detected}</li>
 *   <li>超过 {@value #MAX_TURNS_WITHOUT_PROGRESS} 轮无强进展信号 → {@code no_progress}</li>
 * </ol>
 *
 * <p><b>V2 进展判定（2026-04）</b>：不再把 {@code status==ok} 当成"有进展"。学 Claude Code
 * 的 TodoWrite 模式 —— 进展由模型自己声明，runtime 只识别两个强信号：
 * <ul>
 *   <li><b>Plan 状态迁移</b>：update_plan 调用后出现新的 completed / in_progress 迁移</li>
 *   <li><b>新 artifact 产出</b>：工具 {@code producedArtifactIds} 非空</li>
 * </ul>
 * 不触发这两类信号的 ok 不算进展 —— 空搜索、重复读、只回显都会被正确判死循环。
 *
 * <p>为保持向后兼容，旧接口 {@link #recordProgress(boolean)} 保留，内部等价于
 * "是否发生了任何强信号"。新代码优先调 {@link #recordProgress(ProgressSignal)}。
 *
 * <p>调用契约（每轮两次）：
 * <ol>
 *   <li>{@link #recordAndCheckIdenticalLoop(List)} —— 执行工具<b>前</b>，只看重复调用</li>
 *   <li>{@link #recordProgress(ProgressSignal)} —— 执行工具<b>后</b>一次，判断是否推进</li>
 * </ol>
 */
public class AgentLoopDetector {

    public static final int MAX_IDENTICAL_CALLS = 3;
    public static final int MAX_TURNS_WITHOUT_PROGRESS = 8;

    private final List<ToolCall> history = new ArrayList<>();
    private int turnsWithoutProgress = 0;

    /** 记录工具调用并检查是否发生相同调用循环。 */
    public LoopCheckResult recordAndCheckIdenticalLoop(List<ToolCall> turnCalls) {
        if (turnCalls != null) {
            for (ToolCall tc : turnCalls) history.add(tc);
        }
        if (history.size() < MAX_IDENTICAL_CALLS) return LoopCheckResult.ok();
        ToolCall last = history.get(history.size() - 1);
        int same = 1;
        for (int i = history.size() - 2; i >= 0 && same < MAX_IDENTICAL_CALLS; i--) {
            if (last.equalsInvocation(history.get(i))) same++;
            else break;
        }
        if (same >= MAX_IDENTICAL_CALLS) {
            return new LoopCheckResult(true, "loop_detected",
                    "Same tool+args invoked " + same + " times in a row: " + last.getName());
        }
        return LoopCheckResult.ok();
    }

    /**
     * V2 新接口 / New API: record progress with a structured signal.
     *
     * <p>强信号（plan 迁移 / artifact 产出 / pending 创建）重置计数；否则累加。
     * {@code pendingCreated=true} 虽然 run 会在本轮结束，但对"进展"计数也算是强信号
     * —— 用户审批流程打开是实打实的推进。
     */
    public LoopCheckResult recordProgress(ProgressSignal signal) {
        boolean strong = signal != null && signal.isStrong();
        if (strong) {
            turnsWithoutProgress = 0;
            return LoopCheckResult.ok();
        }
        turnsWithoutProgress++;
        if (turnsWithoutProgress >= MAX_TURNS_WITHOUT_PROGRESS) {
            return new LoopCheckResult(true, "no_progress",
                    "Detected no meaningful progress for " + turnsWithoutProgress + " turns");
        }
        return LoopCheckResult.ok();
    }

    /** 旧接口保留 / Legacy boolean-signal overload. */
    public LoopCheckResult recordProgress(boolean madeProgress) {
        return recordProgress(madeProgress ? ProgressSignal.legacyOk() : ProgressSignal.none());
    }

    /** 旧接口兼容 / Backward compatible one-shot observe. */
    public LoopCheckResult observe(List<ToolCall> turnCalls, boolean madeProgress) {
        LoopCheckResult r = recordAndCheckIdenticalLoop(turnCalls);
        if (r.isTripped()) return r;
        return recordProgress(madeProgress);
    }

    public static final class LoopCheckResult {
        private final boolean tripped;
        private final String reason;
        private final String detail;

        public LoopCheckResult(boolean tripped, String reason, String detail) {
            this.tripped = tripped; this.reason = reason; this.detail = detail;
        }

        public static LoopCheckResult ok() { return new LoopCheckResult(false, null, null); }

        public boolean isTripped() { return tripped; }
        public String getReason() { return reason; }
        public String getDetail() { return detail; }
    }

    /**
     * 进展信号 / Progress signal.
     *
     * <p>本轮是否观察到强信号。由主循环在收完所有工具结果后构造：
     * <ul>
     *   <li>{@link #planStateChanged} —— update_plan 返回的 snapshot 里有 status 迁移</li>
     *   <li>{@link #artifactProduced} —— 任一工具产出了新 artifact</li>
     *   <li>{@link #pendingCreated} —— 本轮创建了 pending request（进入审批）</li>
     * </ul>
     *
     * <p>只要其中任一为 true 就记为强信号。
     */
    public static final class ProgressSignal {
        public final boolean planStateChanged;
        public final boolean artifactProduced;
        public final boolean pendingCreated;

        public ProgressSignal(boolean planStateChanged, boolean artifactProduced, boolean pendingCreated) {
            this.planStateChanged = planStateChanged;
            this.artifactProduced = artifactProduced;
            this.pendingCreated = pendingCreated;
        }

        public boolean isStrong() {
            return planStateChanged || artifactProduced || pendingCreated;
        }

        public static ProgressSignal none() {
            return new ProgressSignal(false, false, false);
        }

        /**
         * 旧 boolean 适配 / Adapter from legacy boolean.
         *
         * <p>老调用方传 {@code true} 意味着"至少有一个工具 ok"，保守当成 artifact 产出。
         * 新代码应改用 {@link #ProgressSignal(boolean, boolean, boolean)} 精确传递。
         */
        public static ProgressSignal legacyOk() {
            return new ProgressSignal(false, true, false);
        }
    }
}
