// popup-modal.js — v4.8.65
// 通用 action dialog（苹果极简白，比 alert/confirm 美观），目前给"辩论回答不足"用
// API: window.ChatModal.show({ tone, icon, title, message, tip, primary, secondary, cancel })
//   - primary / secondary 是 { label, onClick }，cancel 是 { label } 仅关 modal
//   - tone: "warning" | "info"（控制图标圈和标题色）
(function () {
  let activeOverlay = null;
  // v5.0.0-beta fix: escListener 必须在 close 时统一移除，否则点按钮关 modal 后
  // listener 泄漏在 document 上，下次用户按 Ctrl+Enter 想发消息时会被旧 listener
  // 误捕获并重新触发 primary.onClick（典型坑：彻底重置后按 Enter 又重置一遍）
  let activeEscListener = null;
  let activeOnClose = null;   // v5.0.47: confirm() 用 — 非主按钮关闭（取消/Esc/点遮罩）时回调 resolve(false)

  function close(skipOnClose) {
    if (activeEscListener) {
      document.removeEventListener("keydown", activeEscListener);
      activeEscListener = null;
    }
    const onClose = activeOnClose; activeOnClose = null;
    const fire = () => { if (onClose && !skipOnClose) { try { onClose(); } catch (_) {} } };
    if (!activeOverlay) { fire(); return; }
    activeOverlay.classList.remove("show");
    const node = activeOverlay;
    activeOverlay = null;
    setTimeout(() => { try { node.remove(); } catch (_) {} }, 180);
    fire();
  }

  function escapeHtml(s) {
    return String(s == null ? "" : s).replace(/[&<>"']/g, c => ({
      "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
    }[c]));
  }

  function show(opts) {
    close();
    const { tone = "info", icon = "ⓘ", title = "", message = "", tip = "",
            primary, secondary, cancel, onClose } = opts || {};

    const overlay = document.createElement("div");
    overlay.className = `arena-modal-overlay tone-${tone}`;
    overlay.innerHTML = `
      <div class="arena-modal" role="dialog" aria-modal="true" aria-labelledby="arena-modal-title">
        <div class="arena-modal-icon">${escapeHtml(icon)}</div>
        <div class="arena-modal-title" id="arena-modal-title">${escapeHtml(title)}</div>
        <div class="arena-modal-message">${escapeHtml(message)}</div>
        ${tip ? `<div class="arena-modal-tip">${escapeHtml(tip)}</div>` : ""}
        <div class="arena-modal-actions">
          ${secondary ? `<button type="button" class="arena-modal-btn secondary" data-role="secondary">${escapeHtml(secondary.label)}</button>` : ""}
          ${primary ? `<button type="button" class="arena-modal-btn primary" data-role="primary">${escapeHtml(primary.label)}</button>` : ""}
        </div>
        ${cancel ? `<button type="button" class="arena-modal-close" data-role="cancel" aria-label="${escapeHtml(cancel.label || "关闭")}">✕</button>` : ""}
      </div>`;
    document.body.appendChild(overlay);
    activeOverlay = overlay;
    activeOnClose = onClose || null;

    overlay.addEventListener("click", (e) => {
      const role = e.target?.dataset?.role;
      if (role === "primary") { close(true); try { primary?.onClick?.(); } catch (err) { console.warn(err); } }
      else if (role === "secondary") { close(true); try { secondary?.onClick?.(); } catch (err) { console.warn(err); } }
      else if (role === "cancel") close();        // 取消 → 触发 onClose（confirm resolve false）
      else if (e.target === overlay) close();      // 点遮罩关闭 → 同上
    });

    // v5.0.0-beta fix: 注册 escListener 时存到模块级 activeEscListener，
    // close() 统一移除（不再依赖 listener 自己 self-remove，避免点按钮关 modal 时泄漏）
    activeEscListener = function escListener(ev) {
      if (ev.key === "Escape") {
        close();   // 取消语义 → onClose（confirm resolve false）；close 内部会 removeEventListener
      } else if (ev.key === "Enter" && primary) {
        close(true);
        try { primary.onClick?.(); } catch (err) { console.warn(err); }
      }
    };
    document.addEventListener("keydown", activeEscListener);

    requestAnimationFrame(() => overlay.classList.add("show"));
  }

  // ── 辩论回答不足专用快捷封装 ──
  //   ctx: { haveCount, totalCount, missing: [{id, name, service}, ...] }
  //   handlers: { onReextract(missing), onSwitchAsk() }
  function showInsufficientResponses(ctx, handlers) {
    const { haveCount = 0, totalCount = 0, missing = [] } = ctx || {};
    const missingNames = missing.map(m => m.name || m.service).filter(Boolean);
    const missingDisplay = missingNames.length
      ? `还没收到回答的 AI：${missingNames.join(" · ")}`
      : "尚有 AI 未给出回答";
    const message = `辩论需要至少 2 个 AI 给出答案，当前只读到 ${haveCount} / ${totalCount} 个有效回答。`;
    show({
      tone: "warning",
      icon: "⚠",
      title: "暂无法开始辩论",
      message,
      tip: missingDisplay + "。可以重新提取一次回答，或先切到「同时提问」让所有 AI 各自回答完再回来辩论。",
      primary: { label: "重新提取所有回答", onClick: () => handlers?.onReextract?.(missing) },
      secondary: { label: "切到同时提问", onClick: () => handlers?.onSwitchAsk?.() },
      cancel: { label: "关闭" },
    });
  }

  // v4.9.0: 敏感信息守门员命中专用 modal
  //   ctx: { hits: Hit[], masked: string, original: string }
  //   handlers: { onMask(masked), onConfirm(original, hits), onCancel() }
  function showSensitiveBlocked(ctx, handlers) {
    const { hits = [], masked = "", original = "" } = ctx || {};
    const n = hits.length;

    // 命中清单 HTML — 每条一行 "类别 高亮原文"
    const hitsHtml = hits.map(h => `
      <div class="gk-hit-row">
        <span class="gk-hit-cat">${escapeHtml(h.category)}</span>
        <span class="gk-hit-text">${escapeHtml(h.text)}</span>
      </div>
    `).join("");

    // masked 预览 — 简单 escape + 把 <类别> 包成 highlight span
    const previewHtml = escapeHtml(masked).replace(
      /&lt;([^&]+?)&gt;/g,
      '<span class="gk-mask-tag">&lt;$1&gt;</span>'
    );

    close();   // 关掉可能已存在的 modal
    const overlay = document.createElement("div");
    overlay.className = "arena-modal-overlay tone-warning gatekeeper-modal";
    overlay.innerHTML = `
      <div class="arena-modal" role="dialog" aria-modal="true">
        <div class="arena-modal-icon">⚠</div>
        <div class="arena-modal-title">检测到 ${n} 处敏感信息</div>
        <div class="arena-modal-message">发送前请确认，避免内部信息流向外部 AI</div>

        <div class="gk-hits">
          <div class="gk-hits-label">命中项：</div>
          ${hitsHtml}
        </div>

        <div class="gk-preview">
          <div class="gk-preview-label">📝 自动打码后的预览：</div>
          <div class="gk-preview-body">${previewHtml}</div>
        </div>

        <div class="arena-modal-actions gk-actions">
          <button type="button" class="arena-modal-btn secondary" data-role="cancel">取消修改</button>
          <button type="button" class="arena-modal-btn primary"   data-role="mask">自动打码后发送</button>
          <button type="button" class="arena-modal-btn secondary" data-role="confirm">我确认无敏感 · 加入白名单</button>
        </div>

        <button type="button" class="arena-modal-close" data-role="cancel" aria-label="关闭">✕</button>
      </div>`;
    document.body.appendChild(overlay);
    activeOverlay = overlay;

    overlay.addEventListener("click", (e) => {
      const role = e.target?.dataset?.role;
      if (role === "mask") {
        close();
        try { handlers?.onMask?.(masked); } catch (err) { console.warn(err); }
      } else if (role === "confirm") {
        close();
        try { handlers?.onConfirm?.(original, hits); } catch (err) { console.warn(err); }
      } else if (role === "cancel") {
        close();
        try { handlers?.onCancel?.(); } catch (err) { console.warn(err); }
      } else if (e.target === overlay) {
        close();
        try { handlers?.onCancel?.(); } catch (err) { console.warn(err); }
      }
    });

    // v5.0.0-beta fix: 同上 — 用模块级 activeEscListener，close 统一移除避免泄漏
    activeEscListener = function escListener(ev) {
      if (ev.key === "Escape") {
        close();
        try { handlers?.onCancel?.(); } catch (err) {}
      }
    };
    document.addEventListener("keydown", activeEscListener);

    requestAnimationFrame(() => overlay.classList.add("show"));
  }

  // v5.0.11: 辩论 partial inject 警告专用 modal
  //   场景：handleDebateRound 候选 N 个 AI，实际 inject 成功 M 个（M < N），缺 N-M 个被静默丢
  //   ctx: { missing: [{id, name, service, error}], sentCount, totalCount, debateStarted,
  //          longPromptHint, maxPromptLen }
  //   handlers: { onResend(missing), onResendCompressed(missing), onSkip() }
  // v5.0.19: 第二按钮改为「压缩后补发」（原"跳过"是空操作，并入"关闭"）——
  //   超长 prompt 触发公司网关上传限额是补发失败的常见原因，压缩重发是对症操作
  function showPartialDebateInject(ctx, handlers) {
    const { missing = [], sentCount = 0, totalCount = 0, debateStarted = true, longPromptHint = false, maxPromptLen = 0 } = ctx || {};
    const missingNames = missing.map(m => m.name || m.service).filter(Boolean).join(" · ") || "未知 AI";
    const firstErr = missing.find(m => m.error)?.error || "";
    const errorTip = firstErr ? `失败原因：${firstErr}` : "可能是 tab 失联、注入超时或页面未就绪";
    const lenTip = longPromptHint
      ? `本轮 prompt 最长 ${maxPromptLen} 字，可能被公司网关/站点上传限额拦截，建议「压缩后补发」。`
      : "";

    const title = debateStarted
      ? "辩论部分发送成功 · 有 AI 漏掉了"
      : "辩论发送失败 · 有效接收方不足";
    const message = debateStarted
      ? `辩论 prompt 已发给 ${sentCount} / ${totalCount} 个 AI，${missing.length} 个 AI 漏掉了。`
      : `仅 ${sentCount} / ${totalCount} 个 AI 接收成功，不足以开始辩论。`;
    const primaryLabel = missing.length === 1
      ? `🔄 补发给 ${missingNames}`
      : `🔄 补发给 ${missing.length} 个 AI`;

    show({
      tone: "warning",
      icon: "⚠",
      title,
      message,
      tip: `缺失：${missingNames}。${errorTip}${lenTip ? `\n${lenTip}` : ""}`,
      primary: { label: primaryLabel, onClick: () => handlers?.onResend?.(missing) },
      secondary: { label: "🗜 压缩后补发", onClick: () => (handlers?.onResendCompressed || handlers?.onResend)?.(missing) },
      cancel: { label: debateStarted ? `⏭ 跳过 · 用 ${sentCount} 个 AI 继续` : "⏭ 关闭" },
    });
  }

  // v5.0.30: 长文本查看器（查看本轮发出的 prompt 全文）— 可滚动 pre + 复制全文
  function showLongText(opts) {
    const { title = "全文", text = "" } = opts || {};
    close();
    const overlay = document.createElement("div");
    overlay.className = "arena-modal-overlay tone-info prompt-viewer-modal";
    overlay.innerHTML = `
      <div class="arena-modal" role="dialog" aria-modal="true" aria-labelledby="arena-modal-title">
        <div class="arena-modal-title" id="arena-modal-title">${escapeHtml(title)}</div>
        <div class="arena-modal-message pv-meta">${(text || "").length} 字 · 本轮实际发出的完整内容</div>
        <pre class="pv-body">${escapeHtml(text)}</pre>
        <div class="arena-modal-actions">
          <button type="button" class="arena-modal-btn secondary" data-role="copy">📋 复制全文</button>
          <button type="button" class="arena-modal-btn primary" data-role="cancel">关闭</button>
        </div>
        <button type="button" class="arena-modal-close" data-role="cancel" aria-label="关闭">✕</button>
      </div>`;
    document.body.appendChild(overlay);
    activeOverlay = overlay;

    overlay.addEventListener("click", (e) => {
      const role = e.target?.closest?.("[data-role]")?.dataset?.role;
      if (role === "cancel" || e.target === overlay) { close(); return; }
      if (role === "copy") {
        const btn = overlay.querySelector('[data-role="copy"]');
        navigator.clipboard?.writeText(text).then(() => {
          if (btn) { btn.textContent = "✓ 已复制"; setTimeout(() => { btn.textContent = "📋 复制全文"; }, 1200); }
        }).catch(() => {
          if (btn) { btn.textContent = "复制失败"; setTimeout(() => { btn.textContent = "📋 复制全文"; }, 1200); }
        });
      }
    });

    activeEscListener = function escListener(ev) {
      if (ev.key === "Escape") close();
    };
    document.addEventListener("keydown", activeEscListener);
    requestAnimationFrame(() => overlay.classList.add("show"));
  }

  // v5.0.47: 通用 alert / confirm（替代原生）+ 引导高亮（弹窗关闭后高亮某元素，告诉用户"去哪操作"）
  function spotGuide(selector) {
    if (!selector) return;
    setTimeout(() => {
      try {
        document.querySelectorAll(".arena-spot").forEach(e => e.classList.remove("arena-spot"));
        const el = document.querySelector(selector);
        if (!el) return;
        el.classList.add("arena-spot");
        try { el.scrollIntoView({ behavior: "smooth", block: "nearest" }); } catch (_) {}
        setTimeout(() => el.classList.remove("arena-spot"), 3500);
      } catch (_) {}
    }, 240);   // 等关闭动画结束再高亮，避免被正在淡出的遮罩盖住
  }
  function alert(message, opts) {
    opts = opts || {};
    show({
      tone: opts.tone || "info",
      icon: opts.icon || (opts.tone === "warning" ? "⚠" : "ⓘ"),
      title: opts.title || (opts.tone === "warning" ? "出错了" : "提示"),
      message: message || "",
      tip: opts.tip || "",
      primary: { label: opts.okLabel || "知道了", onClick: () => { if (opts.guide) spotGuide(opts.guide); if (typeof opts.onOk === "function") opts.onOk(); } },
      cancel: { label: "关闭" },
    });
  }
  function confirm(opts) {
    opts = (typeof opts === "string") ? { message: opts } : (opts || {});
    return new Promise((resolve) => {
      let done = false;
      const fin = (v) => { if (!done) { done = true; resolve(v); } };
      show({
        tone: opts.tone || "warning",
        icon: opts.icon || "❓",
        title: opts.title || "确认操作",
        message: opts.message || "",
        tip: opts.tip || "",
        primary: { label: opts.okLabel || "确认", onClick: () => fin(true) },
        cancel: { label: opts.cancelLabel || "取消" },
        onClose: () => fin(false),
      });
    });
  }

  window.ChatModal = { show, close, alert, confirm, spotGuide, showInsufficientResponses, showSensitiveBlocked, showPartialDebateInject, showLongText };
})();
