/*
 * Vencord, a modification for Discord's desktop app
 * Copyright (c) 2022 Vendicated and contributors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { migratePluginSettings } from "@api/settings";
import { CheckedTextInput } from "@components/CheckedTextInput";
import { Devs } from "@utils/constants";
import Logger from "@utils/Logger";
import { Margins } from "@utils/margins";
import { ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
import definePlugin from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { Forms, GuildStore, Menu, PermissionStore, React, Toasts, Tooltip, UserStore } from "@webpack/common";

const MANAGE_EMOJIS_AND_STICKERS = 1n << 30n;

const GuildEmojiStore = findByPropsLazy("getGuilds", "getGuildEmoji");
const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS(");

function getGuildCandidates(isAnimated: boolean) {
    const meId = UserStore.getCurrentUser().id;

    return Object.values(GuildStore.getGuilds()).filter(g => {
        const canCreate = g.ownerId === meId ||
            BigInt(PermissionStore.getGuildPermissions({ id: g.id }) & MANAGE_EMOJIS_AND_STICKERS) === MANAGE_EMOJIS_AND_STICKERS;
        if (!canCreate) return false;

        const emojiSlots = g.getMaxEmojiSlots();
        const { emojis } = GuildEmojiStore.getGuilds()[g.id];

        let count = 0;
        for (const emoji of emojis)
            if (emoji.animated === isAnimated) count++;
        return count < emojiSlots;
    }).sort((a, b) => a.name.localeCompare(b.name));
}

async function doClone(guildId: string, id: string, name: string, isAnimated: boolean) {
    const data = await fetch(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${id}.${isAnimated ? "gif" : "png"}`)
        .then(r => r.blob());
    const reader = new FileReader();

    reader.onload = () => {
        uploadEmoji({
            guildId,
            name: name.split("~")[0],
            image: reader.result
        }).then(() => {
            Toasts.show({
                message: `Successfully cloned ${name}!`,
                type: Toasts.Type.SUCCESS,
                id: Toasts.genId()
            });
        }).catch((e: any) => {
            new Logger("EmoteCloner").error("Failed to upload emoji", e);
            Toasts.show({
                message: "Oopsie something went wrong :( Check console!!!",
                type: Toasts.Type.FAILURE,
                id: Toasts.genId()
            });
        });
    };

    reader.readAsDataURL(data);
}

const getFontSize = (s: string) => {
    // [18, 18, 16, 16, 14, 12, 10]
    const sizes = [20, 20, 18, 18, 16, 14, 12];
    return sizes[s.length] ?? 4;
};

const nameValidator = /^\w+$/i;

function CloneModal({ id, name: emojiName, isAnimated }: { id: string; name: string; isAnimated: boolean; }) {
    const [isCloning, setIsCloning] = React.useState(false);
    const [name, setName] = React.useState(emojiName);

    const [x, invalidateMemo] = React.useReducer(x => x + 1, 0);

    const guilds = React.useMemo(() => getGuildCandidates(isAnimated), [isAnimated, x]);

    return (
        <>
            <Forms.FormTitle className={Margins.top20}>Custom Name</Forms.FormTitle>
            <CheckedTextInput
                value={name}
                onChange={setName}
                validate={v =>
                    (v.length > 1 && v.length < 32 && nameValidator.test(v))
                    || "Name must be between 2 and 32 characters and only contain alphanumeric characters"
                }
            />
            <div style={{
                display: "flex",
                flexWrap: "wrap",
                gap: "1em",
                padding: "1em 0.5em",
                justifyContent: "center",
                alignItems: "center"
            }}>
                {guilds.map(g => (
                    <Tooltip text={g.name}>
                        {({ onMouseLeave, onMouseEnter }) => (
                            <div
                                onMouseLeave={onMouseLeave}
                                onMouseEnter={onMouseEnter}
                                role="button"
                                aria-label={"Clone to " + g.name}
                                aria-disabled={isCloning}
                                style={{
                                    borderRadius: "50%",
                                    backgroundColor: "var(--background-secondary)",
                                    display: "inline-flex",
                                    justifyContent: "center",
                                    alignItems: "center",
                                    width: "4em",
                                    height: "4em",
                                    cursor: isCloning ? "not-allowed" : "pointer",
                                    filter: isCloning ? "brightness(50%)" : "none"
                                }}
                                onClick={isCloning ? void 0 : async () => {
                                    setIsCloning(true);
                                    doClone(g.id, id, name, isAnimated).finally(() => {
                                        invalidateMemo();
                                        setIsCloning(false);
                                    });
                                }}
                            >
                                {g.icon ? (
                                    <img
                                        aria-hidden
                                        style={{
                                            borderRadius: "50%",
                                            width: "100%",
                                            height: "100%",
                                        }}
                                        src={g.getIconURL(512, true)}
                                        alt={g.name}
                                    />
                                ) : (
                                    <Forms.FormText
                                        style={{
                                            fontSize: getFontSize(g.acronym),
                                            width: "100%",
                                            overflow: "hidden",
                                            whiteSpace: "nowrap",
                                            textAlign: "center",
                                            cursor: isCloning ? "not-allowed" : "pointer",
                                        }}
                                    >
                                        {g.acronym}
                                    </Forms.FormText>
                                )}
                            </div>
                        )}
                    </Tooltip>
                ))}
            </div>
        </>
    );
}

const messageContextMenuPatch: NavContextMenuPatchCallback = (children, args) => {
    if (!args?.[0]) return;
    const { favoriteableId, emoteClonerDataAlt, itemHref, itemSrc, favoriteableType } = args[0];

    if (!emoteClonerDataAlt || favoriteableType !== "emoji") return;

    const name = emoteClonerDataAlt.match(/:(.*)(?:~\d+)?:/)?.[1];
    if (!name || !favoriteableId) return;

    const src = itemHref ?? itemSrc;
    const isAnimated = new URL(src).pathname.endsWith(".gif");

    const group = findGroupChildrenByChildId("save-image", children);
    if (group && !group.some(child => child?.props?.id === "emote-cloner")) {
        group.push((
            <Menu.MenuItem
                id="emote-cloner"
                key="emote-cloner"
                label="Clone"
                action={() =>
                    openModal(modalProps => (
                        <ModalRoot {...modalProps}>
                            <ModalHeader>
                                <img
                                    role="presentation"
                                    aria-hidden
                                    src={`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${favoriteableId}.${isAnimated ? "gif" : "png"}`}
                                    alt=""
                                    height={24}
                                    width={24}
                                    style={{ marginRight: "0.5em" }}
                                />
                                <Forms.FormText>Clone {name}</Forms.FormText>
                            </ModalHeader>
                            <ModalContent>
                                <CloneModal id={favoriteableId} name={name} isAnimated={isAnimated} />
                            </ModalContent>
                        </ModalRoot>
                    ))
                }
            >
            </Menu.MenuItem>
        ));
    }
};

migratePluginSettings("EmoteCloner", "EmoteYoink");
export default definePlugin({
    name: "EmoteCloner",
    description: "Adds a Clone context menu item to emotes to clone them your own server",
    authors: [Devs.Ven, Devs.Nuckyz],
    dependencies: ["MenuItemDeobfuscatorAPI", "ContextMenuAPI"],

    patches: [
        {
            find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL",
            replacement: {
                match: /(?<=favoriteableType:\i,)(?<=(\i)\.getAttribute\("data-type"\).+?)/,
                replace: (_, target) => `emoteClonerDataAlt:${target}.alt,`
            }
        }
    ],

    start() {
        addContextMenuPatch("message", messageContextMenuPatch);
    },

    stop() {
        removeContextMenuPatch("message", messageContextMenuPatch);
    }
});
