Reddit Chat Delete Suite

Production-ready Reddit chat cleanup with selective delete, sweep, purge, hide, and detailed logs

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Reddit Chat Delete Suite
// @namespace    http://tampermonkey.net/
// @version      v3.3.0
// @description  Production-ready Reddit chat cleanup with selective delete, sweep, purge, hide, and detailed logs
// @author       Amrit
// @match        https://www.reddit.com/chat*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  // ========================= CONFIGURATION =========================
  const CONFIG = {
    // Timings
    INIT_DELAY_MS: 500,
    BIND_TIMEOUT_MS: 4000,
    BIND_POLL_MS: 150,
    URL_WATCH_INTERVAL_MS: 750,
    CHAT_CONTAINER_WAIT_MS: 5000,

    // Deletion / Menus
    MENU_WAIT_MS: 180,
    DIALOG_WAIT_MS: 120,
    DELETE_POST_CONFIRM_MS: 250,

    // Sweep
    SWEEP_BATCH: 10,
    SWEEP_CYCLE_WAIT_MS: 500,
    SCROLL_STEP_PX: 1400,
    SCROLL_SETTLE_MS: 400,
    TOP_STALL_MAX: 4,

    // Hide Chat
    HIDE_CHAT_DIALOG_TIMEOUT_MS: 6000,
    DIALOG_POLL_INTERVAL_MS: 50,

    // UI
    LOG_MAX_LINES: 500,
  };

  // ========================= SETTINGS =========================
  const DEFAULT_SETTINGS = {
    showLog: true,
    hideAfterDeletion: false,
    autoMinimizeAfterDeletion: false,
    deletionDelayMs: 250,
    maxDeletionsPerMinute: 60,
    emptyChatLoadWaitSec: 4,
    markStyle: "modern",
    username: "",
    processModMails: false,
  };

  const Settings = {
    key: "rcd_settings_ui_scaffold_v1",
    current: { ...DEFAULT_SETTINGS },

    load() {
      try {
        const raw = localStorage.getItem(this.key);
        if (!raw) return { ...DEFAULT_SETTINGS };
        return { ...DEFAULT_SETTINGS, ...JSON.parse(raw) };
      } catch {
        return { ...DEFAULT_SETTINGS };
      }
    },

    save() {
      try {
        localStorage.setItem(this.key, JSON.stringify(this.current));
      } catch {}
    },

    init() {
      this.current = this.load();
    },

    get(k) {
      return this.current[k];
    },

    set(k, v) {
      this.current[k] = v;
      this.save();
    },
  };

  // ========================= STATE =========================
  const state = {
    currentTab: "chat",
    lastUrl: "",
    isBinding: false,
    markMode: false,
    markedCount: 0,
    totalDeleted: 0,
    isBusy: false,
    sweepActive: false,
    cancelSweep: false,
    cancelDelete: false,
    cancelChatList: false,
    processedReplyParents: new Set(),
    // --- NEW: progress tracking ---
    deleteProgress: { current: 0, total: 0 }, // current chat deletion progress
    chatListProgress: { current: 0, total: 0 }, // chat list processing progress
    chatsProcessed: 0, // session total chats fully processed
    containers: {
      main: null,
      reply: null,
      chatList: null,
      settings: null,
    },
  };

  // ========================= UTILITIES =========================
  const Utils = {
    sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),

    deepNodes: function* (root) {
      if (!root) return;
      yield root;

      try {
        if (root instanceof Element && root.shadowRoot) {
          yield root.shadowRoot;
          yield* this.deepNodes(root.shadowRoot);
        }
      } catch (_) {}

      const tw = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);
      while (tw.nextNode()) {
        const el = tw.currentNode;
        yield el;
        if (el.shadowRoot) {
          yield el.shadowRoot;
          yield* this.deepNodes(el.shadowRoot);
        }
        if (el.tagName === "IFRAME") {
          try {
            if (el.contentDocument) {
              yield el.contentDocument;
              yield* this.deepNodes(el.contentDocument);
            }
          } catch (_) {}
        }
      }
    },

    deepQueryAll: function (sel, scope = document) {
      const out = [];
      for (const n of this.deepNodes(scope)) {
        if (n.querySelectorAll) {
          try {
            out.push(...n.querySelectorAll(sel));
          } catch (_) {}
        }
      }
      return Array.from(new Set(out));
    },

    localQuery: function (scope, sel) {
      const out = [];
      (function walk(n) {
        if (!n) return;
        if (n.querySelectorAll) {
          try {
            out.push(...n.querySelectorAll(sel));
          } catch (_) {}
        }
        if (n.shadowRoot) walk(n.shadowRoot);
        for (const c of n.children || []) walk(c);
      })(scope);
      return out[0] || null;
    },

    isVisible: function (el) {
      if (!el) return false;
      const cs = getComputedStyle(el);
      const r = el.getBoundingClientRect();
      return (
        cs.display !== "none" &&
        cs.visibility !== "hidden" &&
        cs.opacity !== "0" &&
        r.width > 0 &&
        r.height > 0
      );
    },

    findEventFromClick(e) {
      const path = typeof e.composedPath === "function" ? e.composedPath() : [];
      for (const node of path) {
        if (node && node.tagName === "RS-TIMELINE-EVENT") return node;
      }
      const target = e.target;
      if (target && target.closest) {
        return target.closest("rs-timeline-event");
      }
      return null;
    },
  };

  // ========================= THROTTLE =========================
  const Throttle = {
    windowMs: 60_000,
    timestamps: [],

    prune(now = Date.now()) {
      this.timestamps = this.timestamps.filter((t) => now - t < this.windowMs);
    },

    async waitForSlot() {
      const max = parseInt(Settings.get("maxDeletionsPerMinute"), 10) || 0;
      if (max <= 0) return;

      const now = Date.now();
      this.prune(now);

      if (this.timestamps.length < max) {
        this.timestamps.push(now);
        return;
      }

      const oldest = this.timestamps[0];
      const waitMs = Math.max(0, this.windowMs - (now - oldest) + 25);
      if (waitMs > 250) {
        UI.log(
          `⏳ Rate limit: waiting ${Math.ceil(waitMs / 1000)}s`,
          "warning",
        );
      }
      await Utils.sleep(waitMs);
      return this.waitForSlot();
    },

    async delayAfterDelete() {
      const delay = parseInt(Settings.get("deletionDelayMs"), 10) || 0;
      if (delay > 0) await Utils.sleep(delay);
    },

    reset() {
      this.timestamps = [];
    },
  };

  // ========================= CONTAINER DETECTION =========================
  const Containers = {
    isInReplyPane(el) {
      let cur = el;
      while (cur) {
        if (cur.tagName === "RS-THREAD-TIMELINE") return true;
        cur =
          cur.parentNode instanceof ShadowRoot
            ? cur.parentNode.host
            : cur.parentElement;
      }
      return false;
    },

    findReplyPane() {
      const panes = Utils.deepQueryAll("rs-thread-timeline");
      return panes.find(Utils.isVisible) || null;
    },

    findChatListContainer() {
      const containers = Utils.deepQueryAll("rs-roving-focus-wrapper");
      return containers.find(Utils.isVisible) || null;
    },

    findSettingsContainer() {
      const settings = Utils.deepQueryAll("rs-room-settings");
      return settings.find(Utils.isVisible) || null;
    },

    countEvents(scope) {
      return Utils.deepQueryAll("rs-timeline-event", scope).length;
    },

    findBestContainerFromEvent(evt) {
      let cur = evt;
      let best = null;
      let bestCount = 0;
      for (let i = 0; i < 12 && cur; i++) {
        const cnt = this.countEvents(cur);
        if (cnt > bestCount) {
          best = cur;
          bestCount = cnt;
        }
        cur =
          cur.parentNode instanceof ShadowRoot
            ? cur.parentNode.host
            : cur.parentElement;
      }
      return best || evt;
    },

    findMainContainer() {
      // Strategy 1: Look for timeline events (exists when chat has messages)
      const events = Utils.deepQueryAll("rs-timeline-event");
      if (events.length > 0) {
        const mainEvents = events.filter((e) => !this.isInReplyPane(e));
        if (mainEvents.length > 0) {
          const evt = mainEvents.find(Utils.isVisible) || mainEvents[0];
          return this.findBestContainerFromEvent(evt);
        }
      }

      // Strategy 2: Look for the virtual scroll container (empty chat fallback)
      const virtualScrolls = Utils.deepQueryAll("rs-virtual-scroll-dynamic");
      if (virtualScrolls.length > 0) {
        // Try visible first
        const visible = virtualScrolls.find(Utils.isVisible);
        if (visible) return visible;
        // But return the first one even if not strictly visible
        return virtualScrolls[0];
      }

      // Strategy 3: Look for rs-room-messages container
      const roomMessages = Utils.deepQueryAll("rs-room-messages");
      if (roomMessages.length > 0) {
        const visible = roomMessages.find(Utils.isVisible);
        if (visible) return visible;
        return roomMessages[0];
      }

      // Strategy 4: Look for rs-room container itself
      const rooms = Utils.deepQueryAll("rs-room");
      if (rooms.length > 0) {
        const visible = rooms.find(Utils.isVisible);
        if (visible) return visible;
        return rooms[0];
      }

      // Strategy 5: Last resort - look for ANY main/article element
      const generics = Utils.deepQueryAll("main, article, [role='main']");
      if (generics.length > 0) {
        const visible = generics.find(Utils.isVisible);
        if (visible) return visible;
        return generics[0];
      }

      return null;
    },

    refresh() {
      state.containers.reply = this.findReplyPane();
      state.containers.chatList = this.findChatListContainer();
      state.containers.settings = this.findSettingsContainer();
    },

    async bindMainContainer({ silent = false } = {}) {
      if (state.isBinding) return null;
      state.isBinding = true;

      // Get caller info for debugging
      const caller = new Error().stack?.split("\n")[2]?.trim() || "unknown";
      const callerName = caller.includes("at ")
        ? caller.split("at ")[1].split(" ")[0]
        : "unknown";

      try {
        const start = Date.now();
        let container = null;
        while (Date.now() - start < CONFIG.BIND_TIMEOUT_MS) {
          container = this.findMainContainer();
          if (container) break;
          await Utils.sleep(CONFIG.BIND_POLL_MS);
        }

        // Log binding attempt with caller info
        if (container) {
          const tag = container.tagName || container.nodeName || "?";
          const eventCount = Utils.deepQueryAll(
            "rs-timeline-event",
            container,
          ).length;
          const isConnected = container.isConnected ? "✓" : "✗";
          UI.log(
            `🔗 [BIND] caller=${callerName} container=${tag} events=${eventCount} connected=${isConnected}`,
            eventCount === 0 ? "warning" : "info",
          );
        } else {
          UI.log(
            `🔗 [BIND] caller=${callerName} FAILED no container found`,
            "error",
          );
        }

        state.containers.main = container;
        this.refresh();
        state.markedCount = 0;
        Selection.markedIds.clear();
        if (container) {
          Selection.syncFromIds(container);
        }
        if (state.markMode) {
          Selection.attach(state.containers.main);
          if (state.containers.reply) Selection.attach(state.containers.reply);
        }
        UI.updateMarkedCount();
        UI.updateStatsDisplay();
        UI.updateControls();

        if (!silent) {
          if (container) {
            UI.log("✓ Main chat container detected", "success");
          } else {
            UI.log("⚠️ Could not detect main chat container", "warning");
          }

          const open = [];
          if (state.containers.main) open.push("main");
          if (state.containers.reply) open.push("reply");
          if (state.containers.chatList) open.push("chat list");
          if (state.containers.settings) open.push("settings");
          UI.log(
            open.length
              ? `Open containers: ${open.join(", ")}`
              : "No containers detected",
          );
        }

        return container;
      } finally {
        state.isBinding = false;
      }
    },

    async bindMainContainerWithPolling({
      maxWaitMs = 15000,
      pollIntervalMs = 200,
      silent = false,
    } = {}) {
      if (state.isBinding) return null;
      state.isBinding = true;

      const caller = new Error().stack?.split("\n")[2]?.trim() || "unknown";
      const callerName = caller.includes("at ")
        ? caller.split("at ")[1].split(" ")[0]
        : "unknown";

      try {
        const start = Date.now();
        let bestContainer = null;
        let bestEventCount = 0;

        // Poll for container with events, up to maxWaitMs
        while (Date.now() - start < maxWaitMs) {
          const container = this.findMainContainer();
          if (container) {
            const eventCount = Utils.deepQueryAll(
              "rs-timeline-event",
              container,
            ).length;

            // If we found events, bind immediately
            if (eventCount > 0) {
              bestContainer = container;
              bestEventCount = eventCount;
              UI.log(
                `⏱️ [POLL] Found container with ${eventCount} events after ${Date.now() - start}ms`,
                "success",
              );
              break;
            }

            // Track best container even if empty
            if (eventCount > bestEventCount) {
              bestContainer = container;
              bestEventCount = eventCount;
            }
          }

          await Utils.sleep(pollIntervalMs);
        }

        // Log binding attempt with caller info
        if (bestContainer) {
          const tag = bestContainer.tagName || bestContainer.nodeName || "?";
          const isConnected = bestContainer.isConnected ? "✓" : "✗";
          const elapsed = Date.now() - start;
          UI.log(
            `🔗 [BIND] caller=${callerName} container=${tag} events=${bestEventCount} connected=${isConnected} elapsed=${elapsed}ms`,
            bestEventCount === 0 ? "warning" : "success",
          );
        } else {
          UI.log(
            `🔗 [BIND] caller=${callerName} FAILED no container found after ${Date.now() - start}ms`,
            "error",
          );
        }

        state.containers.main = bestContainer;
        this.refresh();
        state.markedCount = 0;
        Selection.markedIds.clear();
        if (bestContainer) {
          Selection.syncFromIds(bestContainer);
        }
        if (state.markMode) {
          Selection.attach(state.containers.main);
          if (state.containers.reply) Selection.attach(state.containers.reply);
        }
        UI.updateMarkedCount();
        UI.updateStatsDisplay();
        UI.updateControls();

        if (!silent) {
          if (bestContainer) {
            UI.log("✓ Main chat container detected", "success");
          } else {
            UI.log("⚠️ Could not detect main chat container", "warning");
          }

          const open = [];
          if (state.containers.main) open.push("main");
          if (state.containers.reply) open.push("reply");
          if (state.containers.chatList) open.push("chat list");
          if (state.containers.settings) open.push("settings");
          UI.log(
            open.length
              ? `Open containers: ${open.join(", ")}`
              : "No containers detected",
          );
        }

        return bestContainer;
      } finally {
        state.isBinding = false;
      }
    },
  };

  // ========================= DOM HELPERS =========================
  const DOM = {
    getEvents(scope) {
      return Utils.deepQueryAll("rs-timeline-event", scope).filter(
        Utils.isVisible,
      );
    },

    getReplyThreadEvents(replyPane) {
      if (!replyPane) return [];
      const vscroll = Utils.localQuery(replyPane, "rs-virtual-scroll-dynamic");
      if (vscroll) {
        const events = Utils.deepQueryAll("rs-timeline-event", vscroll).filter(
          Utils.isVisible,
        );
        if (events.length) return events;
      }
      return this.getEvents(replyPane).filter((evt) =>
        Containers.isInReplyPane(evt),
      );
    },

    getEventId(evt) {
      const id = evt.getAttribute && evt.getAttribute("data-id");
      if (id) return id;
      const child = Utils.localQuery(evt, "[data-id]");
      return child ? child.getAttribute("data-id") : null;
    },

    hasReplyThread(evt) {
      const replyBtn = Utils.localQuery(
        evt,
        ["button.replies", 'button[class*="replies"]'].join(","),
      );
      if (replyBtn && Utils.isVisible(replyBtn)) return replyBtn;
      return null;
    },

    findReplyPaneCloseButton() {
      const closeButtons = Utils.deepQueryAll(
        'button[aria-label*="Close thread" i], rs-thread header button',
      );
      for (const btn of closeButtons) {
        if (
          Utils.isVisible(btn) &&
          (btn.getAttribute("aria-label") || "").toLowerCase().includes("close")
        ) {
          return btn;
        }
      }
      return null;
    },

    isOwnMessage(evt, username) {
      if (!username) return false;
      const uname = String(username).trim().toLowerCase();
      const m = Utils.localQuery(evt, ".room-message[aria-label]");
      const aria = m ? m.getAttribute("aria-label") || "" : "";
      return (
        !!uname && new RegExp(`^${uname}\\s+said\\b`, "i").test(aria.trim())
      );
    },

    getMessageNode(evt) {
      return Utils.localQuery(evt, ".room-message") || null;
    },

    ensureMarkStyles(evt) {
      const root = evt?.shadowRoot;
      if (!root) return;
      if (root.getElementById("rcd-mark-style")) return;

      const style = document.createElement("style");
      style.id = "rcd-mark-style";
      style.textContent = `
        :host(.rc-marked) {
          box-shadow: inset 3px 0 0 #22c55e;
        }
        .rc-marked-msg.modern {
          background: rgba(34,197,94,0.07);
        }
        .rc-marked-msg.border {
          box-shadow: 0 0 0 1px #22c55e inset;
          outline: 1px solid #22c55e;
        }
      `;
      root.appendChild(style);
    },
  };

  // ========================= SELECTION =========================
  const Selection = {
    markedIds: new Set(),
    boundContainers: new WeakSet(),

    canBeMarked(evt, username) {
      const isOwn = DOM.isOwnMessage(evt, username);
      const hasReplies = DOM.hasReplyThread(evt);
      if (!isOwn && hasReplies) {
        const id = DOM.getEventId(evt);
        if (id && state.processedReplyParents.has(id)) return false;
      }
      if (Containers.isInReplyPane(evt)) {
        return isOwn;
      }
      return isOwn || hasReplies;
    },

    setMark(evt, marked) {
      if (!evt) return;
      const wasMarked = evt.dataset?.rcMarked === "true";
      if (marked && wasMarked) return;
      if (!marked && !wasMarked) return;

      if (marked) {
        evt.dataset.rcMarked = "true";
        evt.classList.add("rc-marked");
        DOM.ensureMarkStyles(evt);
        const msg = DOM.getMessageNode(evt);
        if (msg) {
          msg.classList.add("rc-marked-msg");
          msg.classList.remove("modern", "border");
          msg.classList.add(Settings.get("markStyle") || "modern");
        }

        const id = DOM.getEventId(evt);
        if (id) this.markedIds.add(id);
        state.markedCount++;
      } else {
        delete evt.dataset.rcMarked;
        evt.classList.remove("rc-marked");
        const msg = DOM.getMessageNode(evt);
        if (msg) msg.classList.remove("rc-marked-msg", "modern", "border");

        const id = DOM.getEventId(evt);
        if (id) this.markedIds.delete(id);
        state.markedCount = Math.max(0, state.markedCount - 1);
      }
    },

    toggleMark(evt) {
      const on = !(evt.dataset.rcMarked === "true");
      this.setMark(evt, on);
    },

    attach(container) {
      if (!container || this.boundContainers.has(container)) return;
      this.boundContainers.add(container);

      container.addEventListener(
        "click",
        (e) => {
          if (!state.markMode) return;
          if (!e.isTrusted) return;
          if (!container.contains(e.target)) return;

          const panel = document.getElementById("rcd-panel");
          if (panel && panel.contains(e.target)) return;

          const evt = Utils.findEventFromClick(e);
          if (!evt) return;

          const username = Settings.get("username");
          const isOwn = DOM.isOwnMessage(evt, username);
          const hasReplies = DOM.hasReplyThread(evt);
          if (!isOwn && !hasReplies) {
            UI.log(
              "⚠️ Cannot mark: no replies on other user's message",
              "warning",
            );
            return;
          }
          if (!this.canBeMarked(evt, username)) {
            UI.log("⚠️ This message cannot be marked", "warning");
            return;
          }

          e.preventDefault();
          e.stopImmediatePropagation();
          this.toggleMark(evt);
          UI.updateMarkedCount();
          UI.updateStatsDisplay();
          UI.updateControls();
        },
        { capture: true },
      );
    },

    syncFromIds(container) {
      if (!container || !this.markedIds.size) return;
      const events = DOM.getEvents(container);
      for (const evt of events) {
        const id = DOM.getEventId(evt);
        if (id && this.markedIds.has(id) && evt.dataset.rcMarked !== "true") {
          this.setMark(evt, true);
        }
      }
    },

    clearAll(container) {
      if (!container) return;
      const events = DOM.getEvents(container);
      for (const evt of events) this.setMark(evt, false);
      this.markedIds.clear();
      state.markedCount = 0;
    },

    getMarked(container) {
      if (!container) return [];
      return DOM.getEvents(container).filter(
        (e) => e.dataset.rcMarked === "true",
      );
    },

    autoSelectOwn(container, username) {
      if (!container) {
        UI.log("🧭 [AUTOSELECT] container=null, skipping", "warning");
        return 0;
      }
      let count = 0;
      const events = DOM.getEvents(container);
      const tag = container.tagName || container.nodeName || "?";
      const isConnected = container.isConnected ? "✓" : "✗";

      for (const evt of events) {
        if (this.canBeMarked(evt, username)) {
          this.setMark(evt, true);
          count++;
        }
      }

      // Log only if we found events but couldn't mark any (suspicious)
      if (events.length > 0 && count === 0) {
        UI.log(
          `🧭 [AUTOSELECT] WARNING: container=${tag} connected=${isConnected} events=${events.length} marked=${count}`,
          "warning",
        );
      }

      return count;
    },

    autoSelectBoth(username) {
      let total = 0;
      if (state.containers.main) {
        total += this.autoSelectOwn(state.containers.main, username);
      }
      if (state.containers.reply) {
        total += this.autoSelectOwn(state.containers.reply, username);
      }
      return total;
    },
  };

  // ========================= SCROLL =========================
  const Scroll = {
    getScrollable(container) {
      let cur = container;
      for (let i = 0; i < 10 && cur; i++) {
        const el = cur instanceof ShadowRoot ? cur.host : cur;
        const cs = getComputedStyle(el);
        if (
          (cs.overflowY === "auto" || cs.overflowY === "scroll") &&
          el.scrollHeight > el.clientHeight
        ) {
          return el;
        }
        cur =
          el.parentNode instanceof ShadowRoot
            ? el.parentNode.host
            : el.parentElement;
      }
      return container || document.scrollingElement || document.documentElement;
    },

    isAtTop(scrollEl) {
      return !scrollEl ? true : scrollEl.scrollTop <= 2;
    },

    async scrollUp(scrollEl) {
      if (!scrollEl) return;
      if (scrollEl.scrollTop > 0) {
        scrollEl.scrollTop = Math.max(
          0,
          scrollEl.scrollTop - CONFIG.SCROLL_STEP_PX,
        );
      } else {
        scrollEl.scrollTop = 1;
        await Utils.sleep(30);
        scrollEl.scrollTop = 0;
      }
      await Utils.sleep(CONFIG.SCROLL_SETTLE_MS);
    },
  };

  // ========================= DELETION (SELECTED ONLY) =========================
  const Deletion = {
    async openActions(evt) {
      for (let i = 0; i < 5; i++) {
        evt.scrollIntoView({ block: "center" });
        evt.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
        await Utils.sleep(80);

        const trash = Utils.localQuery(
          evt,
          [
            '[aria-label="Delete"]',
            '[title="Delete"]',
            '[data-testid="delete-message"]',
            'button:has(svg[aria-label="delete"])',
            'rs-icon-button[icon="trash"]',
            'rs-icon-button[icon="delete"]',
            'rs-button[aria-label="Delete"]',
          ].join(","),
        );

        if (trash && Utils.isVisible(trash)) return { directDelete: trash };

        const more = Utils.localQuery(
          evt,
          [
            '[aria-label="More options"]',
            '[aria-label="More"]',
            '[title="More"]',
            'button[aria-haspopup="menu"]',
            '[role="button"][aria-haspopup="menu"]',
            'button:has(svg[aria-label="more"])',
            'rs-icon-button[icon="more"]',
          ].join(","),
        );

        if (more && Utils.isVisible(more)) {
          more.click();
          await Utils.sleep(180);
          return { openedMenu: true };
        }
      }
      return {};
    },

    findDeleteMenuItem() {
      const items = Utils.deepQueryAll(
        '[role="menuitem"], rs-menu-item, rs-dropdown-item',
      );
      for (const el of items) {
        const aria = (el.getAttribute("aria-label") || "").toLowerCase();
        const txt = (el.innerText || el.textContent || "").toLowerCase();
        if (aria.includes("delete") || txt.includes("delete")) return el;
        if (
          aria.includes("remove") ||
          txt.includes("remove message") ||
          txt.includes("remove")
        )
          return el;
      }
      return null;
    },

    findDeleteDialog() {
      const rsDlg = Utils.deepQueryAll(
        "rs-delete-message-modal rpl-dialog",
      ).find(Utils.isVisible);
      if (rsDlg) return rsDlg;

      const candidates = Utils.deepQueryAll(
        'rpl-dialog, [role="dialog"], div',
      ).filter(Utils.isVisible);
      return (
        candidates.find((el) => {
          const txt = (el.innerText || "").toLowerCase();
          return (
            txt.includes("delete this message?") ||
            (txt.includes("delete") && txt.includes("you can't undo"))
          );
        }) || null
      );
    },

    confirmDeleteDialog(dlg) {
      const btns = Utils.deepQueryAll('button, [role="button"]', dlg);
      const yes = btns.find((b) =>
        /yes,\s*delete/i.test((b.innerText || b.textContent || "").trim()),
      );
      if (yes) {
        yes.click();
        return true;
      }
      const fallback = btns.find((b) =>
        /delete|confirm|yes/i.test((b.innerText || b.textContent || "").trim()),
      );
      if (fallback) {
        fallback.click();
        return true;
      }
      return false;
    },

    async deleteMessage(evt) {
      return this.deleteMessageDirect(evt);
    },

    async deleteMessageDirect(evt) {
      await Throttle.waitForSlot();
      const open = await this.openActions(evt);
      if (open.directDelete) {
        open.directDelete.click();
        await Utils.sleep(CONFIG.DIALOG_WAIT_MS);
      } else {
        const deleteItem = this.findDeleteMenuItem();
        if (!deleteItem) return false;
        deleteItem.click();
        await Utils.sleep(CONFIG.DIALOG_WAIT_MS);
      }

      const dlg = this.findDeleteDialog();
      if (dlg) this.confirmDeleteDialog(dlg);
      await Utils.sleep(CONFIG.DELETE_POST_CONFIRM_MS);
      await Throttle.delayAfterDelete();
      return true;
    },

    async deleteRepliesInPane(replyPane, username) {
      const allEvents = DOM.getReplyThreadEvents(replyPane);
      const ownMsgs = allEvents.filter((evt) =>
        DOM.isOwnMessage(evt, username),
      );
      if (!ownMsgs.length) return { deleted: 0, remaining: 0 };

      let deleted = 0;
      for (const evt of ownMsgs) {
        const ok = await this.deleteMessageDirect(evt);
        if (ok) deleted++;
      }

      await Utils.sleep(200);
      const stillThere = DOM.getReplyThreadEvents(replyPane).filter((evt) =>
        DOM.isOwnMessage(evt, username),
      );
      return { deleted, remaining: stillThere.length };
    },

    async handleReplyPaneIfNeeded(evt, username) {
      const replyBtn = DOM.hasReplyThread(evt);
      if (!replyBtn) return { hadReplies: false, fullyCleaned: true };

      replyBtn.click();
      await Utils.sleep(CONFIG.MENU_WAIT_MS);

      const replyPane = Containers.findReplyPane();
      if (!replyPane) {
        UI.log("⚠️ Could not find reply pane", "warning");
        return { hadReplies: true, fullyCleaned: false };
      }

      UI.log("📎 Processing replies...");
      const result = await this.deleteRepliesInPane(replyPane, username);
      if (result.deleted > 0) {
        UI.log(`✅ Deleted ${result.deleted} replies`);
      }
      if (result.remaining > 0) {
        UI.log(
          `⚠️ ${result.remaining} replies remaining after delete`,
          "warning",
        );
      }

      const closeBtn = DOM.findReplyPaneCloseButton();
      if (closeBtn) {
        closeBtn.click();
        await Utils.sleep(CONFIG.MENU_WAIT_MS);
      }
      return { hadReplies: true, fullyCleaned: result.remaining === 0 };
    },

    async deleteOwnMessage(evt) {
      const ok = await this.deleteMessageDirect(evt);
      if (!ok) return false;
      await Utils.sleep(120);
      return !evt.isConnected;
    },

    async deleteSelectedBatch(container, batch) {
      if (!container || !batch.length) return { success: 0, failed: 0 };

      let success = 0;
      let failed = 0;
      const username = Settings.get("username");

      for (const evt of batch) {
        if (state.cancelDelete) break;
        try {
          const replyResult = await this.handleReplyPaneIfNeeded(evt, username);
          const isOwn = DOM.isOwnMessage(evt, username);
          let ok = false;

          if (isOwn) {
            ok = await this.deleteOwnMessage(evt);
          } else if (replyResult.hadReplies && replyResult.fullyCleaned) {
            const id = DOM.getEventId(evt);
            if (id) state.processedReplyParents.add(id);
            ok = true;
          }

          if (ok) {
            success++;
            Selection.setMark(evt, false);
            state.totalDeleted++;
            // Update delete progress
            state.deleteProgress.current++;
            UI.updateStatsDisplay();
          } else {
            failed++;
          }
        } catch {
          failed++;
        }
      }

      return { success, failed };
    },

    async deleteSelected(container) {
      if (!container) {
        UI.log("❌ Select a chat first", "error");
        return;
      }

      const marked = Selection.getMarked(container);
      if (!marked.length) {
        UI.log("⚠️ No selected messages", "warning");
        return;
      }

      // Reset and initialise progress for this run
      state.deleteProgress = { current: 0, total: marked.length };

      UI.log(`🗑️ Deleting ${marked.length} selected...`);
      state.isBusy = true;
      state.cancelDelete = false;
      UI.setHeaderStatus("running", "Deleting");
      UI.updateControls();
      UI.updateStatsDisplay();

      const { success, failed } = await this.deleteSelectedBatch(
        container,
        marked,
      );

      state.isBusy = false;
      state.cancelDelete = false;
      // Clear progress after run
      state.deleteProgress = { current: 0, total: 0 };
      UI.setHeaderStatus("idle");
      UI.updateControls();
      UI.updateMarkedCount();
      UI.updateStatsDisplay();
      UI.log(`✅ Deleted ${success}, Failed ${failed}`);

      if (!state.cancelDelete && Settings.get("hideAfterDeletion")) {
        await HideChat.execute();
      }

      if (!state.cancelDelete && Settings.get("autoMinimizeAfterDeletion")) {
        UI.panel?.classList.add("minimized");
        const btn = document.getElementById("rcd-minimize");
        if (btn) btn.textContent = "+";
      }
    },
  };

  // ========================= SWEEP =========================
  const Sweep = {
    describeContainer(container) {
      if (!container) return "null";
      const tag = container.tagName || container.nodeName || "node";
      const id = container.id ? `#${container.id}` : "";
      const cls = container.className
        ? `.${String(container.className).trim().replace(/\s+/g, ".")}`
        : "";
      return `${String(tag).toLowerCase()}${id}${cls}`;
    },

    getOwnVisibleCount(container, username) {
      if (!container) return 0;
      const events = DOM.getEvents(container);
      let own = 0;
      for (const evt of events) {
        if (DOM.isOwnMessage(evt, username)) own++;
      }
      return own;
    },

    async run() {
      if (!state.containers.main) {
        UI.log("❌ Select a chat first", "error");
        return;
      }
      if (state.sweepActive) return;

      const username = Settings.get("username");
      if (!username) {
        UI.log("❌ Please save username first", "error");
        return;
      }

      // Force fresh container binding to avoid stale references
      await Containers.bindMainContainer({ silent: true });
      if (!state.containers.main) {
        UI.log("❌ Could not bind to chat container", "error");
        return;
      }

      // Validate container health
      const container = state.containers.main;
      const isConnected = container.isConnected;
      const eventCount = DOM.getEvents(container).length;
      const tag = container.tagName || container.nodeName || "?";

      if (!isConnected) {
        UI.log(
          `⚠️ Bound container is detached from DOM (${tag}), attempting rebind...`,
          "warning",
        );
        await Containers.bindMainContainer({ silent: true });
        if (!state.containers.main) {
          UI.log("❌ Rebind failed, aborting sweep", "error");
          return;
        }
      }

      UI.log(
        `✓ Sweep starting with container=${tag} events=${eventCount} connected=${isConnected}`,
        "success",
      );

      // Reset progress for this sweep run
      state.deleteProgress = { current: 0, total: 0 };

      state.sweepActive = true;
      state.isBusy = true;
      state.cancelSweep = false;
      state.cancelDelete = false;
      UI.setHeaderStatus("running", "Running");
      UI.updateControls();
      UI.updateStatsDisplay();
      UI.log("🔄 Auto sweep started");
      UI.log(
        `🧭 Sweep init: container=${this.describeContainer(state.containers.main)}`,
      );

      let topStalls = 0;
      let cycle = 0;

      try {
        while (state.sweepActive && !state.cancelSweep) {
          cycle++;
          const main = state.containers.main;
          const visibleBefore = DOM.getEvents(main).length;
          const ownBefore = this.getOwnVisibleCount(main, username);
          const markedBefore = Selection.getMarked(main).length;
          UI.log(
            `🧭 Sweep cycle ${cycle}: visible=${visibleBefore} own=${ownBefore} marked=${markedBefore} topStalls=${topStalls}`,
          );

          Selection.syncFromIds(main);
          const newlyMarked = Selection.autoSelectOwn(main, username);

          if (newlyMarked > 0) {
            UI.log(`📋 Cycle ${cycle}: Found ${newlyMarked} messages`);
            // Add newly discovered messages to the total
            state.deleteProgress.total += newlyMarked;
            UI.updateStatsDisplay();

            const marked = Selection.getMarked(main);
            const batch = marked.slice(0, CONFIG.SWEEP_BATCH);
            const { success, failed } = await Deletion.deleteSelectedBatch(
              main,
              batch,
            );
            UI.log(`✅ Deleted ${success}, Failed ${failed}`);
            UI.updateMarkedCount();
            UI.updateStatsDisplay();
            topStalls = 0;
            await Utils.sleep(CONFIG.SWEEP_CYCLE_WAIT_MS);
            continue;
          }

          const scrollEl = Scroll.getScrollable(main);
          const scrollTag = this.describeContainer(scrollEl);
          const beforeTop = scrollEl?.scrollTop ?? -1;
          const wasAtTop = Scroll.isAtTop(scrollEl);
          await Scroll.scrollUp(scrollEl);
          const afterTop = scrollEl?.scrollTop ?? -1;
          UI.log(
            `🧭 Scroll probe: scroller=${scrollTag} topBefore=${beforeTop} topAfter=${afterTop} wasAtTop=${wasAtTop}`,
          );

          const afterScroll = Selection.autoSelectOwn(main, username);
          if (afterScroll > 0) {
            UI.log(`🧭 Post-scroll found ${afterScroll} markable messages`);
            state.deleteProgress.total += afterScroll;
            UI.updateStatsDisplay();
            topStalls = 0;
            continue;
          }

          const stillAtTop = Scroll.isAtTop(scrollEl);
          const visibleAfter = DOM.getEvents(main).length;
          const ownAfter = this.getOwnVisibleCount(main, username);
          UI.log(
            `🧭 Post-scroll scan: visible=${visibleAfter} own=${ownAfter} stillAtTop=${stillAtTop}`,
          );

          if (wasAtTop && stillAtTop) {
            topStalls++;
            UI.log(`⬆️ At top (${topStalls}/${CONFIG.TOP_STALL_MAX})`);
            if (topStalls >= CONFIG.TOP_STALL_MAX) break;
          }

          await Utils.sleep(CONFIG.SWEEP_CYCLE_WAIT_MS);
        }
      } finally {
        const wasStopped = !!state.cancelSweep;
        state.sweepActive = false;
        state.cancelSweep = false;
        state.isBusy = false;
        // Clear progress after sweep
        state.deleteProgress = { current: 0, total: 0 };
        if (wasStopped) {
          UI.flashHeaderStatus("stopped", "Stopped");
        } else {
          UI.setHeaderStatus("idle");
        }
        UI.updateControls();
        UI.updateStatsDisplay();
        UI.log("🏁 Sweep completed");
        if (!wasStopped && Settings.get("hideAfterDeletion")) {
          await HideChat.execute();
        }
        if (!wasStopped && Settings.get("autoMinimizeAfterDeletion")) {
          UI.panel?.classList.add("minimized");
          const btn = document.getElementById("rcd-minimize");
          if (btn) btn.textContent = "+";
        }
      }
    },

    stop() {
      if (state.sweepActive) {
        state.cancelSweep = true;
        UI.setHeaderStatus("stopping");
        UI.log("⏸️ Stopping sweep...");
      }
    },
  };

  // ========================= CHAT LIST =========================
  const ChatList = {
    markedChatIds: new Set(),
    purgePendingRoomIds: new Set(),
    purgeProcessingRoomId: null,
    chatListMarkMode: false,
    chatListProcessing: false,
    chatListPurgeActive: false,

    setListMarkModeUI(enabled) {
      const btn = document.getElementById("rcd-chat-mark-mode");
      if (!btn) return;
      btn.textContent = enabled ? "🖱️ Mark Mode (ON)" : "🖱️ Mark Mode";
      btn.style.background = enabled
        ? "linear-gradient(135deg, #10b981, #059669)"
        : "";
    },

    beginListProcessing() {
      const wasMarkMode = this.chatListMarkMode;
      if (wasMarkMode) {
        this.chatListMarkMode = false;
        this.setListMarkModeUI(false);
      }
      return wasMarkMode;
    },

    endListProcessing(wasMarkMode) {
      if (wasMarkMode) {
        this.chatListMarkMode = true;
        this.setListMarkModeUI(true);
      }
    },

    findContainer() {
      return Containers.findChatListContainer();
    },

    getChatItems(container) {
      if (!container) return [];
      return Utils.deepQueryAll("rs-rooms-nav-room", container).filter(
        Utils.isVisible,
      );
    },

    getChatRoomId(chatItem) {
      return chatItem.getAttribute("room") || null;
    },

    refreshChatVisual(chatItem) {
      if (!chatItem?.shadowRoot) return;
      const innerLink = chatItem.shadowRoot.querySelector("div > a");
      if (!innerLink) return;

      const roomId = this.getChatRoomId(chatItem);
      const manuallyMarked = chatItem.dataset?.rcdChatMarked === "true";
      const purgePending = !!(roomId && this.purgePendingRoomIds.has(roomId));
      const purgeProcessing = !!(
        roomId &&
        this.purgeProcessingRoomId &&
        roomId === this.purgeProcessingRoomId
      );

      // Skip highlighting for MMCIs if processModMails is disabled
      const isModmail = this.isModmailChat(chatItem);
      const processModMails = Settings.get("processModMails");
      if (isModmail && !processModMails) {
        innerLink.style.boxShadow = "";
        innerLink.style.outline = "";
        innerLink.style.outlineOffset = "";
        return;
      }

      const shouldHighlight = manuallyMarked || purgePending;

      if (!shouldHighlight) {
        innerLink.style.boxShadow = "";
        innerLink.style.outline = "";
        innerLink.style.outlineOffset = "";
        return;
      }

      innerLink.style.boxShadow = "0 0 0 3px #22c55e inset";
      innerLink.style.outline = purgeProcessing
        ? "3px solid #16a34a"
        : "3px solid #22c55e";
      innerLink.style.outlineOffset = "-3px";
    },

    syncPurgeHighlights(container) {
      const items = this.getChatItems(container || this.findContainer());
      for (const item of items) {
        this.refreshChatVisual(item);
      }
    },

    isModmailChat(chatItem) {
      if (!chatItem?.shadowRoot) return false;
      try {
        // Strategy 1: Look for rs-channel-icon with channeltype="reddit_modmail"
        const channelIcon = chatItem.shadowRoot.querySelector(
          'rs-channel-icon[channeltype="reddit_modmail"]',
        );
        if (channelIcon) return true;

        // Strategy 2: Look for MOD badge
        const modBadge = chatItem.shadowRoot.querySelector(
          ".text-global-moderator",
        );
        if (modBadge) return true;

        return false;
      } catch {
        return false;
      }
    },

    getChatUsername(chatItem) {
      try {
        if (!chatItem.shadowRoot) return "Unknown";
        const nameSpan = chatItem.shadowRoot.querySelector(
          "div > a > div > div:nth-child(1) > div > span.room-name",
        );
        return nameSpan ? nameSpan.textContent.trim() : "Unknown";
      } catch {
        return "Unknown";
      }
    },

    isCurrentlyActive(chatItem) {
      return (
        chatItem.hasAttribute("selected") &&
        chatItem.getAttribute("tabindex") === "0"
      );
    },

    markChat(chatItem, marked) {
      if (!chatItem) return;

      // Skip MMCIs if processModMails is disabled
      if (marked && this.isModmailChat(chatItem)) {
        const processModMails = Settings.get("processModMails");
        if (!processModMails) {
          return; // Silently skip without marking
        }
      }

      const wasMarked = chatItem.dataset?.rcdChatMarked === "true";
      if (marked && wasMarked) return;
      if (!marked && !wasMarked) return;

      if (marked) {
        chatItem.dataset.rcdChatMarked = "true";
        const roomId = this.getChatRoomId(chatItem);
        if (roomId) this.markedChatIds.add(roomId);
      } else {
        delete chatItem.dataset.rcdChatMarked;
        const roomId = this.getChatRoomId(chatItem);
        if (roomId) this.markedChatIds.delete(roomId);
      }
      this.refreshChatVisual(chatItem);
    },

    toggleMarkChat(chatItem) {
      const on = !(chatItem.dataset.rcdChatMarked === "true");

      // Check if trying to select MMCI when disabled
      if (on && this.isModmailChat(chatItem)) {
        const processModMails = Settings.get("processModMails");
        if (!processModMails) {
          const username = this.getChatUsername(chatItem);
          UI.log(
            `⚠️ Cannot select mod mail chat "${username}" - enable "Process Mod Mails" in settings`,
            "warning",
          );
          return;
        }
      }

      this.markChat(chatItem, on);
    },

    attachChatClickHandlers(container) {
      const items = this.getChatItems(container);
      for (const item of items) {
        this.refreshChatVisual(item);
        if (item._rcdChatBound) continue;
        item._rcdChatBound = true;

        const clickHandler = (e) => {
          if (!this.chatListMarkMode) return;

          // Warn if clicking MMCI in skip mode
          if (this.isModmailChat(item)) {
            const processModMails = Settings.get("processModMails");
            if (!processModMails) {
              const username = this.getChatUsername(item);
              UI.log(
                `⚠️ Cannot select mod mail chat "${username}" - enable "Process Mod Mails" in settings`,
                "warning",
              );
              return;
            }
          }

          e.preventDefault();
          e.stopImmediatePropagation();
          this.toggleMarkChat(item);
          UI.updateChatListCount();
          UI.updateChatListControls();
        };

        item._rcdChatClickHandler = clickHandler;
        item.addEventListener("click", clickHandler, { capture: true });
      }
    },

    selectAllChats(container) {
      const items = this.getChatItems(container);
      const processModMails = Settings.get("processModMails");
      let count = 0;

      for (const item of items) {
        // Skip MMCIs if processModMails is disabled
        if (this.isModmailChat(item) && !processModMails) continue;
        this.markChat(item, true);
        count++;
      }
      return count;
    },

    clearAllChats(container) {
      const items = this.getChatItems(container);
      for (const item of items) {
        this.markChat(item, false);
      }
      this.markedChatIds.clear();
    },

    getMarkedChats(container) {
      if (!container) return [];
      const items = this.getChatItems(container);
      return items.filter((item) => item.dataset.rcdChatMarked === "true");
    },

    async navigateToChat(chatItem) {
      if (!chatItem || !chatItem.shadowRoot) {
        return { success: false, reason: "Invalid chat item" };
      }

      try {
        const link = chatItem.shadowRoot.querySelector("div > a");
        if (!link) return { success: false, reason: "Link not found" };
        const roomId = this.getChatRoomId(chatItem) || "unknown";
        const href = link.getAttribute("href") || link.href || "(no href attr)";
        UI.log(`🔗 Navigate: room=${roomId} href=${href}`);
        link.click();
        await Utils.sleep(300);

        const maxAttempts = 15;
        for (let i = 0; i < maxAttempts; i++) {
          if (this.isCurrentlyActive(chatItem)) {
            UI.log(`✅ Active chat detected (attempt ${i + 1}/${maxAttempts})`);
            const pollMs = 200;
            const waitSec = Math.max(
              0,
              parseFloat(Settings.get("emptyChatLoadWaitSec")) || 0,
            );
            const messageAttempts = Math.max(
              1,
              Math.ceil((waitSec * 1000) / pollMs),
            );
            UI.log(`⏱️ Waiting up to ${waitSec}s for messages...`);
            await Utils.sleep(pollMs);
            let finalContainer = null;
            let messageCount = 0;

            for (let j = 0; j < messageAttempts; j++) {
              const mainContainer = Containers.findMainContainer();
              if (mainContainer) {
                const count = Containers.countEvents(mainContainer);
                finalContainer = mainContainer;
                messageCount = count;
                if (j === 0) {
                  UI.log(`🧾 Container found, messages=${count}`);
                }
                if (count > 0) {
                  UI.log(
                    `✅ Messages loaded (attempt ${j + 1}/${messageAttempts})`,
                  );
                  return { success: true, container: mainContainer };
                }
              }
              await Utils.sleep(pollMs);
            }
            if (finalContainer) {
              UI.log(
                `⏳ No messages found after waiting, proceeding with empty chat`,
              );
              return { success: true, container: finalContainer };
            }
            return { success: false, reason: "No container found for chat" };
          }
          if (i === 0 || i === 4 || i === 9 || i === 14) {
            UI.log(`⏳ Waiting for active chat (${i + 1}/${maxAttempts})`);
          }
          await Utils.sleep(200);
        }

        return { success: false, reason: "Chat did not become active" };
      } catch (error) {
        return { success: false, reason: error.message };
      }
    },

    async processSingleChat(chatItem) {
      const username = this.getChatUsername(chatItem);
      const roomId = this.getChatRoomId(chatItem);
      const isModmail = this.isModmailChat(chatItem);

      UI.log(`📱 Processing chat with ${username}...`);
      const nav = await this.navigateToChat(chatItem);
      if (!nav.success) {
        UI.log(`❌ Failed to open chat: ${nav.reason}`, "error");
        return { success: false, username, roomId };
      }

      state.containers.main = nav.container;
      await Containers.bindMainContainer({ silent: true });

      // Handle Mod Mail chats specially
      if (isModmail) {
        UI.log(
          `🔒 Mod mail chat detected - messages cannot be deleted`,
          "info",
        );
        const hideAfterDeletion = Settings.get("hideAfterDeletion");
        if (hideAfterDeletion) {
          UI.log(`📬 Hiding mod mail chat only (no message deletion)...`);
          await HideChat.executeForModmail();
          UI.log(`✅ Mod mail chat hidden successfully`);
        } else {
          UI.log(
            `⏭️ hideAfterDeletion disabled, skipping hide for mod mail`,
            "info",
          );
        }
        state.chatsProcessed++;
        UI.updateStatsDisplay();
        return { success: true, username, roomId };
      }

      // Normal chat processing
      await Sweep.run();
      state.chatsProcessed++;
      UI.updateStatsDisplay();
      UI.log(`✅ Completed processing ${username}`);
      return { success: true, username, roomId };
    },

    async processSelectedChats(container) {
      if (this.chatListProcessing || this.chatListPurgeActive) {
        UI.log("⚠️ Chat list already running", "warning");
        return;
      }
      const marked = this.getMarkedChats(container);
      if (!marked.length) {
        UI.log("⚠️ No chats selected", "warning");
        return;
      }

      const wasMarkMode = this.chatListMarkMode;
      UI.log(
        `🧭 Chat list process: ${marked.length} selected, markMode=${wasMarkMode ? "ON" : "OFF"}`,
      );
      const restoreMarkMode = this.beginListProcessing();

      // Initialise chat list progress
      state.chatListProgress = { current: 0, total: marked.length };

      this.chatListProcessing = true;
      this.chatListPurgeActive = false;
      state.cancelChatList = false;
      UI.setHeaderStatus("running", "Processing");
      UI.updateChatListControls();
      UI.updateControls();
      UI.updateStatsDisplay();

      let completed = 0;
      let failed = 0;

      for (let i = 0; i < marked.length; i++) {
        if (state.cancelChatList) break;
        const item = marked[i];
        const roomId = this.getChatRoomId(item) || "unknown";
        const username = this.getChatUsername(item) || "Unknown";
        UI.log(`\n--- Chat ${i + 1}/${marked.length} ---`);
        UI.log(`➡️ Target: ${username} (room=${roomId})`);
        const result = await this.processSingleChat(item);
        state.chatListProgress.current++;
        UI.updateStatsDisplay();
        if (result.success) {
          this.markChat(item, false);
          completed++;
        } else {
          failed++;
        }
        await Utils.sleep(500);
      }

      this.chatListProcessing = false;
      state.cancelChatList = false;
      state.chatListProgress = { current: 0, total: 0 };
      this.endListProcessing(restoreMarkMode);
      UI.setHeaderStatus("idle");
      UI.updateControls();
      UI.updateStatsDisplay();
      UI.log(`\n🏁 Batch complete: ${completed} processed, ${failed} failed`);
      UI.updateChatListCount();
      UI.updateChatListControls();
    },

    async hideSelectedChats(container) {
      if (this.chatListProcessing || this.chatListPurgeActive) {
        UI.log("⚠️ Chat list already running", "warning");
        return;
      }
      const marked = this.getMarkedChats(container);
      if (!marked.length) {
        UI.log("⚠️ No chats selected", "warning");
        return;
      }

      const wasMarkMode = this.chatListMarkMode;
      UI.log(
        `🧭 Chat list hide: ${marked.length} selected, markMode=${wasMarkMode ? "ON" : "OFF"}`,
      );
      const restoreMarkMode = this.beginListProcessing();

      state.chatListProgress = { current: 0, total: marked.length };

      this.chatListProcessing = true;
      state.cancelChatList = false;
      UI.setHeaderStatus("running", "Hiding");
      UI.updateChatListControls();
      UI.updateControls();
      UI.updateStatsDisplay();

      let completed = 0;
      let failed = 0;

      for (const item of marked) {
        if (state.cancelChatList) break;
        const username = this.getChatUsername(item);
        const roomId = this.getChatRoomId(item) || "unknown";
        UI.log(`📱 Opening chat with ${username}...`);
        UI.log(`➡️ Target: ${username} (room=${roomId})`);
        const nav = await this.navigateToChat(item);
        if (!nav.success) {
          UI.log(`❌ Failed to open chat: ${nav.reason}`, "error");
          failed++;
          state.chatListProgress.current++;
          UI.updateStatsDisplay();
          continue;
        }

        const hideOk = await HideChat.execute();
        state.chatListProgress.current++;
        if (hideOk) {
          this.markChat(item, false);
          state.chatsProcessed++;
          completed++;
        } else {
          failed++;
        }
        UI.updateStatsDisplay();
        await Utils.sleep(500);
      }

      this.chatListProcessing = false;
      state.cancelChatList = false;
      state.chatListProgress = { current: 0, total: 0 };
      this.endListProcessing(restoreMarkMode);
      UI.setHeaderStatus("idle");
      UI.updateControls();
      UI.updateStatsDisplay();
      UI.log(`🏁 Hide complete: ${completed} hidden, ${failed} failed`);
      UI.updateChatListCount();
      UI.updateChatListControls();
    },

    getScrollableList(container) {
      if (!container) return null;
      let cur = container;
      for (let i = 0; i < 10 && cur; i++) {
        const el = cur instanceof ShadowRoot ? cur.host : cur;
        const cs = getComputedStyle(el);
        if (
          (cs.overflowY === "auto" || cs.overflowY === "scroll") &&
          el.scrollHeight > el.clientHeight
        ) {
          return el;
        }
        cur =
          el.parentNode instanceof ShadowRoot
            ? el.parentNode.host
            : el.parentElement;
      }
      return container;
    },

    async scrollDown(scrollEl) {
      if (!scrollEl) return;
      const before = scrollEl.scrollTop;
      scrollEl.scrollTop = Math.min(
        scrollEl.scrollHeight,
        scrollEl.scrollTop + 900,
      );
      if (scrollEl.scrollTop === before) {
        scrollEl.scrollTop = Math.min(
          scrollEl.scrollHeight,
          scrollEl.scrollTop + 450,
        );
      }
      await Utils.sleep(250);
    },

    async scrollHalfScreen(scrollEl) {
      if (!scrollEl) return;
      const step = Math.max(
        120,
        Math.floor((scrollEl.clientHeight || 600) / 2),
      );
      const before = scrollEl.scrollTop;
      scrollEl.scrollTop = Math.min(
        scrollEl.scrollHeight,
        scrollEl.scrollTop + step,
      );
      if (scrollEl.scrollTop === before) {
        scrollEl.scrollTop = Math.min(
          scrollEl.scrollHeight,
          scrollEl.scrollTop + Math.floor(step / 2),
        );
      }
      await Utils.sleep(250);
    },

    async purge() {
      const container = this.findContainer();
      if (!container) {
        UI.log("❌ Could not find chat list", "error");
        return;
      }
      if (this.chatListProcessing || this.chatListPurgeActive) {
        UI.log("⚠️ Purge already running", "warning");
        return;
      }

      const restoreMarkMode = this.beginListProcessing();

      // Initialise progress — total unknown at start for purge
      state.chatListProgress = { current: 0, total: 0 };

      this.chatListProcessing = true;
      this.chatListPurgeActive = true;
      state.cancelChatList = false;
      UI.setHeaderStatus("running", "Purging");
      UI.updateChatListControls();
      UI.updateControls();
      UI.updateStatsDisplay();
      UI.log("🧹 Purge started");

      const processed = new Set();
      let stalls = 0;
      let totalProcessed = 0;
      let processedSinceLastScroll = 0;

      try {
        const initialScrollEl = this.getScrollableList(container);
        if (initialScrollEl) {
          initialScrollEl.scrollTop = 0;
          await Utils.sleep(180);
        }

        while (this.chatListPurgeActive && !state.cancelChatList) {
          const items = this.getChatItems(container);
          for (const item of items) {
            const roomId = this.getChatRoomId(item);
            // Skip MMCIs if processModMails is disabled
            if (this.isModmailChat(item) && !Settings.get("processModMails"))
              continue;
            if (roomId && !processed.has(roomId)) {
              this.purgePendingRoomIds.add(roomId);
              // Increment total as we discover new rooms
              state.chatListProgress.total++;
              UI.updateStatsDisplay();
            }
          }
          this.syncPurgeHighlights(container);

          for (const item of items) {
            if (!this.chatListPurgeActive || state.cancelChatList) break;
            const roomId = this.getChatRoomId(item);
            if (!roomId || processed.has(roomId)) continue;

            // Skip MMCIs if processModMails is disabled
            if (this.isModmailChat(item) && !Settings.get("processModMails")) {
              processed.add(roomId);
              this.purgePendingRoomIds.delete(roomId);
              this.syncPurgeHighlights(container);
              continue;
            }

            item.scrollIntoView({ block: "center", inline: "nearest" });
            await Utils.sleep(80);
            this.purgeProcessingRoomId = roomId;
            this.syncPurgeHighlights(container);

            const result = await this.processSingleChat(item);
            processed.add(roomId);
            this.purgePendingRoomIds.delete(roomId);
            this.purgeProcessingRoomId = null;
            totalProcessed++;
            processedSinceLastScroll++;
            state.chatListProgress.current++;
            UI.updateStatsDisplay();
            this.syncPurgeHighlights(container);

            if (!result.success) {
              UI.log(`⚠️ Skipped chat (${roomId})`, "warning");
            }

            if (processedSinceLastScroll >= 9) {
              const scrollEl = this.getScrollableList(container);
              await this.scrollHalfScreen(scrollEl);
              this.syncPurgeHighlights(container);
              processedSinceLastScroll = 0;
            }
          }

          if (!this.chatListPurgeActive) break;

          const scrollEl = this.getScrollableList(container);
          if (!scrollEl) break;

          const hasNew = this.getChatItems(container).some((item) => {
            const id = this.getChatRoomId(item);
            return id && !processed.has(id);
          });

          if (!hasNew) {
            await this.scrollDown(scrollEl);
            this.syncPurgeHighlights(container);
            const postScrollNew = this.getChatItems(container).some((item) => {
              const id = this.getChatRoomId(item);
              return id && !processed.has(id);
            });

            if (!postScrollNew) {
              stalls++;
              UI.log(`⬇️ No new chats (${stalls}/4)`);
              if (stalls >= 4) break;
            } else {
              stalls = 0;
            }
          } else {
            stalls = 0;
          }
        }
      } finally {
        const wasStopped = !!state.cancelChatList;
        this.purgePendingRoomIds.clear();
        this.purgeProcessingRoomId = null;
        this.syncPurgeHighlights(container);
        this.chatListProcessing = false;
        this.chatListPurgeActive = false;
        state.cancelChatList = false;
        state.chatListProgress = { current: 0, total: 0 };
        this.endListProcessing(restoreMarkMode);
        if (wasStopped) {
          UI.flashHeaderStatus("stopped", "Stopped");
        } else {
          UI.setHeaderStatus("idle");
        }
        UI.updateControls();
        UI.updateChatListControls();
        UI.updateStatsDisplay();
        UI.log(`🏁 Purge completed (${totalProcessed} processed)`);
        // Note: Purge doesn't have hideAfterDeletion at purge level.
        // Each processSingleChat() handles its own sweep+hide flow.
      }
    },

    stopPurge() {
      if (this.chatListPurgeActive) {
        this.chatListPurgeActive = false;
        UI.setHeaderStatus("stopping");
        UI.log("⏸️ Stopping purge...");
      }
      if (this.chatListProcessing) {
        state.cancelChatList = true;
      }
      this.purgePendingRoomIds.clear();
      this.purgeProcessingRoomId = null;
      this.syncPurgeHighlights(this.findContainer());
      UI.updateControls();
      UI.updateChatListControls();
    },
  };

  // ========================= HIDE CHAT =========================
  const HideChat = {
    async findSettingsButton() {
      const buttons = Utils.deepQueryAll("rs-room-settings-button");
      for (const btn of buttons) {
        if (btn.shadowRoot) {
          const innerBtn = btn.shadowRoot.querySelector("button");
          if (innerBtn && Utils.isVisible(innerBtn)) return innerBtn;
        }
      }
      return null;
    },

    async findHideOption() {
      const roomSettings = Utils.deepQueryAll("rs-room-settings");
      for (const rs of roomSettings) {
        if (!Utils.isVisible(rs) || !rs.shadowRoot) continue;
        const hideBtn = rs.shadowRoot.querySelector(
          "rs-room-settings-route-wrapper > faceplate-menu > li:nth-child(3) > div",
        );
        if (hideBtn && Utils.isVisible(hideBtn)) return hideBtn;
      }
      return null;
    },

    findConfirmButton() {
      const dialogs = Utils.deepQueryAll("#rs-confirmation-modal-dialog");
      for (const dialog of dialogs) {
        if (!dialog.shadowRoot) continue;
        const slot = dialog.shadowRoot.querySelector("slot");
        if (!slot) continue;
        const content = slot
          .assignedElements({ flatten: true })
          .find((el) => el.tagName === "RS-RPL-DIALOG-CONTENT");
        if (!content?.shadowRoot) continue;
        const confirmBtn = content.shadowRoot.querySelector(
          "button.button-primary",
        );
        if (confirmBtn) return confirmBtn;
      }
      return null;
    },

    async waitForConfirmButton() {
      const start = performance.now();
      while (performance.now() - start < CONFIG.HIDE_CHAT_DIALOG_TIMEOUT_MS) {
        const btn = this.findConfirmButton();
        if (btn) return btn;
        await Utils.sleep(CONFIG.DIALOG_POLL_INTERVAL_MS);
      }
      return null;
    },

    async executeForModmail() {
      const settingsBtn = await this.findSettingsButton();
      if (!settingsBtn) {
        UI.log("❌ Could not find settings button for mod mail", "error");
        return false;
      }
      settingsBtn.click();
      await Utils.sleep(CONFIG.MENU_WAIT_MS * 2);

      // For mod mail, use the specific selector for hide button
      const roomSettings = Utils.deepQueryAll("rs-room-settings");
      let hideBtn = null;
      for (const rs of roomSettings) {
        if (!Utils.isVisible(rs) || !rs.shadowRoot) continue;
        const btn = rs.shadowRoot.querySelector(
          "rs-room-settings-route-wrapper > faceplate-menu > li > div",
        );
        if (btn && Utils.isVisible(btn)) {
          hideBtn = btn;
          break;
        }
      }

      if (!hideBtn) {
        UI.log("❌ Could not find hide button for mod mail", "error");
        return false;
      }
      hideBtn.click();
      await Utils.sleep(CONFIG.DIALOG_WAIT_MS * 2);

      const confirmBtn = await this.waitForConfirmButton();
      if (!confirmBtn) {
        UI.log("❌ Could not find confirm button for mod mail", "error");
        return false;
      }
      confirmBtn.click();
      await Utils.sleep(CONFIG.DIALOG_WAIT_MS);

      return true;
    },

    async execute() {
      UI.log("🔒 Hiding chat...");

      const settingsBtn = await this.findSettingsButton();
      if (!settingsBtn) {
        UI.log("❌ Could not find settings button", "error");
        return false;
      }
      settingsBtn.click();
      await Utils.sleep(CONFIG.MENU_WAIT_MS * 2);

      const hideOption = await this.findHideOption();
      if (!hideOption) {
        UI.log("❌ Could not find hide option", "error");
        return false;
      }
      hideOption.click();
      await Utils.sleep(CONFIG.DIALOG_WAIT_MS * 2);

      const confirmBtn = await this.waitForConfirmButton();
      if (!confirmBtn) {
        UI.log("❌ Could not find confirm button", "error");
        return false;
      }
      confirmBtn.click();
      await Utils.sleep(CONFIG.DIALOG_WAIT_MS);

      UI.log("✓ Chat hidden successfully", "success");
      return true;
    },
  };

  // ========================= UI =========================
  const UI = {
    panel: null,
    statusResetTimer: null,

    init() {
      Settings.init();
      this.createPanel();
      this.attachEventListeners();
      this.syncSettingsToUI();
      this.switchTab("chat");
      this.log("✨ UI scaffold initialized");
      this.setHeaderStatus("idle");

      // Poll for container with events, binds as soon as found or after max wait
      (async () => {
        await Containers.bindMainContainerWithPolling({
          maxWaitMs: CONFIG.CHAT_CONTAINER_WAIT_MS,
          pollIntervalMs: 200,
          silent: true,
        });
        this.startUrlWatcher();
        this.updateStatsDisplay();
        this.updateControls();
      })();
    },

    createPanel() {
      const panel = document.createElement("div");
      panel.id = "rcd-panel";
      panel.innerHTML = `
                <style>
                    #rcd-panel {
                        position: fixed;
                        right: 16px;
                        bottom: 16px;
                        z-index: 2147483647;
                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                        font-size: 13px;
                        width: 380px;
                        background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                        border-radius: 16px;
                        box-shadow: 0 20px 50px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.1);
                        transition: width 0.3s ease, height 0.3s ease;
                        height: min(82vh, 760px);
                        display: flex;
                        flex-direction: column;
                    }
                    #rcd-panel.minimized {
                      width: fit-content;
                      height: auto;
                    }
                    #rcd-panel.minimized .rcd-header {
                      justify-content: flex-start;
                      align-items: center;
                      gap: 8px;
                    }
                    #rcd-panel.minimized .rcd-content { display: none; }
                    #rcd-panel.minimized .rcd-header {
                        border-radius: 16px;
                        border-bottom: none;
                    }
                    .rcd-content {
                        min-height: 0;
                        flex: 1 1 auto;
                        overflow: hidden;
                        display: flex;
                        flex-direction: column;
                        padding: 16px;
                        color: #e2e8f0;
                    }
                    .rcd-header {
                        padding: 14px 16px;
                        background: rgba(255,255,255,0.05);
                        border-radius: 16px 16px 0 0;
                        display: flex;
                        justify-content: space-between;
                        align-items: center;
                        cursor: move;
                        user-select: none;
                        border-bottom: 1px solid rgba(255,255,255,0.1);
                    }
                    .rcd-title {
                        font-weight: 600;
                        color: #fff;
                        font-size: 14px;
                        display: flex;
                        align-items: center;
                        gap: 8px;
                        flex: 0 0 auto;
                        white-space: nowrap;
                    }
                    .rcd-header-status {
                        padding: 3px 8px;
                        margin-left: 12px;
                        border-radius: 999px;
                        font-size: 10px;
                        font-weight: 700;
                        letter-spacing: 0.5px;
                        text-transform: uppercase;
                        border: 1px solid transparent;
                        white-space: nowrap;
                        line-height: 1.1;
                    }
                    .rcd-header-status.idle {
                        background: rgba(148, 163, 184, 0.16);
                        border-color: rgba(148, 163, 184, 0.35);
                        color: #cbd5e1;
                    }
                    .rcd-header-status.running {
                        background: rgba(16, 185, 129, 0.16);
                        border-color: rgba(16, 185, 129, 0.45);
                        color: #34d399;
                    }
                    .rcd-header-status.stopping {
                        background: rgba(245, 158, 11, 0.16);
                        border-color: rgba(245, 158, 11, 0.4);
                        color: #fbbf24;
                    }
                    .rcd-header-status.stopped {
                        background: rgba(100, 116, 139, 0.2);
                        border-color: rgba(100, 116, 139, 0.45);
                        color: #cbd5e1;
                    }
                    .rcd-header-status.error {
                        background: rgba(239, 68, 68, 0.16);
                        border-color: rgba(239, 68, 68, 0.45);
                        color: #f87171;
                    }
                    .rcd-header-controls {
                        display: flex;
                        gap: 6px;
                        margin-left: auto;
                    }
                    .rcd-header-btn {
                        width: 44px;
                        height: 24px;
                        border-radius: 6px;
                        border: none;
                        background: rgba(255,255,255,0.1);
                        color: #fff;
                        cursor: pointer;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        transition: all 0.2s;
                        font-size: 16px;
                        line-height: 1;
                    }
                    .rcd-header-btn:hover {
                        background: rgba(255,255,255,0.2);
                        transform: scale(1.1);
                    }
                    .rcd-header-btn:disabled {
                        opacity: 0.45;
                        cursor: not-allowed;
                        transform: none;
                    }
                    .rcd-header-btn-stop {
                        width: 64px;
                        height: 28px;
                        font-size: 12px;
                        font-weight: 800;
                        letter-spacing: 0.6px;
                        background: linear-gradient(135deg, #ef4444, #b91c1c);
                        color: #fff;
                        border: 1px solid rgba(255, 255, 255, 0.2);
                        text-transform: uppercase;
                    }
                    .rcd-header-btn-stop:hover {
                        background: linear-gradient(135deg, #f87171, #dc2626);
                    }
                    .rcd-tabs {
                        display: flex;
                        gap: 4px;
                        margin-bottom: 12px;
                        background: rgba(0,0,0,0.2);
                        padding: 4px;
                        border-radius: 10px;
                    }
                    .rcd-tab {
                        flex: 1;
                        padding: 8px 12px;
                        background: transparent;
                        border: none;
                        color: #94a3b8;
                        font-size: 12px;
                        font-weight: 500;
                        cursor: pointer;
                        border-radius: 8px;
                        transition: all 0.2s;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                    }
                    .rcd-tab:hover {
                        color: #e2e8f0;
                        background: rgba(255,255,255,0.05);
                    }
                    .rcd-tab.active {
                        background: linear-gradient(135deg, #10b981, #059669);
                        color: #000;
                    }
                    .rcd-tab-content {
                        display: none;
                        min-height: 0;
                        overflow-y: auto;
                        overflow-x: hidden;
                        flex: 1 1 auto;
                        padding-right: 2px;
                    }
                    .rcd-tab-content.active {
                        display: block;
                    }
                    .rcd-tab-content::-webkit-scrollbar {
                        width: 6px;
                    }
                    .rcd-tab-content::-webkit-scrollbar-thumb {
                        background: rgba(148,163,184,0.45);
                        border-radius: 999px;
                    }

                    /* ===== NEW STATS PANEL ===== */
                    #rcd-stats-display {
                        background: rgba(0,0,0,0.2);
                        border: 1px solid rgba(255,255,255,0.07);
                        border-radius: 10px;
                        padding: 8px 12px;
                        margin-bottom: 12px;
                        display: flex;
                        flex-direction: column;
                        gap: 0;
                    }
                    #rcd-stats-display.hidden {
                        display: none;
                    }
                    .rcd-stat-row {
                        display: flex;
                        justify-content: space-between;
                        align-items: center;
                        padding: 4px 0;
                        border-bottom: 1px solid rgba(255,255,255,0.04);
                    }
                    .rcd-stat-row:last-child {
                        border-bottom: none;
                    }
                    .rcd-stat-row.hidden {
                        display: none;
                    }
                    .rcd-stat-lbl {
                        font-size: 10px;
                        font-weight: 600;
                        text-transform: uppercase;
                        letter-spacing: 0.5px;
                        color: #64748b;
                    }
                    .rcd-stat-val {
                        font-size: 12px;
                        font-weight: 700;
                        color: #e2e8f0;
                        font-family: 'SF Mono', 'Consolas', monospace;
                    }
                    .rcd-stat-val.accent {
                        color: #10b981;
                    }
                    /* ===== END STATS PANEL ===== */

                    .rcd-section {
                        margin-bottom: 14px;
                    }
                    .rcd-section-title {
                        font-size: 11px;
                        font-weight: 600;
                        text-transform: uppercase;
                        letter-spacing: 0.5px;
                        color: #94a3b8;
                        margin-bottom: 8px;
                    }
                    .rcd-input-group {
                        display: flex;
                        gap: 12px;
                        margin-bottom: 12px;
                    }
                    .rcd-input {
                        flex: 1;
                        padding: 10px 12px;
                        background: rgba(255,255,255,0.05);
                        border: 1px solid rgba(255,255,255,0.1);
                        border-radius: 8px;
                        color: #fff;
                        font-size: 13px;
                        outline: none;
                        transition: all 0.2s;
                    }
                    select.rcd-input {
                        background: #0b1220;
                        color: #e2e8f0;
                        border: 1px solid rgba(255,255,255,0.14);
                        appearance: auto;
                    }
                    select.rcd-input:hover {
                        background: #0f172a;
                        color: #f8fafc;
                    }
                    select.rcd-input:focus {
                        background: #0f172a;
                        color: #f8fafc;
                        border-color: #10b981;
                    }
                    select.rcd-input option {
                        background: #0b1220;
                        color: #e2e8f0;
                    }
                    .rcd-input:focus {
                        background: rgba(255,255,255,0.08);
                        border-color: #10b981;
                    }
                    .rcd-input::placeholder {
                        color: #64748b;
                    }
                    .rcd-btn {
                        padding: 10px 16px;
                        border: none;
                        border-radius: 8px;
                        font-size: 13px;
                        font-weight: 500;
                        cursor: pointer;
                        transition: all 0.2s;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        gap: 6px;
                        user-select: none;
                        min-height: 36px;
                    }
                    #rcd-save-username {
                      height: 40px;
                    }
                    .rcd-btn:hover {
                        transform: translateY(-1px);
                        box-shadow: 0 4px 12px rgba(0,0,0,0.3);
                    }
                    .rcd-btn:active {
                        transform: translateY(0);
                    }
                    .rcd-btn:disabled {
                        opacity: 0.45;
                        cursor: not-allowed;
                        transform: none !important;
                        box-shadow: none !important;
                    }
                    .rcd-btn-primary {
                        background: linear-gradient(135deg, #10b981, #059669);
                        color: #000;
                    }
                    .rcd-btn-danger {
                        background: linear-gradient(135deg, #ef4444, #dc2626);
                        color: #fff;
                    }
                    .rcd-btn-secondary {
                        background: rgba(255,255,255,0.1);
                        color: #fff;
                    }
                    .rcd-btn-group {
                        display: grid;
                        grid-template-columns: repeat(2, 1fr);
                        gap: 8px;
                    }
                    .rcd-btn-full {
                        grid-column: 1 / -1;
                    }
                    .rcd-log-container {
                        position: relative;
                    }
                    .rcd-log {
                        position: relative;
                        height: 180px;
                        max-height: 180px;
                        overflow-y: auto;
                        padding: 10px 10px 10px 10px;
                        background: rgba(0,0,0,0.3);
                        border-radius: 8px;
                        font-size: 11px;
                        line-height: 1.5;
                        font-family: 'SF Mono', 'Consolas', monospace;
                        flex: 0 0 180px;
                        scrollbar-width: thin;
                        scrollbar-color: rgba(148,163,184,0.55) transparent;
                    }
                    .rcd-log::-webkit-scrollbar { width: 6px; }
                    .rcd-log::-webkit-scrollbar-track { background: transparent; }
                    .rcd-log::-webkit-scrollbar-thumb {
                        background: rgba(148,163,184,0.55);
                        border-radius: 999px;
                    }
                    .rcd-log::-webkit-scrollbar-thumb:hover {
                        background: rgba(148,163,184,0.75);
                    }
                    .rcd-log.hidden { display: none; }
                    .rcd-log-tools {
                        position: absolute;
                        top: 6px;
                        right: 6px;
                        display: flex;
                        flex-direction: column;
                        gap: 4px;
                        padding: 4px;
                        background: rgba(0,0,0,0.25);
                        backdrop-filter: blur(2px);
                        border-radius: 8px;
                        z-index: 2;
                    }
                    .rcd-log-btn {
                        width: 20px;
                        height: 20px;
                        border-radius: 6px;
                        border: none;
                        background: rgba(255,255,255,0.08);
                        color: #e2e8f0;
                        cursor: pointer;
                        font-size: 10px;
                        line-height: 1;
                    }
                    .rcd-log-btn:hover { background: rgba(255,255,255,0.18); }
                    .rcd-log-entry {
                        margin-bottom: 4px;
                        opacity: 0;
                        animation: fadeIn 0.3s forwards;
                    }
                    .rcd-log-entry.error { color: #ef4444; }
                    .rcd-log-entry.warning { color: #f59e0b; }
                    .rcd-log-entry.success { color: #10b981; }
                    .rcd-log-time {
                        color: #64748b;
                        margin-right: 6px;
                    }
                    @keyframes fadeIn { to { opacity: 1; } }
                    .rcd-toggle {
                        display: flex;
                        align-items: center;
                        gap: 10px;
                        justify-content: space-between;
                        padding: 8px 10px;
                        border-radius: 10px;
                        background: rgba(255,255,255,0.05);
                        border: 1px solid rgba(255,255,255,0.08);
                        font-size: 12px;
                        margin-bottom: 8px;
                    }
                    .rcd-toggle-row {
                        display: grid;
                        grid-template-columns: repeat(2, 1fr);
                        gap: 8px;
                        margin-bottom: 12px;
                    }
                    .rcd-toggle.compact {
                        padding: 6px 8px;
                        font-size: 11px;
                        margin-bottom: 0;
                        min-height: 24px;
                    }
                    .rcd-toggle.compact > div:first-child {
                        font-weight: 600;
                        color: #e2e8f0;
                    }
                    .rcd-switch {
                        width: 40px;
                        height: 22px;
                        border-radius: 999px;
                        border: 1px solid rgba(255,255,255,0.15);
                        background: rgba(255,255,255,0.08);
                        position: relative;
                        cursor: pointer;
                        flex: 0 0 auto;
                    }
                    .rcd-switch::after {
                        content: "";
                        position: absolute;
                        top: 2px;
                        left: 2px;
                        width: 18px;
                        height: 18px;
                        border-radius: 50%;
                        background: rgba(255,255,255,0.85);
                        transition: transform 0.18s ease;
                    }
                    .rcd-switch.on {
                        background: rgba(16,185,129,0.35);
                        border-color: rgba(16,185,129,0.45);
                    }
                    .rcd-switch.on::after {
                        transform: translateX(18px);
                        background: #ffffff;
                    }
                    .rcd-settings-group { margin-bottom: 14px; }
                    .rcd-settings-group-title {
                        font-size: 12px;
                        font-weight: 600;
                        color: #e2e8f0;
                        margin-bottom: 10px;
                        padding-bottom: 6px;
                        border-bottom: 1px solid rgba(255,255,255,0.1);
                    }
                    .rcd-input-labeled { margin-bottom: 12px; }
                    .rcd-input-label {
                        font-size: 11px;
                        color: #94a3b8;
                        margin-bottom: 6px;
                        display: block;
                    }
                    .rcd-input-row {
                        display: flex;
                        gap: 10px;
                        flex-wrap: wrap;
                        margin-bottom: 12px;
                    }
                    .rcd-input-row .rcd-input-labeled {
                        flex: 1 1 160px;
                        margin-bottom: 0;
                    }
                    .rcd-settings-actions {
                        display: flex;
                        gap: 8px;
                        margin-top: 20px;
                        padding-top: 16px;
                        border-top: 1px solid rgba(255,255,255,0.1);
                    }

                    rs-timeline-event.rc-marked {
                        background: rgba(34,197,94,0.07);
                        box-shadow: inset 3px 0 0 #22c55e;
                    }
                </style>

                <div class="rcd-header">
                    <div class="rcd-title">
                        <span>💬 Chat Delete</span>
                    </div>
                    <span id="rcd-header-status" class="rcd-header-status idle">Idle</span>
                    <div class="rcd-header-controls">
                        <button class="rcd-header-btn rcd-header-btn-stop" id="rcd-stop-all" title="Stop all" style="display:none;">STOP</button>
                        <button class="rcd-header-btn" id="rcd-minimize" title="Minimize">−</button>
                    </div>
                </div>

                <div class="rcd-content">
                    <div class="rcd-tabs">
                        <button class="rcd-tab active" data-tab="chat">Current Chat</button>
                        <button class="rcd-tab" data-tab="list">Chat List</button>
                        <button class="rcd-tab" data-tab="settings">Settings</button>
                    </div>

                    <!-- Stats panel — shared, shown for chat + list tabs -->
                    <div id="rcd-stats-display">
                        <div class="rcd-stat-row" id="rcd-stat-row-1">
                            <span class="rcd-stat-lbl" id="rcd-stat-lbl-1">Selected</span>
                            <span class="rcd-stat-val accent" id="rcd-stat-val-1">0</span>
                        </div>
                        <div class="rcd-stat-row hidden" id="rcd-stat-row-2">
                            <span class="rcd-stat-lbl" id="rcd-stat-lbl-2">Deleting</span>
                            <span class="rcd-stat-val accent" id="rcd-stat-val-2">0 / 0</span>
                        </div>
                        <div class="rcd-stat-row" id="rcd-stat-row-3">
                            <span class="rcd-stat-lbl" id="rcd-stat-lbl-3">Session</span>
                            <span class="rcd-stat-val" id="rcd-stat-val-3">0</span>
                        </div>
                    </div>

                    <!-- Current Chat Tab -->
                    <div class="rcd-tab-content active" id="rcd-tab-chat">
                        <div class="rcd-section">
                            <div class="rcd-section-title">🎯 Select</div>
                            <div class="rcd-btn-group">
                                <button class="rcd-btn rcd-btn-secondary" id="rcd-auto-mark">
                                    ✓ Select All
                                </button>
                                <button class="rcd-btn rcd-btn-secondary" id="rcd-toggle-mark">
                                    🖱️ Mark Mode
                                </button>
                                <button class="rcd-btn rcd-btn-secondary" id="rcd-clear-marks">
                                    ✕ Clear Selection
                                </button>
                                <button class="rcd-btn rcd-btn-danger" id="rcd-delete-marked">
                                    🗑️ Delete Selected
                                </button>
                            </div>
                        </div>

                        <div class="rcd-section">
                            <div class="rcd-section-title">⚡ Actions</div>
                            <div class="rcd-btn-group">
                                <button class="rcd-btn rcd-btn-primary" id="rcd-primary-action">
                                    🧹 Auto Sweep
                                </button>
                                <button class="rcd-btn rcd-btn-secondary" id="rcd-hide-chat-now">
                                    👁️ Hide Chat
                                </button>
                            </div>
                        </div>

                        <div class="rcd-section">
                            <div class="rcd-section-title">⚙️ Options</div>
                            <div class="rcd-toggle-row">
                                <div class="rcd-toggle compact" id="rcd-opt-log">
                                    <div>Show logs</div>
                                    <div class="rcd-switch" id="rcd-switch-log"></div>
                                </div>
                                <div class="rcd-toggle compact" id="rcd-opt-hide">
                                    <div>Hide after deletion</div>
                                    <div class="rcd-switch" id="rcd-switch-hide"></div>
                                </div>
                            </div>
                        </div>

                        <div class="rcd-log-container">
                            <div class="rcd-log" id="rcd-log"></div>
                            <div class="rcd-log-tools">
                                <button class="rcd-log-btn" id="rcd-log-copy" title="Copy log">⧉</button>
                                <button class="rcd-log-btn" id="rcd-log-clear" title="Clear log">🗑</button>
                            </div>
                        </div>
                    </div>

                    <!-- Chat List Tab -->
                    <div class="rcd-tab-content" id="rcd-tab-list">
                        <div class="rcd-section">
                            <div class="rcd-section-title">🎯 Select</div>
                            <div class="rcd-btn-group">
                                <button class="rcd-btn rcd-btn-secondary" id="rcd-chat-select-all">
                                    ✓ Select All
                                </button>
                                <button class="rcd-btn rcd-btn-secondary" id="rcd-chat-mark-mode">
                                    🖱️ Mark Mode
                                </button>
                                <button class="rcd-btn rcd-btn-secondary" id="rcd-chat-clear-all">
                                    ✕ Clear Selection
                                </button>
                                <button class="rcd-btn rcd-btn-danger" id="rcd-process-chats">
                                    🗑️ Process Selected
                                </button>
                            </div>
                            <div style="font-size: 11px; color: #94a3b8; margin-top: 8px; line-height: 1.35;">
                                Enable Mark Mode, then click chats in the left pane to select them.
                            </div>
                        </div>

                        <div class="rcd-section">
                            <div class="rcd-section-title">⚡ Actions</div>
                            <div class="rcd-btn-group">
                                <button class="rcd-btn rcd-btn-primary" id="rcd-chat-sweep">
                                    🧹 Purge
                                </button>
                                <button class="rcd-btn rcd-btn-secondary" id="rcd-hide-chats-only">
                                    👁️ Hide Selected Chats
                                </button>
                            </div>
                            <div style="font-size: 11px; color: #94a3b8; margin-top: 8px; line-height: 1.35;">
                                Process: Delete messages + hide (if enabled)<br>
                                Hide Only: Just hide without deleting
                            </div>
                        </div>

                        <div class="rcd-section">
                            <div class="rcd-section-title">⚙️ Options</div>
                            <div class="rcd-toggle-row">
                                <div class="rcd-toggle compact" id="rcd-opt-log-list">
                                    <div>Show logs</div>
                                    <div class="rcd-switch" id="rcd-switch-log-list"></div>
                                </div>
                                <div class="rcd-toggle compact" id="rcd-opt-hide-batch">
                                    <div>Hide after deletion</div>
                                    <div class="rcd-switch" id="rcd-switch-hide-batch"></div>
                                </div>
                            </div>
                        </div>

                        <div class="rcd-log-container">
                            <div class="rcd-log" id="rcd-log-chatlist"></div>
                            <div class="rcd-log-tools">
                                <button class="rcd-log-btn" id="rcd-loglist-copy" title="Copy log">⧉</button>
                                <button class="rcd-log-btn" id="rcd-loglist-clear" title="Clear log">🗑</button>
                            </div>
                        </div>
                    </div>

                    <!-- Settings Tab -->
                    <div class="rcd-tab-content" id="rcd-tab-settings">
                        <div class="rcd-settings-scroll">
                            <div class="rcd-settings-group">
                                <div class="rcd-settings-group-title">General</div>
                                <div class="rcd-input-labeled">
                                  <label class="rcd-input-label">Reddit username</label>
                                  <div class="rcd-input-group">
                                    <input
                                      type="text"
                                      class="rcd-input"
                                      id="rcd-username"
                                      placeholder="Your Reddit username (no u/)"
                                      value=""
                                    >
                                    <button class="rcd-btn rcd-btn-secondary" id="rcd-save-username">Save</button>
                                  </div>
                                </div>
                                <div class="rcd-input-row">
                                    <div class="rcd-input-labeled">
                                        <label class="rcd-input-label">Deletion delay (ms)</label>
                                        <input type="number" class="rcd-input" id="rcd-setting-delay" min="100" max="5000" step="50">
                                    </div>
                                    <div class="rcd-input-labeled">
                                        <label class="rcd-input-label">Max deletions per minute</label>
                                        <input type="number" class="rcd-input" id="rcd-setting-max-deletions" min="1" max="120">
                                    </div>
                                </div>
                                <div class="rcd-input-row">
                                    <div class="rcd-input-labeled">
                                        <label class="rcd-input-label">Empty chat load wait (seconds)</label>
                                        <input type="number" class="rcd-input" id="rcd-setting-empty-chat-wait-sec" min="0" max="30" step="0.5">
                                    </div>
                                    <div class="rcd-input-labeled">
                                        <label class="rcd-input-label">Marked message style</label>
                                        <select class="rcd-input" id="rcd-setting-mark-style">
                                            <option value="modern">Modern (highlight)</option>
                                            <option value="border">Border (1px)</option>
                                        </select>
                                    </div>
                                </div>

                                <div class="rcd-toggle" id="rcd-opt-modmail">
                                    <div>
                                        <div style="font-weight:600; color:#e2e8f0;">Process Mod Mail Chats</div>
                                        <div style="color:#94a3b8;">Allow selection and hiding of mod mail chats (read-only)</div>
                                    </div>
                                    <div class="rcd-switch" id="rcd-switch-modmail"></div>
                                </div>

                                <div class="rcd-toggle" id="rcd-opt-minimize">
                                    <div>
                                        <div style="font-weight:600; color:#e2e8f0;">Auto-minimize after deletion</div>
                                        <div style="color:#94a3b8;">Minimize widget when sweep completes</div>
                                    </div>
                                    <div class="rcd-switch" id="rcd-switch-minimize"></div>
                                </div>
                            </div>

                            <div class="rcd-settings-actions">
                                <button class="rcd-btn rcd-btn-secondary" id="rcd-reset-settings" style="flex: 1;">
                                    Reset Defaults
                                </button>
                                <button class="rcd-btn rcd-btn-primary" id="rcd-save-settings" style="flex: 1;">
                                    Save Settings
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            `;

      document.body.appendChild(panel);
      this.panel = panel;
    },

    attachEventListeners() {
      const safeOn = (id, event, handler) => {
        const el = document.getElementById(id);
        if (!el) return;
        el.addEventListener(event, handler);
      };

      this.enableDrag();

      safeOn("rcd-minimize", "click", () => {
        const minimizing = !this.panel.classList.contains("minimized");
        this.panel.classList.toggle("minimized", minimizing);
        if (minimizing) {
          this.dockToCorner();
        } else {
          this.clampPanelToViewport();
        }
        const btn = document.getElementById("rcd-minimize");
        if (btn) btn.textContent = minimizing ? "▢" : "−";
      });

      window.addEventListener("resize", () => {
        if (!this.panel) return;
        if (this.panel.classList.contains("minimized")) {
          this.dockToCorner();
          return;
        }
        const hasManualPosition =
          !!this.panel.style.left && this.panel.style.left !== "auto";
        if (hasManualPosition) {
          this.clampPanelToViewport();
        }
      });

      safeOn("rcd-stop-all", "click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        this.stopAll("user");
      });

      const tabs = this.panel.querySelectorAll(".rcd-tab");
      tabs.forEach((tab) => {
        tab.addEventListener("click", () => {
          this.switchTab(tab.dataset.tab);
        });
      });

      safeOn("rcd-save-username", "click", () => {
        const input = document.getElementById("rcd-username");
        const username = (input?.value || "").trim();
        Settings.set("username", username);
        this.log(`💾 Username saved: ${username || "(cleared)"}`);
        this.updateControls();
      });

      safeOn("rcd-save-settings", "click", () => {
        const delayEl = document.getElementById("rcd-setting-delay");
        const maxEl = document.getElementById("rcd-setting-max-deletions");
        const emptyWaitEl = document.getElementById(
          "rcd-setting-empty-chat-wait-sec",
        );
        const markEl = document.getElementById("rcd-setting-mark-style");
        const rawDelay = parseInt(delayEl?.value || "0", 10) || 0;
        const rawMax = parseInt(maxEl?.value || "0", 10) || 0;
        const rawEmptyWait =
          parseFloat(
            emptyWaitEl?.value || String(Settings.get("emptyChatLoadWaitSec")),
          ) || 0;
        const delayVal = Math.min(5000, Math.max(0, rawDelay));
        const maxVal = Math.max(0, rawMax);
        const emptyWaitVal = Math.min(30, Math.max(0, rawEmptyWait));
        const markVal = (markEl?.value || "modern").toLowerCase();

        Settings.set("deletionDelayMs", delayVal);
        Settings.set("maxDeletionsPerMinute", maxVal);
        Settings.set("emptyChatLoadWaitSec", emptyWaitVal);
        Settings.set("markStyle", markVal === "border" ? "border" : "modern");
        this.syncSettingsToUI();
        this.log("💾 Settings saved");
      });

      safeOn("rcd-reset-settings", "click", () => {
        Settings.current = { ...DEFAULT_SETTINGS };
        Settings.save();
        this.syncSettingsToUI();
        this.log("↩️ Settings reset to defaults");
      });

      safeOn("rcd-auto-mark", "click", () => {
        const username = Settings.get("username");
        if (!username) {
          this.log("❌ Please save username first", "error");
          return;
        }
        const total = Selection.autoSelectBoth(username);
        this.log(`✓ Total marked: ${total}`);
        this.updateMarkedCount();
        this.updateStatsDisplay();
        this.updateControls();
      });

      safeOn("rcd-toggle-mark", "click", () => {
        if (!state.containers.main) {
          this.log("❌ Select a chat first", "error");
          return;
        }
        state.markMode = !state.markMode;
        const btn = document.getElementById("rcd-toggle-mark");
        if (btn) {
          btn.textContent = state.markMode
            ? "🖱️ Mark Mode (ON)"
            : "🖱️ Mark Mode";
          btn.style.background = state.markMode
            ? "linear-gradient(135deg, #10b981, #059669)"
            : "";
        }
        Selection.attach(state.containers.main);
        if (state.containers.reply) Selection.attach(state.containers.reply);
        this.log(state.markMode ? "🖱️ Mark Mode ON" : "🖱️ Mark Mode OFF");
        this.updateControls();
      });

      safeOn("rcd-clear-marks", "click", () => {
        if (!state.containers.main) {
          this.log("❌ Select a chat first", "error");
          return;
        }
        Selection.clearAll(state.containers.main);
        if (state.containers.reply) Selection.clearAll(state.containers.reply);
        this.log("✓ Cleared selection");
        this.updateMarkedCount();
        this.updateStatsDisplay();
        this.updateControls();
      });

      safeOn("rcd-delete-marked", "click", async (e) => {
        e.preventDefault();
        e.stopImmediatePropagation();
        await Deletion.deleteSelected(state.containers.main);
        this.updateControls();
      });

      safeOn("rcd-primary-action", "click", async (e) => {
        e.preventDefault();
        e.stopPropagation();
        if (state.sweepActive) {
          Sweep.stop();
          return;
        }
        await Sweep.run();
      });

      safeOn("rcd-hide-chat-now", "click", async (e) => {
        e.preventDefault();
        e.stopPropagation();
        await HideChat.execute();
      });

      safeOn("rcd-log-clear", "click", () => {
        const log = document.getElementById("rcd-log");
        if (log)
          log.querySelectorAll(".rcd-log-entry").forEach((e) => e.remove());
      });

      safeOn("rcd-log-copy", "click", async () => {
        const log = document.getElementById("rcd-log");
        if (!log) return;
        const text = Array.from(log.querySelectorAll(".rcd-log-entry"))
          .map((el) => el.textContent || "")
          .join("\n");
        if (!text) return;
        try {
          await navigator.clipboard.writeText(text);
          this.log("📋 Log copied", "success");
        } catch {
          this.log("❌ Copy failed", "error");
        }
      });

      safeOn("rcd-loglist-clear", "click", () => {
        const log = document.getElementById("rcd-log-chatlist");
        if (log)
          log.querySelectorAll(".rcd-log-entry").forEach((e) => e.remove());
      });

      safeOn("rcd-loglist-copy", "click", async () => {
        const log = document.getElementById("rcd-log-chatlist");
        if (!log) return;
        const text = Array.from(log.querySelectorAll(".rcd-log-entry"))
          .map((el) => el.textContent || "")
          .join("\n");
        if (!text) return;
        try {
          await navigator.clipboard.writeText(text);
          this.log("📋 Log copied", "success");
        } catch {
          this.log("❌ Copy failed", "error");
        }
      });

      safeOn("rcd-opt-log", "click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        Settings.set("showLog", !Settings.get("showLog"));
        this.syncSettingsToUI();
        this.log(`Show logs: ${Settings.get("showLog") ? "ON" : "OFF"}`);
      });

      safeOn("rcd-opt-log-list", "click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        Settings.set("showLog", !Settings.get("showLog"));
        this.syncSettingsToUI();
        this.log(`Show logs: ${Settings.get("showLog") ? "ON" : "OFF"}`);
      });

      safeOn("rcd-opt-hide", "click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        Settings.set("hideAfterDeletion", !Settings.get("hideAfterDeletion"));
        this.syncSettingsToUI();
        this.log(
          `Hide after deletion: ${Settings.get("hideAfterDeletion") ? "ON" : "OFF"}`,
        );
      });

      safeOn("rcd-opt-hide-batch", "click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        Settings.set("hideAfterDeletion", !Settings.get("hideAfterDeletion"));
        this.syncSettingsToUI();
        this.log(
          `Hide after deletion: ${Settings.get("hideAfterDeletion") ? "ON" : "OFF"}`,
        );
      });

      safeOn("rcd-opt-minimize", "click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        Settings.set(
          "autoMinimizeAfterDeletion",
          !Settings.get("autoMinimizeAfterDeletion"),
        );
        this.syncSettingsToUI();
        this.log(
          `Auto-minimize: ${Settings.get("autoMinimizeAfterDeletion") ? "ON" : "OFF"}`,
        );
      });

      safeOn("rcd-opt-modmail", "click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        Settings.set("processModMails", !Settings.get("processModMails"));
        this.syncSettingsToUI();
        this.log(
          `Process Mod Mails: ${Settings.get("processModMails") ? "ON" : "OFF"}`,
        );
      });

      // Chat List controls
      safeOn("rcd-chat-mark-mode", "click", () => {
        ChatList.chatListMarkMode = !ChatList.chatListMarkMode;
        const btn = document.getElementById("rcd-chat-mark-mode");
        if (btn) {
          btn.textContent = ChatList.chatListMarkMode
            ? "🖱️ Mark Mode (ON)"
            : "🖱️ Mark Mode";
          btn.style.background = ChatList.chatListMarkMode
            ? "linear-gradient(135deg, #10b981, #059669)"
            : "";
        }

        const container = ChatList.findContainer();
        if (container) {
          ChatList.attachChatClickHandlers(container);
        } else {
          this.log("❌ Could not find chat list", "error");
        }
        this.updateChatListControls();
      });

      safeOn("rcd-chat-select-all", "click", () => {
        const container = ChatList.findContainer();
        if (!container) {
          this.log("❌ Could not find chat list", "error");
          return;
        }
        const count = ChatList.selectAllChats(container);
        this.log(`✓ Selected all ${count} chats`);
        this.updateChatListCount();
        this.updateChatListControls();
      });

      safeOn("rcd-chat-clear-all", "click", () => {
        const container = ChatList.findContainer();
        if (!container) {
          this.log("❌ Could not find chat list", "error");
          return;
        }
        ChatList.clearAllChats(container);
        this.log("✓ Cleared all selections");
        this.updateChatListCount();
        this.updateChatListControls();
      });

      safeOn("rcd-process-chats", "click", async () => {
        const container = ChatList.findContainer();
        if (!container) {
          this.log("❌ Could not find chat list", "error");
          return;
        }
        await ChatList.processSelectedChats(container);
      });

      safeOn("rcd-hide-chats-only", "click", async () => {
        const container = ChatList.findContainer();
        if (!container) {
          this.log("❌ Could not find chat list", "error");
          return;
        }
        await ChatList.hideSelectedChats(container);
      });

      safeOn("rcd-chat-sweep", "click", () => {
        if (ChatList.chatListProcessing || ChatList.chatListPurgeActive) {
          ChatList.stopPurge();
          return;
        }
        ChatList.purge();
      });
    },

    switchTab(tabName) {
      state.currentTab = tabName;
      const tabs = this.panel.querySelectorAll(".rcd-tab");
      const contents = this.panel.querySelectorAll(".rcd-tab-content");

      tabs.forEach((tab) => {
        tab.classList.toggle("active", tab.dataset.tab === tabName);
      });

      contents.forEach((content) => {
        content.classList.toggle("active", content.id === `rcd-tab-${tabName}`);
      });

      if (tabName === "list") {
        const container = ChatList.findContainer();
        if (container) ChatList.attachChatClickHandlers(container);
      }

      this.updateStatsDisplay();
    },

    updateMarkedCount() {
      let count = 0;
      if (state.containers.main) {
        count = Selection.getMarked(state.containers.main).length;
      }
      state.markedCount = count;
    },

    // ========================= STATS DISPLAY (REWRITTEN) =========================
    updateStatsDisplay() {
      const statsEl = document.getElementById("rcd-stats-display");
      if (!statsEl) return;

      const lbl1 = document.getElementById("rcd-stat-lbl-1");
      const val1 = document.getElementById("rcd-stat-val-1");
      const row2 = document.getElementById("rcd-stat-row-2");
      const lbl2 = document.getElementById("rcd-stat-lbl-2");
      const val2 = document.getElementById("rcd-stat-val-2");
      const lbl3 = document.getElementById("rcd-stat-lbl-3");
      const val3 = document.getElementById("rcd-stat-val-3");

      // Hide stats on settings tab
      if (state.currentTab === "settings") {
        statsEl.classList.add("hidden");
        return;
      }
      statsEl.classList.remove("hidden");

      const isDeleting =
        state.sweepActive ||
        state.isBusy ||
        ChatList.chatListProcessing ||
        ChatList.chatListPurgeActive;

      if (state.currentTab === "chat") {
        // Row 1: messages selected
        if (lbl1) lbl1.textContent = "Selected";
        if (val1) val1.textContent = `${state.markedCount} msgs`;

        // Row 2: deleting x/y — only shown while active
        if (
          isDeleting &&
          (state.deleteProgress.current > 0 || state.deleteProgress.total > 0)
        ) {
          row2?.classList.remove("hidden");
          if (lbl2) lbl2.textContent = "Deleting";
          if (val2)
            val2.textContent = `${state.deleteProgress.current} / ${state.deleteProgress.total}`;
        } else {
          row2?.classList.add("hidden");
        }

        // Row 3: session deleted messages
        if (lbl3) lbl3.textContent = "Session";
        if (val3) val3.textContent = `${state.totalDeleted} deleted`;
      } else {
        // Chat list tab
        const isRunning =
          ChatList.chatListProcessing || ChatList.chatListPurgeActive;

        // Row 1: chats selected / progress
        if (lbl1) lbl1.textContent = "Selected";
        if (isRunning && state.chatListProgress.total > 0) {
          if (val1)
            val1.textContent = `${state.chatListProgress.current} / ${state.chatListProgress.total} chats`;
        } else {
          if (val1) val1.textContent = `${ChatList.markedChatIds.size} chats`;
        }

        // Row 2: deleting x/y for current sweep inside the chat — shown while sweep is active
        if (
          isDeleting &&
          (state.deleteProgress.current > 0 || state.deleteProgress.total > 0)
        ) {
          row2?.classList.remove("hidden");
          if (lbl2) lbl2.textContent = "Deleting";
          if (val2)
            val2.textContent = `${state.deleteProgress.current} / ${state.deleteProgress.total}`;
        } else {
          row2?.classList.add("hidden");
        }

        // Row 3: session chats processed
        if (lbl3) lbl3.textContent = "Session";
        if (val3) val3.textContent = `${state.chatsProcessed} chats done`;
      }
    },

    updateControls() {
      const btnAuto = document.getElementById("rcd-auto-mark");
      const btnMark = document.getElementById("rcd-toggle-mark");
      const btnClear = document.getElementById("rcd-clear-marks");
      const btnDelete = document.getElementById("rcd-delete-marked");
      const btnPrimary = document.getElementById("rcd-primary-action");
      const btnStopAll = document.getElementById("rcd-stop-all");
      const btnSaveSettings = document.getElementById("rcd-save-settings");
      const btnResetSettings = document.getElementById("rcd-reset-settings");
      const inputDelay = document.getElementById("rcd-setting-delay");
      const inputMax = document.getElementById("rcd-setting-max-deletions");
      const inputEmptyWait = document.getElementById(
        "rcd-setting-empty-chat-wait-sec",
      );
      const inputUsername = document.getElementById("rcd-username");
      const btnSaveUsername = document.getElementById("rcd-save-username");

      const hasContainer = !!state.containers.main;
      const hasSelection = state.markedCount > 0;
      const hasUsername = !!Settings.get("username");
      const isRunning =
        state.sweepActive ||
        state.isBusy ||
        ChatList.chatListProcessing ||
        ChatList.chatListPurgeActive;

      if (btnAuto)
        btnAuto.disabled = !hasContainer || !hasUsername || isRunning;
      if (btnMark) btnMark.disabled = !hasContainer || isRunning;
      if (btnClear)
        btnClear.disabled = !hasContainer || !hasSelection || isRunning;
      if (btnDelete)
        btnDelete.disabled = !hasContainer || !hasSelection || isRunning;

      if (btnPrimary) {
        const sweepRunning = state.sweepActive || state.isBusy;
        btnPrimary.disabled = !hasContainer || sweepRunning;
        btnPrimary.textContent = "🧹 Auto Sweep";
        btnPrimary.classList.remove("rcd-btn-danger");
        btnPrimary.classList.add("rcd-btn-primary");
      }

      if (btnStopAll) {
        btnStopAll.style.display = isRunning ? "flex" : "none";
        btnStopAll.disabled = !isRunning;
      }

      if (btnSaveSettings) btnSaveSettings.disabled = isRunning;
      if (btnResetSettings) btnResetSettings.disabled = isRunning;
      if (inputDelay) inputDelay.disabled = isRunning;
      if (inputMax) inputMax.disabled = isRunning;
      if (inputEmptyWait) inputEmptyWait.disabled = isRunning;
      if (inputUsername) inputUsername.disabled = isRunning;
      if (btnSaveUsername) btnSaveUsername.disabled = isRunning;
    },

    stopAll(reason = "user") {
      state.cancelSweep = true;
      state.cancelDelete = true;
      state.cancelChatList = true;
      Throttle.reset();
      if (state.sweepActive) Sweep.stop();
      if (ChatList.chatListPurgeActive) ChatList.stopPurge();
      ChatList.chatListProcessing = false;
      state.isBusy = false;
      this.setHeaderStatus("stopping");
      this.updateControls();
      this.updateChatListControls();
      this.log(`■ Stop requested (${reason})`, "warning");
    },

    updateChatListCount() {
      this.updateStatsDisplay();
    },

    updateChatListControls() {
      const btnMarkMode = document.getElementById("rcd-chat-mark-mode");
      const btnSelectAll = document.getElementById("rcd-chat-select-all");
      const btnClearAll = document.getElementById("rcd-chat-clear-all");
      const btnProcess = document.getElementById("rcd-process-chats");
      const btnHideOnly = document.getElementById("rcd-hide-chats-only");
      const btnSweep = document.getElementById("rcd-chat-sweep");

      const hasSelection = ChatList.markedChatIds.size > 0;
      const isProcessing = ChatList.chatListProcessing;
      const isRunning =
        ChatList.chatListProcessing || ChatList.chatListPurgeActive;

      if (btnMarkMode) btnMarkMode.disabled = isProcessing;
      if (btnSelectAll) btnSelectAll.disabled = isProcessing;
      if (btnClearAll) btnClearAll.disabled = isProcessing || !hasSelection;
      if (btnProcess) btnProcess.disabled = isProcessing || !hasSelection;
      if (btnHideOnly) btnHideOnly.disabled = isProcessing || !hasSelection;
      if (btnSweep) {
        btnSweep.textContent = "🧹 Purge";
        btnSweep.classList.remove("rcd-btn-danger");
        btnSweep.classList.add("rcd-btn-primary");
        btnSweep.disabled = isRunning;
      }
    },

    setStatus(text, tone) {
      const statusEl = document.getElementById("rcd-status-inline");
      if (statusEl) {
        statusEl.textContent = "";
        statusEl.className = "rcd-status-inline";
      }
      if (!text) {
        this.setHeaderStatus("idle");
        return;
      }
      const normalized = String(text).toLowerCase();
      if (tone === "warn" && normalized.includes("stop")) {
        this.setHeaderStatus("stopping");
        return;
      }
      if (tone === "warn") {
        this.setHeaderStatus("stopped");
        return;
      }
      this.setHeaderStatus("running", text);
    },

    setHeaderStatus(stateName = "idle", text = "") {
      const el = document.getElementById("rcd-header-status");
      if (!el) return;
      if (this.statusResetTimer) {
        clearTimeout(this.statusResetTimer);
        this.statusResetTimer = null;
      }

      const allowed = new Set([
        "idle",
        "running",
        "stopping",
        "stopped",
        "error",
      ]);
      const normalizedState = allowed.has(stateName) ? stateName : "idle";
      const labelMap = {
        idle: "Idle",
        running: "Running",
        stopping: "Stopping",
        stopped: "Stopped",
        error: "Error",
      };
      const label = text
        ? String(text)
            .replace(/^🔄\s*/, "")
            .trim()
        : labelMap[normalizedState];
      el.className = `rcd-header-status ${normalizedState}`;
      el.textContent = label || labelMap[normalizedState];
    },

    flashHeaderStatus(stateName, text, delayMs = 1500) {
      this.setHeaderStatus(stateName, text);
      this.statusResetTimer = setTimeout(() => {
        this.setHeaderStatus("idle");
      }, delayMs);
    },

    syncSettingsToUI() {
      const swLog = document.getElementById("rcd-switch-log");
      const swLogList = document.getElementById("rcd-switch-log-list");
      const swHide = document.getElementById("rcd-switch-hide");
      const swHideBatch = document.getElementById("rcd-switch-hide-batch");
      const swMin = document.getElementById("rcd-switch-minimize");
      const swModmail = document.getElementById("rcd-switch-modmail");
      const inputDelay = document.getElementById("rcd-setting-delay");
      const inputMax = document.getElementById("rcd-setting-max-deletions");
      const inputEmptyWait = document.getElementById(
        "rcd-setting-empty-chat-wait-sec",
      );
      const inputMarkStyle = document.getElementById("rcd-setting-mark-style");
      const inputUsername = document.getElementById("rcd-username");
      const log = document.getElementById("rcd-log");
      const logList = document.getElementById("rcd-log-chatlist");

      if (swLog) swLog.classList.toggle("on", Settings.get("showLog"));
      if (swLogList) swLogList.classList.toggle("on", Settings.get("showLog"));
      if (swHide)
        swHide.classList.toggle("on", Settings.get("hideAfterDeletion"));
      if (swHideBatch)
        swHideBatch.classList.toggle("on", Settings.get("hideAfterDeletion"));
      if (swMin)
        swMin.classList.toggle("on", Settings.get("autoMinimizeAfterDeletion"));
      if (swModmail)
        swModmail.classList.toggle("on", Settings.get("processModMails"));

      if (log) log.classList.toggle("hidden", !Settings.get("showLog"));
      if (logList) logList.classList.toggle("hidden", !Settings.get("showLog"));

      if (inputDelay)
        inputDelay.value = String(Settings.get("deletionDelayMs"));
      if (inputMax)
        inputMax.value = String(Settings.get("maxDeletionsPerMinute"));
      if (inputEmptyWait)
        inputEmptyWait.value = String(Settings.get("emptyChatLoadWaitSec"));
      if (inputMarkStyle)
        inputMarkStyle.value = String(Settings.get("markStyle") || "modern");
      if (inputUsername)
        inputUsername.value = String(Settings.get("username") || "");
    },

    log(message, type = "info") {
      const logId = this.getActiveLogId();
      const logEl = document.getElementById(logId);
      if (!logEl) return;

      const entry = document.createElement("div");
      entry.className = `rcd-log-entry ${type}`;
      const time = new Date().toLocaleTimeString("en-US", {
        hour: "2-digit",
        minute: "2-digit",
        second: "2-digit",
      });
      entry.innerHTML = `<span class="rcd-log-time">[${time}]</span>${message}`;
      logEl.insertBefore(entry, logEl.firstChild);

      while (logEl.children.length > CONFIG.LOG_MAX_LINES) {
        logEl.removeChild(logEl.lastChild);
      }
    },

    getActiveLogId() {
      const active = this.panel.querySelector(".rcd-tab-content.active");
      if (!active) return "rcd-log";
      if (active.id === "rcd-tab-list") return "rcd-log-chatlist";
      return "rcd-log";
    },

    dockToCorner() {
      if (!this.panel) return;
      this.panel.style.left = "auto";
      this.panel.style.top = "auto";
      this.panel.style.right = "16px";
      this.panel.style.bottom = "16px";
    },

    clampPanelToViewport() {
      if (!this.panel) return;
      const rect = this.panel.getBoundingClientRect();
      const margin = 8;
      const maxLeft = Math.max(margin, window.innerWidth - rect.width - margin);
      const maxTop = Math.max(
        margin,
        window.innerHeight - rect.height - margin,
      );
      const clampedLeft = Math.min(Math.max(rect.left, margin), maxLeft);
      const clampedTop = Math.min(Math.max(rect.top, margin), maxTop);
      this.panel.style.right = "auto";
      this.panel.style.bottom = "auto";
      this.panel.style.left = `${clampedLeft}px`;
      this.panel.style.top = `${clampedTop}px`;
    },

    startUrlWatcher() {
      state.lastUrl = location.href;
      setInterval(() => {
        if (state.lastUrl !== location.href) {
          state.lastUrl = location.href;
          Containers.bindMainContainer({ silent: true });
          this.updateControls();
        }
      }, CONFIG.URL_WATCH_INTERVAL_MS);
    },

    enableDrag() {
      const header = this.panel.querySelector(".rcd-header");
      if (!header) return;

      let isDragging = false;
      let initialX = 0;
      let initialY = 0;
      let rafId = null;

      header.addEventListener("mousedown", (e) => {
        if (e.button !== 0) return;
        if (e.target && e.target.closest("button, input, select, textarea, a"))
          return;
        isDragging = true;
        initialX = e.clientX - this.panel.offsetLeft;
        initialY = e.clientY - this.panel.offsetTop;
      });

      document.addEventListener("mousemove", (e) => {
        if (!isDragging) return;
        if (rafId) return;
        rafId = requestAnimationFrame(() => {
          const rect = this.panel.getBoundingClientRect();
          const margin = 8;
          const maxX = Math.max(
            margin,
            window.innerWidth - rect.width - margin,
          );
          const maxY = Math.max(
            margin,
            window.innerHeight - rect.height - margin,
          );
          const currentX = Math.min(
            Math.max(e.clientX - initialX, margin),
            maxX,
          );
          const currentY = Math.min(
            Math.max(e.clientY - initialY, margin),
            maxY,
          );
          this.panel.style.right = "auto";
          this.panel.style.bottom = "auto";
          this.panel.style.left = `${currentX}px`;
          this.panel.style.top = `${currentY}px`;
          rafId = null;
        });
      });

      document.addEventListener("mouseup", () => {
        isDragging = false;
        if (rafId) {
          cancelAnimationFrame(rafId);
          rafId = null;
        }
        this.clampPanelToViewport();
      });
    },
  };

  function init() {
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", init);
      return;
    }

    setTimeout(() => {
      UI.init();
    }, CONFIG.INIT_DELAY_MS);
  }

  init();
})();