Make YouTube Livestreams Theater Mode compatible with Enhancer for YouTube

Fixes the issues caused when both the "YouTube Livestreams Theater Mode" and "Enhancer for YouTube" extensions are installed.

// ==UserScript==
// @name         Make YouTube Livestreams Theater Mode compatible with Enhancer for YouTube
// @namespace    http://tampermonkey.net/
// @version      1.0.2
// @description  Fixes the issues caused when both the "YouTube Livestreams Theater Mode" and "Enhancer for YouTube" extensions are installed.
// @author       Jacky Xu
// @match        https://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant        none
// @license      MIT
// @noframes
// ==/UserScript==

(function() {
    'use strict';

    const debug = true;
    const logPrefix = '[YT Theater Mode Patch]';
    const consoleLog = (...args) => console.log(`${logPrefix} ${args[0]}`, ...args.slice(1));
    const consoleDebug = (...args) => { if (debug) console.debug(`${logPrefix} ${args[0]}`, ...args.slice(1)); };

    consoleLog('Loaded Tampermonkey script');

    // Since YouTube is an SPA and parts of the page are loaded asynchronously, we use a DOM listener to watch until the
    // parent element of the theater mode toggle button is loaded in the DOM, then we can exit the listener.
    // Due to YouTube's implementation, the parent element will not removed from the DOM when we navigate away from a
    // video-containing page, only its display is set to none.
    const pageListener = (e) => {
        const containerElem = document.querySelector('.ytp-right-controls');
        if (!!containerElem) {
            document.removeEventListener('DOMSubtreeModified', pageListener);
            consoleDebug('Removed page event listener');
            addMutationObserver(containerElem);
            return;
        }
    }

    // Add an oberver to the parent element that contains the theater mode toggle button to watch for the addition/removal
    // of the theater mode toggle button. When that happens, we add/remove the click listener to the button.
    const addMutationObserver = (containerElem) => {

        let chatOpenState = false;

        const clickListener = () => {
            chatOpenState = !chatOpenState;
            consoleDebug(`Theater Mode button clicked. Chat opened state: ${chatOpenState}`);

            // This hides the annoying top pane that shows when the mouse hovers over the video while the chat drawer is opened.
            const topHoverElem = document.querySelector('#primary.ytd-watch-flexy');
            if (!!topHoverElem) {
                topHoverElem.style.display = chatOpenState ? 'none' : 'initial';
                consoleDebug('Top hover element: ', topHoverElem);
            }
        }

        const observer = new MutationObserver((mutations) => {
            consoleDebug('Mutations: ', mutations);

            const lastChange = mutations
            .flatMap(m => [
                ...Array.from(m.addedNodes).map(e => ({ type: 'added', element: e })),
                ...Array.from(m.removedNodes).map(e => ({ type: 'removed', element: e }))
            ])
            .filter(e => e.element.id === 'livestreamsTheaterMode')
            .at(-1);

            if (!lastChange || (lastChange.type !== 'added' && lastChange.type !== 'removed')) {
                consoleDebug('No mutation related to adding/removing the Theater Mode button');
                return;
            }

            const btnElem = lastChange.element;

            if (lastChange.type === 'added') { // added
                consoleDebug('Theater Mode button added', btnElem);
                // By default, the chat drawer in theater mode is closed.
                chatOpenState = false;
                btnElem.addEventListener('click', clickListener);

            } else { // removed
                consoleDebug('Theater Mode button removed', btnElem);
                btnElem.removeEventListener('click', clickListener);
                chatOpenState = false;
            }
        });

        observer.observe(containerElem, { childList: true });
        consoleDebug('Added Mutation Observer');
    }

    document.addEventListener('DOMSubtreeModified', pageListener);
    consoleDebug('Added page event listener');
})();