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

import { DataStore } from "@api/index";
import { definePluginSettings } from "@api/Settings";
import { Flex } from "@components/Flex";
import { DeleteIcon } from "@components/Icons";
import { makeRange } from "@components/PluginSettings/components/SettingSliderComponent";
import { sleep } from "@utils/misc";
import { useForceUpdater } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import {
    Button,
    Forms,
    React,
    RelationshipStore,
    SelectedChannelStore,
    TextInput,
    useState,
} from "@webpack/common";
import { Message } from "discord-types/general";

interface IMessageCreate {
    type: "MESSAGE_CREATE";
    optimistic: boolean;
    isPushNotification: boolean;
    channelId: string;
    message: Message;
}

interface IRPCNotificationCreate {
    type: "RPC_NOTIFICATION_CREATE";
    channelId: string;
    message: Message;
}

const CLICKER_RULES_KEY = "[Clicker]-rules";

interface ClickerRule {
    trigger: string;
    sound: string;
}
let clickerRules = [
    {
        sound: "",
        trigger: "",
    },
] as ClickerRule[];

interface InputProps {
    placeholder: string;
    initalValue: string;
    onChange(newValue: string): void;
}

function Input({ placeholder, initalValue, onChange }: InputProps) {
    const [value, setValue] = useState(initalValue);
    return (
        <TextInput
            placeholder={placeholder}
            value={value}
            onChange={setValue}
            spellCheck={false}
            onBlur={() => value !== initalValue && onChange(value)}
        />
    );
}

function RuleInputter({ update }: { update: () => void; }) {
    async function deleteRule(index: number) {
        if (index === clickerRules.length - 1) return;
        clickerRules.splice(index, 1);

        await DataStore.set(CLICKER_RULES_KEY, clickerRules);
        update();
    }

    async function modifyRule(
        newValue: string,
        index: number,
        key: keyof ClickerRule
    ) {
        if (index === clickerRules.length - 1) {
            clickerRules.push({
                sound: "",
                trigger: "",
            });
        }
        clickerRules[index][key] = newValue;

        if (clickerRules[index].trigger === "" && clickerRules[index].sound === "" && index !== clickerRules.length - 1) {
            clickerRules.splice(index, 1);
        }

        await DataStore.set(CLICKER_RULES_KEY, clickerRules);
        update();
    }
    return (
        <>
            <Forms.FormDivider />
            <Forms.FormTitle tag="h4">Rules</Forms.FormTitle>
            <Flex flexDirection="column" style={{ gap: "0.5em" }}>
                {clickerRules.map((rule, ind) => {
                    return (
                        <React.Fragment key={`${rule.trigger}-${ind}`}>
                            <Flex flexDirection="row" style={{ gap: "0.5em" }}>
                                <div
                                    style={{
                                        flexGrow: 1,
                                        gap: "0.5em",
                                        display: "grid",
                                        gridTemplateColumns: "1fr 1fr",
                                    }}
                                >
                                    <Input
                                        placeholder="Trigger"
                                        initalValue={rule.trigger}
                                        onChange={e => modifyRule(e, ind, "trigger")}
                                    />
                                    <TextInput
                                        placeholder="Sound"
                                        value={rule.sound}
                                        onChange={e => modifyRule(e, ind, "sound")}
                                    />
                                </div>
                                <Button
                                    size={Button.Sizes.ICON}
                                    onClick={() => deleteRule(ind)}
                                    style={{
                                        background: "none",
                                        color: "var(--status-danger)",
                                        ...(ind === clickerRules.length - 1
                                            ? {
                                                visibility: "hidden",
                                                pointerEvents: "none",
                                            }
                                            : {}),
                                    }}
                                >
                                    <DeleteIcon />
                                </Button>
                            </Flex>
                        </React.Fragment>
                    );
                })}
            </Flex>
        </>
    );
}

const settings = definePluginSettings({
    rules: {
        type: OptionType.COMPONENT,
        description: "List of triggers to react to",
        component: () => {
            const update = useForceUpdater();
            return <RuleInputter update={update} />;
        },
    },
    delay: {
        description: "Delay between triggers",
        type: OptionType.SLIDER,
        markers: makeRange(0, 500, 50),
        default: 250,
        stickToMarkers: true,
    },
    volume: {
        description: "Volume",
        type: OptionType.SLIDER,
        markers: makeRange(0, 1, 0.1),
        default: 0.5,
        stickToMarkers: false,
    },
    ignoreBlocked: {
        description: "Ignore blocked users",
        type: OptionType.BOOLEAN,
        default: true,
    },
});

export default definePlugin({
    name: "Sound Trigger",
    description: "Configure trigger phrases (powered by regex) to play sounds.",
    authors: [
        {
            id: 172557961133162496n,
            name: "Rose (rosasaur)",
        },
    ],
    settings,
    async start() {
        clickerRules = (await DataStore.get(CLICKER_RULES_KEY)) ?? clickerRules;
    },
    flux: {
        async RPC_NOTIFICATION_CREATE({ type, message }: IRPCNotificationCreate) {
            if (type !== "RPC_NOTIFICATION_CREATE") return;
            if (message.state === "SENDING") return;
            if (
                settings.store.ignoreBlocked &&
                RelationshipStore.isBlocked(message.author?.id)
            )
                return;
            if (!message.content) return;

            await handleMessage(message);
        },
        async MESSAGE_CREATE({
            optimistic,
            type,
            channelId,
            message,
        }: IMessageCreate) {
            if (optimistic || type !== "MESSAGE_CREATE") return;
            if (message.state === "SENDING") return;
            if (
                settings.store.ignoreBlocked &&
                RelationshipStore.isBlocked(message.author?.id)
            )
                return;
            if (!message.content) return;
            if (channelId !== SelectedChannelStore.getChannelId()) return;

            await handleMessage(message);
        },
    },
});

async function handleMessage(message: Message) {
    let queue = [] as ClickerRule[];

    for (const rule of clickerRules) {
        if (!rule.trigger) continue;
        const regex = new RegExp(rule.trigger, "g");
        const count = (message.content.match(regex) ?? []).length;
        queue = [...queue, ...Array(count).fill(rule)];
    }

    for (const item of queue) {
        await playSound(item.sound);
        await sleep(settings.store.delay);
    }
}

function playSound(sound: string) {
    return new Promise<void>(res => {
        const audio = new Audio();
        audio.src = sound;
        audio.volume = settings.store.volume;
        audio.addEventListener("ended", () => res());
        audio.play();
    });
}
