Reddit Chat Delete Suite

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

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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();
})();