YT Comment Filter (Improved)

Automatically hide YouTube comments based on username, specific keywords, and spam text patterns. Features Dual-Mode Observer.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         YT Comment Filter (Improved)
// @namespace    YT Comment Filter (Improved)
// @version      6.2
// @description  Automatically hide YouTube comments based on username, specific keywords, and spam text patterns. Features Dual-Mode Observer.
// @author       Mochamad Adi MR (adimuham.mad)
// @match        *://*.youtube.com/watch*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @license      MIT
// @compatible   chrome
// @compatible   firefox
// @compatible   edge
// @compatible   opera
// @compatible   safari
// @supportURL   https://buymeacoffee.com/mochadimr
// @homepageURL  https://gist.github.com/adimuhamad/143a06052413aaecb6ddf1a4e39103c1
// @run-at       document-end
// ==/UserScript==

(function () {
    "use strict";

    const CONFIG = {
        STORAGE_KEY_USERS: "yt_filter_custom_words",
        STORAGE_KEY_KEYWORDS: "yt_filter_custom_content_keywords",
        STORAGE_KEY_MODE: "yt_filter_observer_mode",
        THROTTLE_DELAY: 1000,
        SELECTORS: {
            COMMENT_CONTAINER: "ytd-comment-view-model",
            COMMENT_TEXT: "#content-text",
            AUTHOR_TEXT: "#author-text span",
            HEADER_TARGET: "ytd-comments-header-renderer #additional-section",
            THREAD_RENDERER: "ytd-comment-thread-renderer",
            REPLIES_CONTAINER: "#replies",
        },
        CLASSES: {
            HIDDEN: "yt-comment-filter-hidden",
            SHOW_HIDDEN: "yt-filter-showing-hidden",
        },
        DEFAULTS: {
            BLOCKED_USERS: ["vip"],
            BLOCKED_KEYWORDS: ["pulauwin"],
            MODE: "efficient",
        },
        REGEX_NON_LATIN:
            /[^\u0000-\u007F\u00A0-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u02B0-\u02FF\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0800-\u083F\u0840-\u085F\u0860-\u087F\u08A0-\u08FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF\u1000-\u109F\u10A0-\u10FF\u1100-\u11FF\u1200-\u125F\u1280-\u12BF\u13A0-\u13FF\u1400-\u167F\u1680-\u169F\u16A0-\u16FF\u1700-\u171F\u1720-\u173F\u1740-\u175F\u1760-\u177F\u1780-\u17FF\u1800-\u18AF\u1900-\u194F\u1950-\u197F\u1980-\u19DF\u19E0-\u19FF\u1A00-\u1A1F\u1A20-\u1A5F\u1A80-\u1AFF\u1B00-\u1B7F\u1B80-\u1BBF\u1BC0-\u1BFF\u1C00-\u1C4F\u1C50-\u1C7F\u1C90-\u1CBF\u1CC0-\u1CCF\u1CD0-\u1CFF\u1E00-\u1EFF\u1F00-\u1FFF\u2000-\u206F\u2070-\u20CF\u20D0-\u20FF\u2150-\u218F\u2C60-\u2C7F\u2C80-\u2CFF\u2D00-\u2D2F\u2D30-\u2D7F\u2D80-\u2DDF\u2DE0-\u2DFF\u2E00-\u2E7F\u2E80-\u2EFF\u2F00-\u2FDF\u2FF0-\u2FFF\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3130-\u318F\u3190-\u319F\u31A0-\u31BF\u31C0-\u31EF\u31F0-\u31FF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FFF\uA000-\uA48F\uA490-\uA4CF\uA4D0-\uA4FF\uA500-\uA63F\uA640-\uA69F\uA6A0-\uA6FF\uA700-\uA71F\uA720-\uA7FF\uA800-\uA82F\uA830-\uA83F\uA840-\uA87F\uA880-\uA8DF\uA8E0-\uA8FF\uA900-\uA92F\uA930-\uA95F\uA960-\uA97F\uA980-\uA9DF\uA9E0-\uA9FF\uAA00-\uAA3F\uAA40-\uAA6F\uAA70-\uAAAB\uAAAC-\uAAAF\uAAB0-\uAABF\uAAC0-\uAADF\uAAE0-\uAAEF\uAAF0-\uAAFF\uAB00-\uAB2F\uAB30-\uAB6F\uAB70-\uABBF\uABC0-\uABFF\uAC00-\uD7AF\uD7B0-\uD7FF\uF900-\uFAFF\uFB00-\uFB4F\uFB50-\uFDFF\uFE00-\uFE0F\uFE10-\uFE1F\uFE20-\uFE2F\uFE30-\uFE4F\uFE50-\uFE6F\uFE70-\uFEFF]/,
    };

    const UI_CONSTANTS = {
        ID: {
            DIALOG_MAIN: "yt-filter-dialog-main",
            DIALOG_CONFIRM: "yt-filter-dialog-confirm",
            OVERLAY: "yt-filter-overlay",
            USER_INPUT: "yt-filter-input-users",
            KEYWORD_INPUT: "yt-filter-input-keywords",
        },
    };

    const Theme = {
        getColors() {
            const isDark =
                document.documentElement.getAttribute("dark") === "true" ||
                (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches);

            return {
                background: isDark ? "#212121" : "#ffffff",
                text: isDark ? "#f1f1f1" : "#0f0f0f",
                textSec: isDark ? "#aaaaaa" : "#606060",
                border: isDark ? "#3e3e3e" : "#e5e5e5",
                primary: "#3ea6ff",
                danger: "#ff4e45",
                buttonBg: isDark ? "#303030" : "#f2f2f2",
                inputBg: isDark ? "#121212" : "#f9f9f9",
                overlay: "rgba(0,0,0,0.7)",
                shadow: "rgba(0,0,0,0.5)",
            };
        },
    };

    const Utils = {
        debounce: (func, wait) => {
            let timeout;
            return (...args) => {
                clearTimeout(timeout);
                timeout = setTimeout(() => func(...args), wait);
            };
        },
        throttle: (func, limit) => {
            let inThrottle;
            return function () {
                const args = arguments,
                    context = this;
                if (!inThrottle) {
                    func.apply(context, args);
                    inThrottle = true;
                    setTimeout(() => (inThrottle = false), limit);
                }
            };
        },
        escapeRegExp: (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
        createLeetPattern: (word) => {
            return Utils.escapeRegExp(word)
                .replace(/a/gi, "[a4@]")
                .replace(/i/gi, "[i1l]")
                .replace(/e/gi, "[e3]")
                .replace(/o/gi, "[o0]")
                .replace(/s/gi, "[s5$]")
                .replace(/t/gi, "[t7+]")
                .replace(/g/gi, "[g9]");
        },
        setStyle: (element, styles) => Object.assign(element.style, styles),
    };

    const State = {
        isShowingHidden: false,
        forbiddenUserRegex: null,
        forbiddenContentRegex: null,
        observerMode: "efficient",

        loadSettings: () => {
            State.observerMode = GM_getValue(CONFIG.STORAGE_KEY_MODE, CONFIG.DEFAULTS.MODE);

            const loadRegex = (key, defaults) => {
                const saved = GM_getValue(key, "");
                const custom = saved
                    ? saved
                          .split(",")
                          .map((w) => w.trim())
                          .filter(Boolean)
                    : [];
                const all = [...new Set([...defaults, ...custom])];
                return all.length > 0 ? new RegExp(all.map(Utils.createLeetPattern).join("|"), "i") : null;
            };

            State.forbiddenUserRegex = loadRegex(CONFIG.STORAGE_KEY_USERS, CONFIG.DEFAULTS.BLOCKED_USERS);
            State.forbiddenContentRegex = loadRegex(CONFIG.STORAGE_KEY_KEYWORDS, CONFIG.DEFAULTS.BLOCKED_KEYWORDS);
        },

        saveSettings: (usersStr, keywordsStr) => {
            GM_setValue(CONFIG.STORAGE_KEY_USERS, usersStr);
            GM_setValue(CONFIG.STORAGE_KEY_KEYWORDS, keywordsStr);
            State.loadSettings();
            FilterEngine.processAll();
        },

        applySwitchMode: () => {
            const newMode = State.observerMode === "efficient" ? "fast" : "efficient";
            GM_setValue(CONFIG.STORAGE_KEY_MODE, newMode);
            State.observerMode = newMode;
            ObserverManager.start();
        },
    };

    const SettingsUI = {
        openSettings() {
            if (document.getElementById(UI_CONSTANTS.ID.DIALOG_MAIN)) return;
            const colors = Theme.getColors();
            const userVal = GM_getValue(CONFIG.STORAGE_KEY_USERS, "");
            const keywordVal = GM_getValue(CONFIG.STORAGE_KEY_KEYWORDS, "");

            this._createModal(
                UI_CONSTANTS.ID.DIALOG_MAIN,
                "Filter Configuration",
                colors,
                1000,
                (contentDiv) => {
                    contentDiv.append(this._createLabel("Blocked Usernames", colors));

                    const userArea = this._createTextarea(
                        UI_CONSTANTS.ID.USER_INPUT,
                        userVal,
                        "Example: login, browser, search, google, etc",
                        colors
                    );

                    contentDiv.append(userArea);
                    const spacer = document.createElement("div");
                    Utils.setStyle(spacer, { height: "16px" });
                    contentDiv.append(spacer);
                    contentDiv.append(this._createLabel("Blocked Keywords (Content)", colors));

                    const keyArea = this._createTextarea(
                        UI_CONSTANTS.ID.KEYWORD_INPUT,
                        keywordVal,
                        "Example: winningisland, luckyeagle, gambletoto, etc",
                        colors
                    );

                    contentDiv.append(keyArea);
                    setTimeout(() => userArea.focus(), 100);
                },
                (footerDiv) => {
                    const btnReset = this._createBtn("Reset Filters", "transparent", colors.danger, colors);
                    Utils.setStyle(btnReset, { border: `1px solid ${colors.danger}` });

                    btnReset.onmouseover = () => {
                        btnReset.style.backgroundColor = colors.danger;
                        btnReset.style.color = "#fff";
                    };

                    btnReset.onmouseout = () => {
                        btnReset.style.backgroundColor = "transparent";
                        btnReset.style.color = colors.danger;
                    };

                    btnReset.onclick = () => {
                        this.openConfirm(
                            "Reset All Filters?",
                            "This will delete all custom usernames and keywords.\nAre you sure?",
                            "Yes, Reset",
                            colors.danger,
                            () => {
                                document.getElementById(UI_CONSTANTS.ID.USER_INPUT).value = "";
                                document.getElementById(UI_CONSTANTS.ID.KEYWORD_INPUT).value = "";
                                State.saveSettings("", "");
                            }
                        );
                    };

                    const btnSave = this._createBtn("Save Changes", colors.primary, "#ffffff", colors);

                    btnSave.onclick = () => {
                        const uVal = document.getElementById(UI_CONSTANTS.ID.USER_INPUT).value;
                        const kVal = document.getElementById(UI_CONSTANTS.ID.KEYWORD_INPUT).value;
                        State.saveSettings(uVal, kVal);
                        this.close(UI_CONSTANTS.ID.DIALOG_MAIN);
                    };

                    footerDiv.append(btnReset, btnSave);
                }
            );
        },

        openModeSwitcher() {
            if (document.getElementById(UI_CONSTANTS.ID.DIALOG_CONFIRM)) return;
            const colors = Theme.getColors();
            const currentMode = State.observerMode.toUpperCase();
            const targetMode = currentMode === "EFFICIENT" ? "FAST" : "EFFICIENT";

            const desc =
                targetMode === "FAST"
                    ? "Comments will disappear instantly, but browser CPU usage may increase slightly."
                    : "Saves battery and CPU usage. Comments disappear after scroll stops.";

            this.openConfirm(
                "Switch Observer Mode?",
                `Current Mode: <b>${currentMode}</b>\n\nDo you want to switch to <b>${targetMode}</b> mode?\n${desc}`,
                `Switch to ${targetMode}`,
                colors.primary,
                () => {
                    State.applySwitchMode();
                }
            );
        },

        openConfirm(title, messageHtml, confirmBtnText, confirmBtnColor, onConfirmCallback) {
            const colors = Theme.getColors();

            this._createModal(
                UI_CONSTANTS.ID.DIALOG_CONFIRM,
                title,
                colors,
                2000,
                (contentDiv) => {
                    const p = document.createElement("p");
                    p.innerHTML = messageHtml.replace(/\n/g, "<br>");
                    
                    Utils.setStyle(p, { 
                        lineHeight: "1.5", 
                        fontSize: "14px", 
                        color: colors.textSec 
                    });
                    
                    contentDiv.append(p);
                },
                (footerDiv) => {
                    const btnCancel = this._createBtn("Cancel", "transparent", colors.text, colors);
                    btnCancel.onclick = () => this.close(UI_CONSTANTS.ID.DIALOG_CONFIRM);
                    const btnConfirm = this._createBtn(confirmBtnText, confirmBtnColor, "#ffffff", colors);
                    
                    btnConfirm.onclick = () => {
                        onConfirmCallback();
                        this.close(UI_CONSTANTS.ID.DIALOG_CONFIRM);
                    };

                    footerDiv.append(btnCancel, btnConfirm);
                }
            );
        },

        _createModal(dialogId, titleText, colors, zIndex, contentBuilder, actionBuilder) {
            const overlay = document.createElement("div");
            overlay.id = dialogId + "-overlay";

            Utils.setStyle(overlay, {
                position: "fixed",
                top: "0",
                left: "0",
                width: "100%",
                height: "100%",
                backgroundColor: colors.overlay,
                zIndex: zIndex,
                backdropFilter: "blur(3px)",
            });

            const dialog = document.createElement("div");
            dialog.id = dialogId;

            Utils.setStyle(dialog, {
                position: "fixed",
                top: "50%",
                left: "50%",
                transform: "translate(-50%, -50%)",
                backgroundColor: colors.background,
                color: colors.text,
                borderRadius: "12px",
                padding: "24px",
                width: "450px",
                maxWidth: "90vw",
                maxHeight: "85vh",
                zIndex: zIndex + 1,
                boxShadow: `0 10px 25px ${colors.shadow}`,
                fontFamily: "Roboto, Arial, sans-serif",
                display: "flex",
                flexDirection: "column",
                overflowY: "auto",
            });

            const header = document.createElement("div");

            Utils.setStyle(header, {
                display: "flex",
                justifyContent: "space-between",
                alignItems: "center",
                marginBottom: "16px",
            });

            const title = document.createElement("h2");
            title.textContent = titleText;

            Utils.setStyle(title, {
                margin: "0", 
                fontSize: "20px", 
                fontWeight: "500"
            });

            const closeBtn = document.createElement("button");
            closeBtn.innerHTML = `<svg viewBox="0 0 24 24" style="width:24px;height:24px;fill:${colors.text}"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>`;

            Utils.setStyle(closeBtn, {
                background: "none", 
                border: "none", 
                cursor: "pointer", 
                padding: "4px"
            });

            closeBtn.onclick = () => this.close(dialogId);
            header.append(title, closeBtn);
            const content = document.createElement("div");
            contentBuilder(content);
            const actions = document.createElement("div");

            Utils.setStyle(actions, {
                display: "flex", 
                justifyContent: "flex-end", 
                gap: "10px", 
                marginTop: "24px"
            });

            actionBuilder(actions);
            dialog.append(header, content, actions);
            document.body.append(overlay, dialog);

            const escHandler = (e) => {
                if (e.key === "Escape") this.close(dialogId);
            };

            dialog.dataset.escHandler = "true";
            document.addEventListener("keydown", escHandler);
            this._escHandlers = this._escHandlers || {};
            this._escHandlers[dialogId] = escHandler;
        },

        _createLabel(text, colors) {
            const lbl = document.createElement("label");
            lbl.textContent = text;

            Utils.setStyle(lbl, {
                display: "block",
                marginBottom: "8px",
                fontSize: "13px",
                fontWeight: "500",
                color: colors.text,
            });

            return lbl;
        },

        _createTextarea(id, val, placeholder, colors) {
            const ta = document.createElement("textarea");
            ta.id = id;
            ta.value = val;
            ta.placeholder = placeholder;

            Utils.setStyle(ta, {
                width: "100%",
                height: "80px",
                backgroundColor: colors.inputBg,
                color: colors.text,
                border: `1px solid ${colors.border}`,
                borderRadius: "8px",
                padding: "10px",
                fontFamily: "monospace",
                fontSize: "12px",
                resize: "vertical",
                boxSizing: "border-box",
                outline: "none",
            });

            ta.onfocus = () => {
                ta.style.borderColor = colors.primary;
            };

            ta.onblur = () => {
                ta.style.borderColor = colors.border;
            };

            return ta;
        },

        _createBtn(text, bg, textColor, colors) {
            const btn = document.createElement("button");
            btn.textContent = text;

            Utils.setStyle(btn, {
                padding: "8px 16px",
                backgroundColor: bg,
                color: textColor,
                border: bg === "transparent" ? `1px solid ${colors.border}` : "none",
                borderRadius: "18px",
                cursor: "pointer",
                fontWeight: "500",
            });

            if (bg === "transparent") {
                btn.onmouseover = () => (btn.style.backgroundColor = colors.buttonBg);
                btn.onmouseout = () => (btn.style.backgroundColor = "transparent");
            }

            return btn;
        },

        close(dialogId) {
            const dialog = document.getElementById(dialogId);
            const overlay = document.getElementById(dialogId + "-overlay");
            if (dialog) dialog.remove();
            if (overlay) overlay.remove();

            if (this._escHandlers && this._escHandlers[dialogId]) {
                document.removeEventListener("keydown", this._escHandlers[dialogId]);
                delete this._escHandlers[dialogId];
            }
        },
    };

    const FilterEngine = {
        isSpam: (commentText, username) => {
            if (CONFIG.REGEX_NON_LATIN.test(commentText)) return true;
            if (username && State.forbiddenUserRegex?.test(username)) return true;
            if (commentText && State.forbiddenContentRegex?.test(commentText)) return true;
            return false;
        },
        processAll: () => {
            const comments = document.querySelectorAll(CONFIG.SELECTORS.COMMENT_CONTAINER);
            let hiddenCount = 0;
            let totalCount = comments.length;

            comments.forEach((container) => {
                const textEl = container.querySelector(CONFIG.SELECTORS.COMMENT_TEXT);
                const userEl = container.querySelector(CONFIG.SELECTORS.AUTHOR_TEXT);
                if (!textEl) return;
                const text = textEl.innerText;
                const user = userEl ? userEl.innerText : "";

                if (FilterEngine.isSpam(text, user)) {
                    FilterEngine.hideComment(container);
                    hiddenCount++;
                } else {
                    FilterEngine.showComment(container);
                }
            });

            UIManager.updateCount(hiddenCount, totalCount);
        },
        hideComment: (container) => {
            if (!container.classList.contains(CONFIG.CLASSES.HIDDEN)) {
                container.style.display = "none";
                container.classList.add(CONFIG.CLASSES.HIDDEN);

                if (container.id === "comment") {
                    const thread = container.closest(CONFIG.SELECTORS.THREAD_RENDERER);
                    const replies = thread?.querySelector(CONFIG.SELECTORS.REPLIES_CONTAINER);
                    if (replies) replies.style.display = "none";
                }
            }
        },
        showComment: (container) => {
            if (container.classList.contains(CONFIG.CLASSES.HIDDEN)) {
                container.style.display = "";
                container.classList.remove(CONFIG.CLASSES.HIDDEN);

                if (container.id === "comment") {
                    const thread = container.closest(CONFIG.SELECTORS.THREAD_RENDERER);
                    const replies = thread?.querySelector(CONFIG.SELECTORS.REPLIES_CONTAINER);
                    if (replies) replies.style.display = "";
                }
            }
        },
    };

    const UIManager = {
        injectStyles: () => {
            GM_addStyle(`
                .${CONFIG.CLASSES.HIDDEN} { display: none; }
                body.${CONFIG.CLASSES.SHOW_HIDDEN} .${CONFIG.CLASSES.HIDDEN} {
                    display: block !important; opacity: 0.6; border: 1px dashed rgba(255, 0, 0, 0.5);
                    border-radius: 8px; margin-bottom: 8px !important;
                }
            `);
        },
        createButton: () => {
            if (document.getElementById("yt-filter-toggle")) return;
            const target = document.querySelector(CONFIG.SELECTORS.HEADER_TARGET);
            if (!target) return;
            const btn = document.createElement("span");
            btn.id = "yt-filter-toggle";

            Utils.setStyle(btn, {
                display: "inline-flex",
                alignItems: "center",
                cursor: "pointer",
                fontFamily: '"Roboto","Arial",sans-serif',
                fontSize: "14px",
                fontWeight: "500",
                color: "var(--yt-spec-text-secondary)",
                marginLeft: "32px",
                position: "relative",
                bottom: "3px",
            });

            const iconStyle = "width:24px;height:24px;fill:currentColor;margin-right:4px";

            const svgs = {
                on: `<svg class="icon-vis-on" style="${iconStyle}" viewBox="0 0 24 24"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5C21.27 7.61 17 4.5 12 4.5zm0 13c-3.04 0-5.5-2.46-5.5-5.5S8.96 6.5 12 6.5s5.5 2.46 5.5 5.5-2.46 5.5-5.5 5.5zm0-9c-1.93 0-3.5 1.57-3.5 3.5s1.57 3.5 3.5 3.5 3.5-1.57 3.5-3.5-1.57-3.5-3.5-3.5z"/></svg>`,
                off: `<svg class="icon-vis-off" style="${iconStyle};display:none" viewBox="0 0 24 24"><path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L21.73 23 23 21.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.93 1.57 3.5 3.5 3.5.22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-3.04 0-5.5-2.46-5.5-5.5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.93-1.57-3.5-3.5-3.5l-.16.02z"/></svg>`,
            };

            btn.innerHTML = `${svgs.on}${svgs.off}<span class="yt-filter-label">Hidden</span><span class="yt-filter-status" style="margin: 0 4px;">OFF</span><span class="yt-filter-info" style="display:none">(0/0)</span>`;
            btn.addEventListener("click", UIManager.toggleHidden);
            target.after(btn);
        },
        toggleHidden: () => {
            State.isShowingHidden = !State.isShowingHidden;
            document.body.classList.toggle(CONFIG.CLASSES.SHOW_HIDDEN, State.isShowingHidden);
            UIManager.updateCount();
        },
        updateCount: (passedHidden, passedTotal) => {
            const btn = document.getElementById("yt-filter-toggle");
            if (!btn) return;
            let hiddenCount = passedHidden ?? document.querySelectorAll(`.${CONFIG.CLASSES.HIDDEN}`).length;
            let totalCount = passedTotal ?? document.querySelectorAll(CONFIG.SELECTORS.COMMENT_CONTAINER).length;
            const statusEl = btn.querySelector(".yt-filter-status");
            const infoEl = btn.querySelector(".yt-filter-info");
            const iconOn = btn.querySelector(".icon-vis-on");
            const iconOff = btn.querySelector(".icon-vis-off");

            if (State.isShowingHidden) {
                statusEl.textContent = "OFF";
                infoEl.style.display = "none";
                iconOn.style.display = "none";
                iconOff.style.display = "block";
            } else {
                statusEl.textContent = "ON";
                infoEl.textContent = `(${hiddenCount}/${totalCount})`;
                infoEl.style.display = "inline";
                iconOn.style.display = "block";
                iconOff.style.display = "none";
            }
        },
    };

    const ObserverManager = {
        activeObserver: null,
        start: () => {
            if (ObserverManager.activeObserver) ObserverManager.activeObserver.disconnect();
            if (State.observerMode === "fast") ObserverManager.startFastMode();
            else ObserverManager.startEfficientMode();
        },
        startEfficientMode: () => {
            console.log("[YT Filter] EFFICIENT Mode.");
            const debouncedProcess = Utils.debounce(FilterEngine.processAll, 500);
            ObserverManager.activeObserver = new MutationObserver((mutations) => {
                let shouldProcess = false;

                for (const m of mutations) {
                    if (
                        m.target.nodeName === "YTD-COMMENT-VIEW-MODEL" ||
                        m.target.id === "contents" ||
                        m.target.id === "comments"
                    ) {
                        shouldProcess = true;
                        break;
                    }
                }

                if (shouldProcess || document.readyState === "loading") debouncedProcess();
            });

            ObserverManager.activeObserver.observe(document.body, { childList: true, subtree: true });
        },
        startFastMode: () => {
            console.log("[YT Filter] FAST Mode.");
            const throttledProcess = Utils.throttle(FilterEngine.processAll, CONFIG.THROTTLE_DELAY);
            ObserverManager.activeObserver = new MutationObserver(() => throttledProcess());
            ObserverManager.activeObserver.observe(document.body, { childList: true, subtree: true });
        },
    };

    const init = () => {
        State.loadSettings();
        UIManager.injectStyles();
        GM_registerMenuCommand("⚙ Configure Filter Settings", SettingsUI.openSettings.bind(SettingsUI));
        GM_registerMenuCommand("🔁 Switch Observer Mode", SettingsUI.openModeSwitcher.bind(SettingsUI));
        GM_registerMenuCommand("👁️‍🗨️ Toggle Button Check", UIManager.createButton);

        const btnInterval = setInterval(() => {
            if (document.querySelector(CONFIG.SELECTORS.HEADER_TARGET)) {
                UIManager.createButton();
                clearInterval(btnInterval);
            }
        }, 2000);

        ObserverManager.start();
        setTimeout(FilterEngine.processAll, 2000);
    };

    init();
})();