/*
 * Vencord, a Discord client mod
 * Copyright (c) 2024 Vendicated and contributors
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { SettingsSection } from "@components/settings/tabs/plugins/components/Common";
import { Devs } from "@utils/constants";
import definePlugin, { defineDefault, OptionType, StartAt } from "@utils/types";
import { Checkbox, Text } from "@webpack/common";

const Noop = () => { };
const NoopLogger = {
    logDangerously: Noop,
    log: Noop,
    verboseDangerously: Noop,
    verbose: Noop,
    info: Noop,
    warn: Noop,
    error: Noop,
    trace: Noop,
    time: Noop,
    fileOnly: Noop
};

const logAllow = new Set();

interface AllowLevels {
    error: boolean;
    warn: boolean;
    trace: boolean;
    log: boolean;
    info: boolean;
    debug: boolean;
}

interface AllowLevelSettingProps {
    settingKey: keyof AllowLevels;
}

function AllowLevelSetting({ settingKey }: AllowLevelSettingProps) {
    const { allowLevel } = settings.use(["allowLevel"]);
    const value = allowLevel[settingKey];

    return (
        <Checkbox
            value={value}
            onChange={(_, newValue) => settings.store.allowLevel[settingKey] = newValue}
            size={20}
        >
            <Text variant="text-sm/normal">{settingKey[0].toUpperCase() + settingKey.slice(1)}</Text>
        </Checkbox>
    );
}

const AllowLevelSettings = ErrorBoundary.wrap(() => {
    return (
        <SettingsSection name="Filter List" description="Always allow loggers of these types">
            <div style={{ display: "flex", flexDirection: "row" }}>
                {Object.keys(settings.store.allowLevel).map(key => (
                    <AllowLevelSetting key={key} settingKey={key as keyof AllowLevels} />
                ))}
            </div>
        </SettingsSection>
    );
});

const settings = definePluginSettings({
    disableLoggers: {
        type: OptionType.BOOLEAN,
        description: "Disables Discords loggers",
        default: false,
        restartNeeded: true
    },
    disableSpotifyLogger: {
        type: OptionType.BOOLEAN,
        description: "Disable the Spotify logger, which leaks account information and access token",
        default: true,
        restartNeeded: true
    },
    whitelistedLoggers: {
        type: OptionType.STRING,
        description: "Semicolon (;) separated list of loggers to allow even if others are hidden",
        default: "GatewaySocket; Routing/Utils",
        multiline: true,
        onChange(newVal: string) {
            logAllow.clear();
            newVal.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow));
        }
    },
    allowLevel: {
        type: OptionType.COMPONENT,
        component: AllowLevelSettings,
        default: defineDefault<AllowLevels>({
            error: true,
            warn: false,
            trace: false,
            log: false,
            info: false,
            debug: false
        })
    }
});

export default definePlugin({
    name: "ConsoleJanitor",
    description: "Disables annoying console messages/errors",
    authors: [Devs.Nuckyz, Devs.sadan],
    settings,

    startAt: StartAt.Init,
    start() {
        logAllow.clear();
        this.settings.store.whitelistedLoggers?.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow));
    },

    Noop,
    NoopLogger: () => NoopLogger,

    shouldLog(logger: string, level: keyof AllowLevels) {
        return logAllow.has(logger) || settings.store.allowLevel[level] === true;
    },

    patches: [
        {
            find: "https://github.com/highlightjs/highlight.js/issues/2277",
            replacement: {
                match: /\(console.log\(`Deprecated.+?`\),/,
                replace: "("
            }
        },
        {
            find: 'The "interpolate" function is deprecated in v10 (use "to" instead)',
            replacement: {
                match: /,console.warn\(\i\+'The "interpolate" function is deprecated in v10 \(use "to" instead\)'\)/,
                replace: ""
            }
        },
        {
            find: 'console.warn("Window state not initialized"',
            replacement: {
                match: /console\.warn\("Window state not initialized",\i\),/,
                replace: ""
            }
        },
        {
            find: "is not a valid locale.",
            replacement: [
                {
                    match: /\i\.error(?=\(`\$\{\i\} is not a valid locale.`)/,
                    replace: "$self.Noop"
                }
            ]
        },
        {
            find: '"AppCrashedFatalReport: getLastCrash not supported."',
            replacement: {
                match: /console\.log(?=\("AppCrashedFatalReport: getLastCrash not supported\."\))/,
                replace: "$self.Noop"
            }
        },
        {
            find: "RPCServer:WSS",
            replacement: {
                match: /\i\.error\(`Error: \$\{(\i)\.message\}/,
                replace: '!$1.message.includes("EADDRINUSE")&&$&'
            }
        },
        {
            find: "Tried getting Dispatch instance before instantiated",
            replacement: {
                match: /null==\i&&\i\.warn\("Tried getting Dispatch instance before instantiated"\),/,
                replace: ""
            }
        },
        {
            find: "Unable to determine render window for element",
            replacement: {
                match: /console\.warn\("Unable to determine render window for element",\i\),/,
                replace: ""
            }
        },
        {
            find: "failed to send analytics events",
            replacement: [
                {
                    match: /console\.error\(`\[analytics\] failed to send analytics events query: \$\{\i\}`\)/,
                    replace: ""
                }
            ]
        },
        {
            find: "Slow dispatch on",
            replacement: [
                {
                    match: /\i\.totalTime>\i&&\i\.verbose\([`"]Slow dispatch on.{0,55}\);/,
                    replace: ""
                },
            ]
        },
        // Patches Discord generic logger function
        {
            find: '"file-only"!==',
            predicate: () => settings.store.disableLoggers,
            replacement: {
                match: /(?<=&&)(?=console)/,
                replace: "$self.shouldLog(arguments[0],arguments[1])&&"
            }
        },
        {
            find: '("Spotify")',
            predicate: () => settings.store.disableSpotifyLogger,
            replacement: {
                match: /new \i\.\i\("Spotify"\)/,
                replace: "$self.NoopLogger()"
            }
        }
    ]
});
