export function createRecallMessageUiController(deps = {}) {
  let persistedRecallUiRefreshTimer = null;
  let persistedRecallUiRefreshObserver = null;
  let persistedRecallUiRefreshSession = 0;
  const persistedRecallUiDiagnosticTimestamps = new Map();
  const persistedRecallPersistDiagnosticTimestamps = new Map();

  const getContextValue = () => deps.getContext?.() || null;
  const getSettingsValue = () => deps.getSettings?.() || {};
  const getCurrentGraphValue = () => deps.getCurrentGraph?.() || null;
  const getDocument = () => deps.document || globalThis.document;
  const getMutationObserver = () => deps.MutationObserver || globalThis.MutationObserver;
  const getToastr = () => deps.toastr || {};
  const getConsole = () => deps.console || console;
  const getSetTimeout = () => deps.setTimeout || setTimeout;
  const getClearTimeout = () => deps.clearTimeout || clearTimeout;
  const getRefreshRetryDelays = () =>
    Array.isArray(deps.PERSISTED_RECALL_UI_REFRESH_RETRY_DELAYS_MS)
      ? deps.PERSISTED_RECALL_UI_REFRESH_RETRY_DELAYS_MS
      : [0, 80, 180, 320, 500, 850, 1300, 2000, 3000, 4200];
  const getDiagnosticThrottleMs = () =>
    Number.isFinite(Number(deps.PERSISTED_RECALL_UI_DIAGNOSTIC_THROTTLE_MS))
      ? Number(deps.PERSISTED_RECALL_UI_DIAGNOSTIC_THROTTLE_MS)
      : 1500;

function getMessageRecallRecord(messageIndex) {
  const chat = getContextValue()?.chat;
  return deps.readPersistedRecallFromUserMessage(chat, messageIndex);
}

function debugWithThrottle(cache, key, ...args) {
  if (!globalThis.__stBmeDebugLoggingEnabled) return;
  const now = Date.now();
  const lastAt = cache.get(key) || 0;
  if (now - lastAt < getDiagnosticThrottleMs()) return;
  cache.set(key, now);
  getConsole().debug(...args);
}

function debugPersistedRecallUi(reason, details = null, throttleKey = reason) {
  const suffix = details ? ` ${JSON.stringify(details)}` : "";
  debugWithThrottle(
    persistedRecallUiDiagnosticTimestamps,
    `ui:${throttleKey}`,
    `[ST-BME] Recall Card UI: ${reason}${suffix}`,
  );
}

function removeMessageRecallRecord(messageIndex) {
  const chat = getContextValue()?.chat;
  if (!Array.isArray(chat)) return false;
  const removed = deps.removePersistedRecallFromUserMessage(chat, messageIndex);
  if (removed) {
    deps.triggerChatMetadataSave(getContextValue(), { immediate: false });
  }
  return removed;
}

function editMessageRecallRecord(messageIndex, nextInjectionText) {
  const chat = getContextValue()?.chat;
  if (!Array.isArray(chat)) return null;
  const current = deps.readPersistedRecallFromUserMessage(chat, messageIndex);
  if (!current) return null;

  const normalizedText = deps.normalizeRecallInputText(nextInjectionText);
  if (!normalizedText) return null;
  const nowIso = new Date().toISOString();
  const nextRecord = {
    ...current,
    injectionText: normalizedText,
    tokenEstimate: deps.estimateTokens(normalizedText),
    updatedAt: nowIso,
  };
  if (!deps.writePersistedRecallToUserMessage(chat, messageIndex, nextRecord)) {
    return null;
  }
  const edited = deps.markPersistedRecallManualEdit(
    chat,
    messageIndex,
    true,
    nowIso,
  );
  if (!edited) return null;

  deps.triggerChatMetadataSave(getContextValue(), { immediate: false });
  return edited;
}

function syncEditedUserMessageDom(messageIndex, nextText) {
  const chatRoot = getDocument()?.getElementById?.("chat");
  if (!chatRoot?.querySelectorAll) return false;

  for (const messageElement of Array.from(chatRoot.querySelectorAll(".mes") || [])) {
    if (resolveMessageIndexFromElement(messageElement) !== messageIndex) continue;
    const userTextElement = messageElement.querySelector?.(".mes_text");
    if (!userTextElement) return false;
    userTextElement.textContent = String(nextText || "");
    return true;
  }
  return false;
}

function persistEditedUserMessage(context = getContextValue()) {
  const candidates = [
    ["saveChatConditional", context?.saveChatConditional],
    ["saveChat", context?.saveChat],
  ];

  for (const [label, handler] of candidates) {
    if (typeof handler !== "function") continue;
    try {
      const result = handler.call(context);
      if (result && typeof result.catch === "function") {
        result.catch((error) => {
          getConsole().error(`[ST-BME] 保存用户输入编辑失败 (${label}):`, error);
        });
      }
      return label;
    } catch (error) {
      getConsole().error(`[ST-BME] 调用 ${label} 保存用户输入编辑失败:`, error);
    }
  }

  return deps.triggerChatMetadataSave(context, { immediate: true });
}

function editMessageUserInputText(messageIndex, nextUserInputText) {
  const context = getContextValue();
  const chat = context?.chat;
  if (!Array.isArray(chat)) {
    return { ok: false, error: "missing-chat" };
  }

  const message = chat[messageIndex];
  if (!message?.is_user) {
    return { ok: false, error: "not-user-message" };
  }

  const normalizedText = deps.normalizeRecallInputText(nextUserInputText);
  if (!normalizedText) {
    return { ok: false, error: "empty-user-input" };
  }

  const previousText = deps.normalizeRecallInputText(message.mes || "");
  const currentRecord = deps.readPersistedRecallFromUserMessage(chat, messageIndex);
  const recallBoundText = deps.normalizeRecallInputText(
    currentRecord?.boundUserFloorText || previousText,
  );
  const recallMayBeStale = Boolean(currentRecord) && recallBoundText !== normalizedText;

  message.mes = normalizedText;
  const swipeIndex = Number.isFinite(Number(message?.swipe_id))
    ? Math.max(0, Math.floor(Number(message.swipe_id)))
    : null;
  if (
    Array.isArray(message?.swipes) &&
    swipeIndex !== null &&
    swipeIndex < message.swipes.length
  ) {
    message.swipes[swipeIndex] = normalizedText;
  }

  if (message.extra && typeof message.extra === "object") {
    if (typeof message.extra.display_text === "string") {
      message.extra.display_text = normalizedText;
    }
    if (typeof message.extra.current_display_text === "string") {
      message.extra.current_display_text = normalizedText;
    }
  }

  const saveMode = persistEditedUserMessage(context);
  const domSynced = syncEditedUserMessageDom(messageIndex, normalizedText);

  return {
    ok: true,
    nextText: normalizedText,
    recallMayBeStale,
    unchanged: previousText === normalizedText,
    saveMode,
    domSynced,
  };
}

function clearPersistedRecallMessageUiObserver() {
  try {
    persistedRecallUiRefreshObserver?.disconnect?.();
  } catch (error) {
    getConsole().warn("[ST-BME] Recall Card UI observer disconnect 失败:", error);
  }
  persistedRecallUiRefreshObserver = null;
}

function isDomNodeAttached(node) {
  if (!node) return false;
  if (node.isConnected === true) return true;
  return typeof getDocument()?.contains === "function"
    ? getDocument().contains(node)
    : true;
}

function cleanupRecallCardElement(cardElement) {
  if (!cardElement) return;
  const messageElement = cardElement.closest?.(".mes") || null;
  if (messageElement) {
    restoreRecallCardUserInputDisplay(messageElement);
  }
  try {
    cardElement._bmeDestroyRenderer?.();
  } catch (error) {
    getConsole().warn("[ST-BME] Recall Card renderer 清理失败:", error);
  }
  cardElement.remove?.();
}

function cleanupLegacyRecallBadges(messageElement) {
  if (!messageElement?.querySelectorAll) return;
  const oldBadges = Array.from(
    messageElement.querySelectorAll(".st-bme-recall-badge") || [],
  );
  for (const oldBadge of oldBadges) oldBadge.remove();
}

function cleanupRecallArtifacts(messageElement, keepMessageIndex = null) {
  if (!messageElement?.querySelectorAll) return;

  cleanupLegacyRecallBadges(messageElement);
  restoreRecallCardUserInputDisplay(messageElement);

  const existingCards = Array.from(
    messageElement.querySelectorAll(".bme-recall-card") || [],
  );
  for (const card of existingCards) {
    if (
      keepMessageIndex !== null &&
      card.dataset?.messageIndex === String(keepMessageIndex)
    ) {
      continue;
    }
    cleanupRecallCardElement(card);
  }
}

function parseStableMessageIndex(candidate) {
  const normalized = String(candidate ?? "").trim();
  if (!normalized) return null;
  if (!/^\d+$/.test(normalized)) return null;
  const parsed = Number.parseInt(normalized, 10);
  return Number.isFinite(parsed) ? parsed : null;
}

function resolveMessageIndexFromElement(messageElement) {
  if (!messageElement) return null;

  const candidates = [
    messageElement.getAttribute?.("mesid"),
    messageElement.getAttribute?.("data-mesid"),
    messageElement.getAttribute?.("data-message-id"),
    messageElement.dataset?.mesid,
    messageElement.dataset?.messageId,
  ];

  for (const candidate of candidates) {
    const parsed = parseStableMessageIndex(candidate);
    if (parsed !== null) return parsed;
  }

  return null;
}

function resolveRecallCardAnchor(messageElement) {
  if (!messageElement || !isDomNodeAttached(messageElement)) return null;
  const mesBlock = messageElement.querySelector?.(".mes_block");
  if (isDomNodeAttached(mesBlock)) return mesBlock;

  const mesTextParent =
    messageElement.querySelector?.(".mes_text")?.parentElement;
  if (isDomNodeAttached(mesTextParent)) return mesTextParent;

  return isDomNodeAttached(messageElement) ? messageElement : null;
}

function getRecallMessageElementPriority(messageElement) {
  if (!messageElement || !isDomNodeAttached(messageElement)) return -1;

  let priority = 0;
  const anchor = resolveRecallCardAnchor(messageElement);
  if (anchor === messageElement) priority += 1;
  else if (anchor) priority += 3;

  if (messageElement.querySelector?.(".mes_text")) priority += 1;
  if (messageElement.classList?.contains("last_mes")) priority += 2;
  if (
    messageElement.getAttribute?.("is_user") === "true" ||
    messageElement.dataset?.isUser === "true" ||
    messageElement.classList?.contains("user_mes")
  ) {
    priority += 1;
  }

  return priority;
}

function normalizeRecallCardUserInputDisplayMode(mode) {
  const normalized = String(mode || "").trim();
  if (
    normalized === "off" ||
    normalized === "beautify_only" ||
    normalized === "mirror"
  ) {
    return normalized;
  }
  return "beautify_only";
}

function applyRecallCardUserInputDisplayMode(messageElement, mode) {
  if (!messageElement?.querySelector) return;
  const userTextElement = messageElement.querySelector(".mes_text");
  if (!userTextElement) return;
  userTextElement.classList.toggle(
    "bme-hide-original-user-text",
    normalizeRecallCardUserInputDisplayMode(mode) === "beautify_only",
  );
}

function restoreRecallCardUserInputDisplay(messageElement) {
  if (!messageElement?.querySelector) return;
  const userTextElement = messageElement.querySelector(".mes_text");
  userTextElement?.classList?.remove("bme-hide-original-user-text");
}

function buildPersistedRecallUiRetryDelays(initialDelayMs = 0) {
  const normalizedInitial = Math.max(
    0,
    Number.parseInt(initialDelayMs, 10) || 0,
  );
  if (!normalizedInitial)
    return [...getRefreshRetryDelays()];
  return [
    normalizedInitial,
    ...getRefreshRetryDelays().filter(
      (delay) => delay > normalizedInitial,
    ),
  ];
}

function summarizePersistedRecallRefreshStatus(summary) {
  if (summary.waitingMessageIndices.length > 0) return "waiting_dom";
  if (summary.anchorFailureIndices.length > 0) return "missing_message_anchor";
  if (summary.renderedCount > 0) return "rendered";
  if (summary.skippedNonUserIndices.length > 0) return "skipped_non_user";
  if (summary.persistedRecordCount === 0) return "missing_recall_record";
  return "missing_message_anchor";
}

function refreshPersistedRecallMessageUi() {
  const context = getContextValue();
  const chat = context?.chat;
  if (!Array.isArray(chat) || typeof getDocument()?.getElementById !== "function") {
    return {
      status: "missing_chat_root",
      renderedCount: 0,
      persistedRecordCount: 0,
      waitingMessageIndices: [],
      anchorFailureIndices: [],
      skippedNonUserIndices: [],
    };
  }

  const chatRoot = getDocument().getElementById("chat");
  if (!chatRoot) {
    debugPersistedRecallUi("缺少 #chat 根节点");
    return {
      status: "missing_chat_root",
      renderedCount: 0,
      persistedRecordCount: 0,
      waitingMessageIndices: [],
      anchorFailureIndices: [],
      skippedNonUserIndices: [],
    };
  }

  const settings = getSettingsValue();
  const themeName = settings?.panelTheme || "crimson";
  const recallCardUserInputDisplayMode =
    normalizeRecallCardUserInputDisplayMode(
      settings?.recallCardUserInputDisplayMode,
    );
  const callbacks = getRecallCardCallbacks();
  const messageElementMap = new Map();
  const messageElements = Array.from(chatRoot.querySelectorAll(".mes"));
  for (const messageElement of messageElements) {
    cleanupLegacyRecallBadges(messageElement);
    const messageIndex = resolveMessageIndexFromElement(messageElement);
    if (!Number.isFinite(messageIndex)) {
      debugPersistedRecallUi(
        "消息 DOM 缺少稳定索引属性，跳过挂载",
        {
          className: messageElement.className || "",
        },
        "missing-stable-message-index",
      );
      continue;
    }
    if (messageElementMap.has(messageIndex)) {
      const previousElement = messageElementMap.get(messageIndex) || null;
      const previousPriority = getRecallMessageElementPriority(previousElement);
      const nextPriority = getRecallMessageElementPriority(messageElement);
      const shouldReplace = nextPriority >= previousPriority;
      debugPersistedRecallUi(
        "检测到重复消息 DOM 索引，已挑选更可靠的锚点",
        {
          messageIndex,
          previousPriority,
          nextPriority,
          replaced: shouldReplace,
        },
        `duplicate-message-index:${messageIndex}`,
      );
      if (shouldReplace) {
        cleanupRecallArtifacts(previousElement);
        messageElementMap.set(messageIndex, messageElement);
      } else {
        cleanupRecallArtifacts(messageElement);
      }
      continue;
    }
    messageElementMap.set(messageIndex, messageElement);
  }

  const summary = {
    status: "missing_recall_record",
    renderedCount: 0,
    persistedRecordCount: 0,
    waitingMessageIndices: [],
    anchorFailureIndices: [],
    skippedNonUserIndices: [],
  };

  for (let messageIndex = 0; messageIndex < chat.length; messageIndex++) {
    const message = chat[messageIndex];
    const messageElement = messageElementMap.get(messageIndex) || null;
    const existingCard =
      messageElement?.querySelector?.(
        `.bme-recall-card[data-message-index="${messageIndex}"]`,
      ) || null;

    if (!message?.is_user) {
      if (messageElement) {
        restoreRecallCardUserInputDisplay(messageElement);
      }
      if (existingCard) cleanupRecallCardElement(existingCard);
      const unexpectedRecord = deps.readPersistedRecallFromUserMessage(
        chat,
        messageIndex,
      );
      if (unexpectedRecord) {
        summary.skippedNonUserIndices.push(messageIndex);
        debugPersistedRecallUi(
          "非 user 楼层存在持久召回记录，已跳过挂载",
          {
            messageIndex,
          },
          `skipped-non-user:${messageIndex}`,
        );
      }
      continue;
    }

    const record = deps.readPersistedRecallFromUserMessage(chat, messageIndex);
    if (!record?.injectionText) {
      if (messageElement) {
        restoreRecallCardUserInputDisplay(messageElement);
      }
      if (existingCard) cleanupRecallCardElement(existingCard);
      continue;
    }

    summary.persistedRecordCount += 1;
    if (!messageElement) {
      summary.waitingMessageIndices.push(messageIndex);
      debugPersistedRecallUi(
        "目标 user 楼层 DOM 未就绪，等待后续刷新",
        {
          messageIndex,
        },
        `waiting-dom:${messageIndex}`,
      );
      continue;
    }

    const anchor = resolveRecallCardAnchor(messageElement);
    if (!anchor) {
      restoreRecallCardUserInputDisplay(messageElement);
      cleanupRecallCardElement(existingCard);
      summary.anchorFailureIndices.push(messageIndex);
      debugPersistedRecallUi(
        "目标 user 楼层锚点解析失败，跳过挂载",
        {
          messageIndex,
        },
        `missing-anchor:${messageIndex}`,
      );
      continue;
    }

    cleanupRecallArtifacts(messageElement, messageIndex);
    const currentCard =
      messageElement.querySelector?.(
        `.bme-recall-card[data-message-index="${messageIndex}"]`,
      ) || null;

    if (currentCard) {
      deps.updateRecallCardData(currentCard, record, {
        userMessageText: message.mes || "",
        userInputDisplayMode: recallCardUserInputDisplayMode,
        graph: getCurrentGraphValue(),
        themeName,
        callbacks,
      });
    } else {
      const card = deps.createRecallCardElement({
        messageIndex,
        record,
        userMessageText: message.mes || "",
        userInputDisplayMode: recallCardUserInputDisplayMode,
        graph: getCurrentGraphValue(),
        themeName,
        callbacks,
      });
      anchor.appendChild(card);
    }
    applyRecallCardUserInputDisplayMode(
      messageElement,
      recallCardUserInputDisplayMode,
    );
    summary.renderedCount += 1;
  }

  summary.status = summarizePersistedRecallRefreshStatus(summary);
  if (summary.status === "missing_recall_record") {
    debugPersistedRecallUi("当前无有效持久召回记录可渲染");
  } else if (summary.renderedCount > 0) {
    debugPersistedRecallUi(
      "Recall Card 挂载完成",
      {
        renderedCount: summary.renderedCount,
        persistedRecordCount: summary.persistedRecordCount,
        waitingDom: summary.waitingMessageIndices.length,
      },
      `rendered:${summary.renderedCount}`,
    );
  }
  return summary;
}

function getRecallCardCallbacks() {
  return {
    onEdit: (messageIndex) => {
      const record = getMessageRecallRecord(messageIndex);
      if (!record) return;
      deps.openRecallSidebar({
        mode: "edit",
        messageIndex,
        record,
        node: null,
        graph: getCurrentGraphValue(),
        callbacks: {
          onSave: (idx, newText) => {
            const edited = editMessageRecallRecord(idx, newText);
            if (edited) {
              getToastr().success("已保存手动编辑");
            } else {
              getToastr().warning("编辑失败：注入文本不能为空");
            }
            schedulePersistedRecallMessageUiRefresh();
          },
          estimateTokens: deps.estimateTokens,
        },
      });
    },
    onEditUserInput: (messageIndex, nextUserInputText) => {
      const result = editMessageUserInputText(messageIndex, nextUserInputText);
      if (!result?.ok) {
        getToastr().warning("编辑失败：内容不能为空或此楼层非用户消息");
        return result;
      }

      if (result.unchanged) {
        getToastr().info("用户输入未变化");
      } else {
        getToastr().success("已更新本轮用户输入");
      }
      if (result.recallMayBeStale) {
        getToastr().info("输入已改，当前召回结果可能需要重新召回");
      }
      schedulePersistedRecallMessageUiRefresh();
      return result;
    },
    onDelete: (messageIndex) => {
      if (removeMessageRecallRecord(messageIndex)) {
        getToastr().success("已删除持久召回注入");
        schedulePersistedRecallMessageUiRefresh();
      }
    },
    onRerunRecall: async (messageIndex) => {
      const result = await deps.rerunRecallForMessage(messageIndex);
      if (result?.status === "completed") {
        getToastr().success("重新召回完成");
      }
      schedulePersistedRecallMessageUiRefresh();
    },
    onNodeClick: (messageIndex, node) => {
      const record = getMessageRecallRecord(messageIndex);
      if (!record) return;
      deps.openRecallSidebar({
        mode: "view",
        messageIndex,
        record,
        node,
        graph: getCurrentGraphValue(),
        callbacks: {
          onSave: (idx, newText) => {
            const edited = editMessageRecallRecord(idx, newText);
            if (edited) getToastr().success("已保存手动编辑");
            else getToastr().warning("编辑失败：注入文本不能为空");
            schedulePersistedRecallMessageUiRefresh();
          },
          estimateTokens: deps.estimateTokens,
        },
      });
    },
  };
}

function armPersistedRecallMessageUiObserver(sessionId, runAttempt) {
  clearPersistedRecallMessageUiObserver();
  const chatRoot = getDocument()?.getElementById?.("chat");
  const ObserverCtor = getMutationObserver();
  if (!chatRoot || typeof ObserverCtor !== "function") return false;

  persistedRecallUiRefreshObserver = new ObserverCtor(() => {
    if (sessionId !== persistedRecallUiRefreshSession) return;
    clearPersistedRecallMessageUiObserver();
    runAttempt();
  });
  persistedRecallUiRefreshObserver.observe(chatRoot, {
    childList: true,
    subtree: true,
    attributes: true,
    attributeFilter: [
      "mesid",
      "data-mesid",
      "data-message-id",
      "class",
      "is_user",
    ],
  });
  return true;
}

function schedulePersistedRecallMessageUiRefresh(delayMs = 0) {
  getClearTimeout()(persistedRecallUiRefreshTimer);
  clearPersistedRecallMessageUiObserver();

  const retryDelays = buildPersistedRecallUiRetryDelays(delayMs);
  const sessionId = ++persistedRecallUiRefreshSession;
  let attemptIndex = 0;

  const runAttempt = () => {
    if (sessionId !== persistedRecallUiRefreshSession) return;
    if (persistedRecallUiRefreshTimer) {
      getClearTimeout()(persistedRecallUiRefreshTimer);
      persistedRecallUiRefreshTimer = null;
    }

    const summary = refreshPersistedRecallMessageUi();

    const shouldRetryForPending =
      (summary.status === "missing_chat_root" ||
        summary.status === "waiting_dom" ||
        summary.status === "missing_message_anchor") &&
      attemptIndex < retryDelays.length - 1;

    // 勿在「已成功渲染」时长期监听 MutationObserver：chat 的 class/流式更新会疯狂触发
    // runAttempt，造成满屏刷新与日志；显式事件（USER_MESSAGE_RENDERED 等）仍会 schedule 刷新。
    const shouldWatchForRepaint = false;

    if (!shouldRetryForPending && !shouldWatchForRepaint) {
      clearPersistedRecallMessageUiObserver();
      return;
    }

    armPersistedRecallMessageUiObserver(sessionId, runAttempt);
    if (shouldRetryForPending) {
      attemptIndex += 1;
      persistedRecallUiRefreshTimer = getSetTimeout()(
        runAttempt,
        retryDelays[attemptIndex],
      );
      return;
    }

    const lingerMs = retryDelays[retryDelays.length - 1] || 0;
    if (lingerMs <= 0) {
      clearPersistedRecallMessageUiObserver();
      return;
    }
    persistedRecallUiRefreshTimer = getSetTimeout()(() => {
      if (sessionId !== persistedRecallUiRefreshSession) return;
      clearPersistedRecallMessageUiObserver();
      persistedRecallUiRefreshTimer = null;
    }, lingerMs);
  };

  persistedRecallUiRefreshTimer = getSetTimeout()(
    runAttempt,
    retryDelays[attemptIndex],
  );
}

function cleanupPersistedRecallMessageUi() {
  getClearTimeout()(persistedRecallUiRefreshTimer);
  persistedRecallUiRefreshTimer = null;
  clearPersistedRecallMessageUiObserver();
  const chatRoot = getDocument().getElementById("chat");
  if (!chatRoot?.querySelectorAll) return;
  for (const messageElement of Array.from(chatRoot.querySelectorAll(".mes"))) {
    cleanupRecallArtifacts(messageElement);
  }
}

  return {
    refreshPersistedRecallMessageUi,
    schedulePersistedRecallMessageUiRefresh,
    cleanupPersistedRecallMessageUi,
    resolveMessageIndexFromElement,
    resolveRecallCardAnchor,
  };
}
