(() => {
  if (window.MMWQianchuanAgentPlanner) return;

  const SAFE_STEPS = new Set([
    "click_control", "hover_value", "click_trigger", "type_value", "confirm_platform", "wait"
  ]);
  const STEP_ROLES = {
    click_control: new Set(["control"]),
    hover_value: new Set(["value"]),
    click_trigger: new Set(["trigger"]),
    type_value: new Set(["input"]),
    confirm_platform: new Set(["confirm"])
  };
  const RECOVERABLE_PLAN_ERROR = /未识别到唯一|存在多个同等入口|未找到唯一|悬停后未出现唯一|没有唯一安全的确认按钮|未按预期出现|无法规划下一步/;

  function createPlanner(options = {}) {
    const requestInference = options.requestInference;
    const captureVisual = options.captureVisual;
    const attemptedFingerprints = new Set();
    const maxInferenceCalls = Math.max(1, Math.min(3, Number(options.maxInferenceCalls) || 2));
    let inferenceCalls = 0;

    async function plan(task, observation, context, localPlan) {
      try {
        const decision = await localPlan(task, observation, context);
        return { ...decision, plannerSource: "local", visualUsed: false, plannerTokens: 0 };
      } catch (error) {
        if (!canRecover(error, observation) || typeof requestInference !== "function") throw error;
        const snapshot = compileObservation(task, observation, context);
        const allowedSteps = deriveAllowedSteps(snapshot, context);
        if (!allowedSteps.length || !snapshot.candidates.length || inferenceCalls >= maxInferenceCalls) throw error;
        const recentSteps = snapshot.previousSteps.slice(-4).join(">");
        const attemptKey = `${snapshot.pageFingerprint}:${allowedSteps.join(",")}:${recentSteps}`;
        if (attemptedFingerprints.has(attemptKey)) throw error;
        attemptedFingerprints.add(attemptKey);
        inferenceCalls += 1;

        let visualDataUrl = "";
        if (options.allowVisual === true && shouldUseVisual(snapshot) && typeof captureVisual === "function") {
          const visual = await captureVisual({ clip: visualClip(observation), task, snapshot }).catch(() => null);
          const captured = String(visual?.dataUrl || "");
          visualDataUrl = captured.length <= 1_200_000 ? captured : "";
        }
        const response = await requestInference({ task, observation: snapshot, allowedSteps, visualDataUrl });
        return validateDecision(response, snapshot, allowedSteps, task.desiredValue, Boolean(visualDataUrl));
      }
    }

    return Object.freeze({ plan });
  }

  function canRecover(error, observation) {
    const message = String(error?.message || error || "");
    if (!RECOVERABLE_PLAN_ERROR.test(message)) return false;
    if (observation?.blocker || observation?.securityText || Number(observation?.dialogCount) > 1) return false;
    return true;
  }

  function compileObservation(task = {}, observation = {}, context = {}) {
    const candidates = (Array.isArray(observation.candidates) ? observation.candidates : [])
      .slice(0, 24)
      .map((item) => ({
        id: String(item.id || "").slice(0, 40),
        role: String(item.role || "").slice(0, 24),
        text: String(item.text || "").slice(0, 240),
        ariaLabel: String(item.ariaLabel || "").slice(0, 160),
        title: String(item.title || "").slice(0, 160),
        tag: String(item.tag || "").slice(0, 24),
        disabled: item.disabled === true,
        rect: compactRect(item.rect)
      }))
      .filter((item) => item.id && STEP_ROLES_VALUES.has(item.role) && !item.disabled);
    return {
      task: {
        action: String(task.action || ""),
        targetId: String(task.targetId || "").slice(0, 32),
        targetName: String(task.targetName || "").slice(0, 160),
        desiredValue: Number.isFinite(Number(task.desiredValue)) ? Number(task.desiredValue) : null
      },
      pageFingerprint: String(observation.pageFingerprint || "").slice(0, 64),
      url: String(observation.url || location.href).slice(0, 600),
      rowText: String(observation.rowText || "").slice(0, 900),
      dialogText: String(observation.dialogText || "").slice(0, 900),
      dialogCount: Number(observation.dialogCount) || 0,
      checked: typeof observation.checked === "boolean" ? observation.checked : null,
      currentValue: finiteOrNull(observation.currentValue),
      currentBudget: finiteOrNull(observation.currentBudget),
      currentRoi: finiteOrNull(observation.currentRoi),
      previousSteps: (Array.isArray(context.audit) ? context.audit : []).slice(-8).map((item) => String(item.action || "")),
      candidates
    };
  }

  const STEP_ROLES_VALUES = new Set(Object.values(STEP_ROLES).flatMap((roles) => [...roles]));

  function deriveAllowedSteps(snapshot, context = {}) {
    const roles = new Set(snapshot.candidates.map((item) => item.role));
    const allowed = [];
    for (const [step, validRoles] of Object.entries(STEP_ROLES)) {
      if ([...validRoles].some((role) => roles.has(role))) allowed.push(step);
    }
    if ((context.audit || []).length) allowed.push("wait");
    return [...new Set(allowed)].filter((step) => SAFE_STEPS.has(step));
  }

  function validateDecision(response, snapshot, allowedSteps, desiredValue, visualUsed) {
    const payload = response?.payload || response || {};
    const decision = payload.decision && typeof payload.decision === "object" ? payload.decision : payload;
    if (["x", "y", "coordinates", "selector", "script"].some((key) => Object.hasOwn(decision, key))) {
      throw new Error("模型返回了坐标或脚本，AI 接管已停止。");
    }
    const type = String(decision.action || decision.type || "").trim();
    if (type === "stop") throw new Error(String(decision.reason || "模型无法安全确认下一步，AI 接管已停止。"));
    if (!SAFE_STEPS.has(type) || !allowedSteps.includes(type)) throw new Error("模型返回了非白名单单步，AI 接管已停止。");
    const confidence = Number(decision.confidence);
    if (!Number.isFinite(confidence) || confidence < 0.75) throw new Error("模型对当前候选的判断置信度不足，AI 接管已停止。");
    const candidateId = String(decision.candidateId || decision.candidate_id || "").trim();
    const candidate = snapshot.candidates.find((item) => item.id === candidateId);
    if (type !== "wait") {
      if (!candidate || !STEP_ROLES[type]?.has(candidate.role)) throw new Error("模型没有选择当前观察中的有效候选控件，AI 接管已停止。");
    }
    const action = { type };
    if (candidateId) action.candidateId = candidateId;
    if (type === "type_value") action.value = Number(desiredValue);
    return {
      action,
      message: String(decision.reason || "正在按当前页面重新规划单步操作").slice(0, 180),
      waitMs: type === "wait" ? 800 : 650,
      plannerSource: "model",
      visualUsed: visualUsed || payload.visualUsed === true,
      plannerTokens: Math.max(0, Number(payload?.usage?.prompt_tokens || payload?.usage?.input_tokens || 0) || 0)
    };
  }

  function shouldUseVisual(snapshot) {
    if (snapshot.candidates.length > 1) return true;
    const candidate = snapshot.candidates[0];
    return Boolean(candidate && !candidate.text && !candidate.ariaLabel && !candidate.title);
  }

  function visualClip(observation = {}) {
    const element = observation.dialog || observation.row;
    const rect = element?.getBoundingClientRect?.();
    if (!rect || !(rect.width > 0 && rect.height > 0)) return null;
    const margin = 18;
    const left = Math.max(0, rect.left - margin);
    const top = Math.max(0, rect.top - margin);
    const right = Math.min(window.innerWidth, rect.right + margin);
    const bottom = Math.min(window.innerHeight, rect.bottom + margin);
    return compactRect({ left, top, width: right - left, height: bottom - top });
  }

  function compactRect(rect) {
    if (!rect) return null;
    const left = Number(rect.left ?? rect.x);
    const top = Number(rect.top ?? rect.y);
    const width = Number(rect.width);
    const height = Number(rect.height);
    if (![left, top, width, height].every(Number.isFinite) || width <= 0 || height <= 0) return null;
    return { left: Math.round(left), top: Math.round(top), width: Math.round(width), height: Math.round(height) };
  }

  function finiteOrNull(value) {
    const number = Number(value);
    return Number.isFinite(number) ? number : null;
  }

  window.MMWQianchuanAgentPlanner = Object.freeze({ createPlanner, compileObservation, deriveAllowedSteps });
})();
