/**
 * @name WiderUserArea
 * @version 0.2.5
 * @description A BetterDiscord plugin that expands your user area into the server list, compatible with most themes
 * @author TheLazySquid
 * @authorId 619261917352951815
 * @website https://github.com/TheLazySquid/DiscordWiderUserArea
 * @source https://github.com/TheLazySquid/DiscordWiderUserArea/blob/main/build/WiderUserArea.plugin.js
 */
module.exports = class {
    constructor() {
'use strict';

var styles = ":root {\r\n    --user-area-bottom: 0;\r\n    --user-area-left: 0;\r\n}\r\n\r\nnav[class*=\"guilds_\"] {\r\n    height: var(--sidebar-height);\r\n}\r\n\r\n[class*=\"sidebar_\"] {\r\n    height: calc(var(--sidebar-height) - var(--notices-height));\r\n}\r\n\r\n/* user area */\r\nsection[class*=\"panels_\"] {\r\n    position: fixed;\r\n    overflow: hidden;\r\n    bottom: var(--user-area-bottom);\r\n    left: var(--user-area-left);\r\n    width: var(--user-area-width);\r\n}\r\n\r\nsection[class*=\"panels_\"] [class*=\"avatarWrapper_\"] {\r\n    flex-grow: 1;\r\n}\r\n\r\n/* prevent the user info popout from clipping */\r\n[class*=\"accountProfilePopoutWrapper\"] {\r\n    left: 0;\r\n}";

const userAreaSelector = 'section[class*="panels_"]';
const serverListSelector = 'nav[class*="guilds_"]';
const containerSelector = `div:has(> ${serverListSelector})`;
const layerSelector = `[class*="baseLayer"]`;
const channelsSelector = '[class*="sidebar_"] > nav';
const scaleRegex = /scale\((.*)\)/;
const UAButtonsSelector = 'div[class*="avatarWrapper_"] + div';
const firstServerListSelector = `${serverListSelector}:first-child`;
const secondServerListSelector = `${serverListSelector}:nth-child(2)`;

function scaleDOMRect(rect, scale, scaleCenterX, scaleCenterY) {
    // Calculate the distance of the rect from the scale center
    const distX = rect.x - scaleCenterX;
    const distY = rect.y - scaleCenterY;
    // Scale the distances
    const newDistX = distX * scale;
    const newDistY = distY * scale;
    // Calculate the new position of the rect
    const newX = scaleCenterX + newDistX;
    const newY = scaleCenterY + newDistY;
    // Scale the dimensions of the rect
    const newWidth = rect.width * scale;
    const newHeight = rect.height * scale;
    // Create a new DOMRect with the new dimensions and position
    const newRect = new DOMRect(newX, newY, newWidth, newHeight);
    return newRect;
}

const createCallbackHandler = (callbackName) => {
    const fullName = callbackName + "Callbacks";
    this[fullName] = [];
    this[callbackName] = () => {
        for (let i = 0; i < this[fullName].length; i++) {
            this[fullName][i].callback();
        }
    };
    return (callback, once, id) => {
        let object = { callback };
        const delCallback = () => {
            this[fullName].splice(this[fullName].indexOf(object), 1);
        };
        // if once is true delete it after use
        if (once === true) {
            object.callback = () => {
                callback();
                delCallback();
            };
        }
        if (id) {
            object.id = id;
            for (let i = 0; i < this[fullName].length; i++) {
                if (this[fullName][i].id === id) {
                    this[fullName][i] = object;
                    return delCallback;
                }
            }
        }
        this[fullName].push(object);
        return delCallback;
    };
};
/**
 * Takes a callback and fires it when the plugin is started
 * @param callback - The callback to be fired
 * @param once - If true, the callback will be deleted after use
 * @param id - The id of the callback - if it already exists, it will be replaced
 * @returns A function to delete the callback
 */
const onStart = createCallbackHandler("start");
/**
 * Takes a callback and fires it when the plugin is stopped
 * @param callback - The callback to be fired
 * @param once - If true, the callback will be deleted after use
 * @param id - The id of the callback - if it already exists, it will be replaced
 * @returns A function to delete the callback
 */
const onStop = createCallbackHandler("stop");
/**
 * Takes a callback and fires it when the user navigates
 * @param callback - The callback to be fired
 * @param once - If true, the callback will be deleted after use
 * @param id - The id of the callback - if it already exists, it will be replaced
 * @returns A function to delete the callback
 */
const onSwitch = createCallbackHandler("onSwitch");

/**
 * Watches for an element with a given selector to be added to the DOM
 * @param selector The CSS selector to watch
 * @param callback The callback to run whenever the matching element is added to the DOM
 * @returns A function to stop watching
 */
function watchElement(selector, callback) {
    let observer = new MutationObserver((mutations) => {
        for (let mutation of mutations) {
            if (mutation.addedNodes.length) {
                for (let node of mutation.addedNodes) {
                    if (!(node instanceof HTMLElement))
                        continue;
                    if (node.matches && node.matches(selector)) {
                        callback(node);
                    }
                    if (node.querySelectorAll) {
                        for (let element of node.querySelectorAll(selector)) {
                            callback(element);
                        }
                    }
                }
            }
        }
    });
    let startDispose = onStart(() => {
        observer.observe(document.body, { childList: true, subtree: true });
        for (let element of document.querySelectorAll(selector)) {
            callback(element);
        }
    });
    let stopDispose = onStop(() => {
        observer.disconnect();
    });
    return () => {
        observer.disconnect();
        startDispose();
        stopDispose();
    };
}

// @ts-ignore
let baseChannelHeight = 0;
let baseChannelWidth = 0;
let varsSet = new Set();
const recalcDebounce = BdApi.Utils.debounce(() => {
    const userArea = document.querySelector(userAreaSelector);
    if (userArea)
        userAreaFound(userArea);
}, 150);
watchElement(userAreaSelector, userAreaFound);
let userAreaObserver = new ResizeObserver(entries => {
    for (let entry of entries) {
        updateVar('--sidebar-height', `${baseChannelHeight - entry.contentRect.height}px`);
    }
});
let channelsRect;
let secondChannelsRect;
let channelsObserver = new ResizeObserver(entries => {
    for (let entry of entries) {
        channelsRect = entry.contentRect;
        // hide everything but the profile picture if the channel list is hidden
        let btns = document.querySelector(UAButtonsSelector);
        if (entry.contentRect.width === 0) {
            if (btns)
                btns.style.display = 'none';
        }
        else {
            if (btns)
                btns.style.display = '';
        }
        let newWidth = entry.contentRect.right - baseChannelWidth;
        if (secondChannelsRect)
            newWidth += secondChannelsRect.width;
        updateVar('--user-area-width', `${newWidth}px`);
    }
});
watchElement(channelsSelector, (element) => {
    channelsObserver.disconnect();
    channelsObserver.observe(element);
});
let secondServerListObserver = new ResizeObserver((entries) => {
    if (!channelsRect)
        return;
    for (let entry of entries) {
        secondChannelsRect = entry.contentRect;
        let width = entry.contentRect.width;
        updateVar('--user-area-width', `${channelsRect.right - baseChannelWidth + width}px`);
    }
});
watchElement(secondServerListSelector, (element) => {
    secondServerListObserver.disconnect();
    secondServerListObserver.observe(element);
});
let themesObserver = new MutationObserver(async () => {
    // wait a bit for it to apply
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log('theme changed');
    // recalculate the user area
    const userArea = document.querySelector(userAreaSelector);
    if (userArea)
        userAreaFound(userArea);
});
let noticesHeight = 0;
let noticesObserver = new ResizeObserver((entries) => {
    for (let entry of entries) {
        noticesHeight = entry.contentRect.height;
        updateVar('--notices-height', `${entry.contentRect.height}px`);
    }
});
watchElement("#bd-notices", (el) => {
    noticesObserver.disconnect();
    noticesObserver.observe(el);
});
function updateVar(name, value) {
    BdApi.DOM.removeStyle(`wua-${name}`);
    BdApi.DOM.addStyle(`wua-${name}`, `:root { ${name}: ${value} !important; }`);
    varsSet.add(name);
}
onSwitch(() => {
    // hacky fix for betteranimations sometimes giving a parent the "perspective" style breaking the fixed positioning
    setTimeout(() => {
        const userArea = document.querySelector(userAreaSelector);
        let parent = userArea?.parentElement;
        while (parent) {
            if (parent.style.perspective) {
                parent.style.perspective = '';
                break;
            }
            parent = parent.parentElement;
        }
        userAreaFound(userArea);
    }, 5100);
});
function userAreaFound(element) {
    // remove the old style if it exists
    BdApi.DOM.removeStyle('wua-styles');
    const layer = document.querySelector(layerSelector);
    const container = document.querySelector(containerSelector);
    const serverList = document.querySelector(firstServerListSelector);
    const channels = document.querySelector(channelsSelector);
    const secondServerList = document.querySelector(secondServerListSelector);
    const layerScale = 1 / parseFloat(scaleRegex.exec(layer.style.transform)?.[1] ?? '1');
    const layerRect = layer.getBoundingClientRect();
    const centerX = layerRect.left + layerRect.width / 2;
    const centerY = layerRect.top + layerRect.height / 2;
    // this scaling is neccesary for compatibility with betterAnimations,
    // which sometimes scales the layer when in a settings menu, which is where theme switches would be.
    const rect = scaleDOMRect(element.getBoundingClientRect(), layerScale, centerX, centerY);
    const containerRect = scaleDOMRect(container.getBoundingClientRect(), layerScale, centerX, centerY);
    const serverListRect = scaleDOMRect(serverList.getBoundingClientRect(), layerScale, centerX, centerY);
    const channelsRect = scaleDOMRect(channels.getBoundingClientRect(), layerScale, centerX, centerY);
    // figure out how far from the bottom of the screen the user area is
    const bottom = containerRect.bottom - rect.bottom;
    baseChannelHeight = channelsRect.height + rect.height;
    baseChannelWidth = serverListRect.left - channelsRect.left;
    if (secondServerList) {
        const secondServerListRect = scaleDOMRect(secondServerList.getBoundingClientRect(), layerScale, centerX, centerY);
        baseChannelWidth += secondServerListRect.width;
    }
    // add the new style
    BdApi.DOM.addStyle('wua-styles', styles);
    updateVar('--sidebar-height', `${channelsRect.height + noticesHeight}px`);
    updateVar('--user-area-width', `${channelsRect.right - serverListRect.left}px`);
    updateVar('--user-area-left', `${serverListRect.left}px`);
    updateVar('--user-area-bottom', `${bottom}px`);
    userAreaObserver.observe(element);
}
onStart(() => {
    window.addEventListener('resize', recalcDebounce);
    updateVar('--notices-height', '0px');
    themesObserver.observe(document.querySelector("bd-themes"), { childList: true, subtree: true });
});
onStop(() => {
    userAreaObserver.disconnect();
    channelsObserver.disconnect();
    themesObserver.disconnect();
    secondServerListObserver.disconnect();
    noticesObserver.disconnect();
    BdApi.DOM.removeStyle('wua-styles');
    for (let varName of varsSet) {
        BdApi.DOM.removeStyle(`wua-${varName}`);
    }
    window.removeEventListener('resize', recalcDebounce);
});
    }
}
