import axios from "axios";
import { useState } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Label } from "@/components/ui/label";
import { Maximize2, Minimize2, Trash2Icon } from "lucide-react";
import { ActionType } from "@prisma/client";
import { boolean, z } from "zod";


const actionTypes = [
    {
        "name": "Overwrite local file content",
        "value": ActionType.WRITE_TO_FILE,
        "inputs": [
            {
                "type": "text",
                "label": "File path",
                "key": "file_path",
                "placeholder": "Enter local file path, relative or full.",
                "required": true
            },
            {
                "type": "text",
                "label": "File content",
                "key": "file_content",
                "placeholder": "Enter the text to write to the file.",
                "required": true
            }
        ]
    },
    {
        "name": "Append to local file",
        "value": ActionType.APPEND_TO_FILE,
        "inputs": [
            {
                "type": "text",
                "label": "File path",
                "key": "file_path",
                "placeholder": "Enter local file path, relative or full.",
                "required": true
            },
            {
                "type": "text",
                "label": "File content",
                "key": "file_content",
                "placeholder": "Enter the text to append to the file.",
                "required": true
            }
        ]
    },
    {
        "name": "Cause a transformation on OBS scene item",
        "value": ActionType.OBS_TRANSFORM,
        "inputs": []
    },
    {
        "name": "Play an audio file locally",
        "value": ActionType.AUDIO_FILE,
        "inputs": [
            {
                "type": "text",
                "label": "File path",
                "key": "file_path",
                "placeholder": "Enter local file path, relative or full.",
                "required": true
            }
        ]
    },
    {
        "name": "User gets a random TTS voice that is enabled",
        "value": ActionType.RANDOM_TTS_VOICE,
        "inputs": []
    },
    {
        "name": "User gets a specific TTS voice",
        "value": ActionType.SPECIFIC_TTS_VOICE,
        "inputs": [
            {
                "type": "text",
                "label": "TTS Voice",
                "key": "tts_voice",
                "placeholder": "Name of an enabled TTS voice",
                "required": true
            }
        ]
    },
    {
        "name": "Toggle OBS scene item visibility",
        "value": ActionType.TOGGLE_OBS_VISIBILITY,
        "inputs": [
            {
                "type": "text",
                "label": "Scene Name",
                "key": "scene_name",
                "placeholder": "Name of the OBS scene",
                "required": true
            },
            {
                "type": "text",
                "label": "Scene Item Name",
                "key": "scene_item_name",
                "placeholder": "Name of the OBS scene item / source",
                "required": true
            }
        ]
    },
    {
        "name": "Set OBS scene item visibility",
        "value": ActionType.SPECIFIC_OBS_VISIBILITY,
        "inputs": [
            {
                "type": "text",
                "label": "Scene Name",
                "key": "scene_name",
                "placeholder": "Name of the OBS scene",
                "required": true
            },
            {
                "type": "text",
                "label": "Scene Item Name",
                "key": "scene_item_name",
                "placeholder": "Name of the OBS scene item / source",
                "required": true
            },
            {
                "type": "text-values",
                "label": "Visible",
                "key": "obs_visible",
                "placeholder": "true for visible; false otherwise",
                "values": ["true", "false"],
                "required": true
            }
        ]
    },
    {
        "name": "Set OBS scene item's index",
        "value": ActionType.SPECIFIC_OBS_INDEX,
        "inputs": [
            {
                "type": "text",
                "label": "Scene Name",
                "key": "scene_name",
                "placeholder": "Name of the OBS scene",
                "required": true
            },
            {
                "type": "text",
                "label": "Scene Item Name",
                "key": "scene_item_name",
                "placeholder": "Name of the OBS scene item / source",
                "required": true
            },
            {
                "type": "number",
                "label": "Index",
                "key": "obs_index",
                "placeholder": "index, starting from 0.",
                "required": true
            }
        ]
    },
    {
        "name": "Sleep - do nothing",
        "value": ActionType.SLEEP,
        "inputs": [
            {
                "type": "number",
                "label": "Sleep",
                "key": "sleep",
                "placeholder": "Time in milliseconds to do nothing",
                "required": true
            }
        ]
    },
    {
        "name": "Nightbot - Play",
        "value": ActionType.NIGHTBOT_PLAY,
        "inputs": [
            {
                "type": "oauth.nightbot.play",
                "label": "nightbot.play",
                "key": "nightbot_play",
                "placeholder": "",
                "required": true
            }
        ]
    },
    {
        "name": "Nightbot - Pause",
        "value": ActionType.NIGHTBOT_PAUSE,
        "inputs": [
            {
                "type": "oauth.nightbot.pause",
                "label": "nightbot.pause",
                "key": "nightbot_pause",
                "placeholder": "",
                "required": true
            }
        ]
    },
    {
        "name": "Nightbot - Skip",
        "value": ActionType.NIGHTBOT_SKIP,
        "inputs": [
            {
                "type": "oauth.nightbot.skip",
                "label": "nightbot.skip",
                "key": "nightbot_skip",
                "placeholder": "",
                "required": true
            }
        ]
    },
    {
        "name": "Nightbot - Clear Playlist",
        "value": ActionType.NIGHTBOT_CLEAR_PLAYLIST,
        "inputs": [
            {
                "type": "oauth.nightbot.clear_playlist",
                "label": "nightbot.clear_playlist",
                "key": "nightbot_clear_playlist",
                "placeholder": "",
                "required": true
            }
        ]
    },
    {
        "name": "Nightbot - Clear Queue",
        "value": ActionType.NIGHTBOT_CLEAR_QUEUE,
        "inputs": [
            {
                "type": "oauth.nightbot.clear_queue",
                "label": "nightbot.clear_queue",
                "key": "nightbot_clear_queue",
                "placeholder": "",
                "required": true
            }
        ]
    },
    {
        "name": "Veadotube - Set State",
        "value": ActionType.VEADOTUBE_SET_STATE,
        "inputs": [
            {
                "type": "text",
                "label": "State",
                "key": "state",
                "placeholder": "state #1",
                "required": true
            }
        ]
    },
]

const nameSchema = z.string({
    required_error: "Name is required.",
    invalid_type_error: "Name must be a string"
}).regex(/^[\w\-\s]{1,32}$/, "Name must contain only letters, numbers, spaces, dashes, and underscores.")

interface RedeemableAction {
    name: string
    type: string | undefined
    data: { [key: string]: string }
    edit?: boolean
    showEdit?: boolean
    isNew: boolean
    obsTransformations: { label: string, placeholder: string, description: string }[]
    connections: { name: string, type: string }[]
    adder: (name: string, type: ActionType, data: { [key: string]: string }) => void
    remover: (action: { name: string, type: string, data: any }) => void
}


const RedemptionAction = ({
    name,
    type,
    data,
    edit,
    showEdit = true,
    isNew = false,
    obsTransformations = [],
    connections = [],
    adder,
    remover
}: RedeemableAction) => {
    const [open, setOpen] = useState<{ [key: string]: boolean }>({ 'actions': false, 'oauth': false, 'oauth.nightbot': false, 'oauth.twitch': false })
    const [actionName, setActionName] = useState(name)
    const [actionType, setActionType] = useState<{ name: string, value: ActionType, inputs: any[] } | undefined>(actionTypes.find(a => a.value == type?.toUpperCase()))
    const [actionData, setActionData] = useState<{ [key: string]: string }>(data)
    const [isEditable, setIsEditable] = useState(edit)
    const [isMinimized, setIsMinimized] = useState(!isNew)
    const [oldData, setOldData] = useState<{ n: string, t: ActionType | undefined, d: { [k: string]: string } } | undefined>(undefined)
    const [error, setError] = useState<string | undefined>(undefined)

    function Save(isNew: boolean) {
        setError(undefined)
        if (!actionName) {
            setError("Name is required.")
            return
        }
        const nameValidation = nameSchema.safeParse(actionName)
        if (!nameValidation.success) {
            setError(JSON.parse(nameValidation.error['message'])[0].message)
            return
        }
        if (!actionType) {
            setError("Action type is required.")
            return
        }
        if (!actionTypes.some(t => t.value == actionType.value)) {
            setError("Invalid action type given.")
            return
        }
        if (!actionData) {
            setError("Something went wrong with the data.")
            return
        }

        const inputs = actionTypes.find(a => a.value == actionType.value && a.name == actionType.name)!.inputs
        const required = inputs.filter(i => i.required)
        for (const input of required) {
            if (!(input.key in actionData)) {
                setError("The field '" + input.label + "' is required.")
                return
            }
        }

        let info: any = {
            name: actionName,
            type: actionType.value,
        }

        info.data = actionData
        if (isNew) {
            axios.post("/api/settings/redemptions/actions", info)
                .then(d => {
                    adder(actionName, actionType.value, actionData)
                    setActionName("")
                    setActionType(undefined)
                    setActionData({})
                })
                .catch(error => setError(error.response.data.message))
        } else {
            axios.put("/api/settings/redemptions/actions", info)
                .then(d => {
                    setIsEditable(false)
                })
                .catch(error => setError(error.response.data.message))
        }
    }

    function Cancel(data: { n: string, t: ActionType | undefined, d: { [k: string]: any } } | undefined) {
        setError(undefined)
        if (!data)
            return

        setActionName(data.n)
        setActionType(actionTypes.find(a => a.value == data.t))
        setActionData(data.d)
        setIsEditable(false)
        setOldData(undefined)
    }

    function Delete() {
        axios.delete("/api/settings/redemptions/actions?action_name=" + actionName)
            .then(d => {
                remover(d.data)
            })
    }

    return (
        <div
            className="bg-orange-300 p-3 border-2 border-orange-400 rounded-lg w-[830px]">
            {isMinimized &&
                <div
                    className="flex">
                    <Label
                        className="mr-2 grow text-lg align-middle"
                        htmlFor="name">
                        {actionName}
                    </Label>
                    <Button
                        className="flex self-end"
                        onClick={e => setIsMinimized(!isMinimized)}>
                        {isMinimized ? <Maximize2 /> : <Minimize2 />}
                    </Button>
                </div>
                || !isMinimized &&
                <div>
                    <div
                        className="pb-3">
                        <Label
                            className="mr-2"
                            htmlFor="name">
                            Action name
                        </Label>
                        <Input
                            className="inline-block w-[300px]"
                            id="name"
                            placeholder="Enter a name for this action"
                            onChange={e => setActionName(e.target.value)}
                            value={actionName}
                            readOnly={!isNew} />
                        <Label
                            className="ml-10 mr-2"
                            htmlFor="type">
                            Action type
                        </Label>
                        {!isEditable &&
                            <Input
                                className="inline-block w-[300px] justify-between"
                                name="type"
                                value={actionType?.name}
                                readOnly />
                            || isEditable &&
                            <Popover
                                open={open['actions']}
                                onOpenChange={() => setOpen({ ...open, 'actions': !open['actions'] })}>
                                <PopoverTrigger asChild>
                                    <Button
                                        variant="outline"
                                        role="combobox"
                                        aria-expanded={open['actions']}
                                        className="w-[300px] justify-between"
                                    >{!actionType ? "Select one..." : actionType.name}</Button>
                                </PopoverTrigger>
                                <PopoverContent>
                                    <Command>
                                        <CommandInput
                                            placeholder="Filter actions..."
                                            autoFocus={true} />
                                        <CommandList>
                                            <CommandEmpty>No action found.</CommandEmpty>
                                            <CommandGroup>
                                                {actionTypes.map((action) => (
                                                    <CommandItem
                                                        value={action.name}
                                                        key={action.value}
                                                        onSelect={(value) => {
                                                            setActionType(actionTypes.find(v => v.name.toLowerCase() == value.toLowerCase()))
                                                            setOpen({ ...open, 'actions': false })
                                                        }}>
                                                        {action.name}
                                                    </CommandItem>
                                                ))}
                                            </CommandGroup>
                                        </CommandList>
                                    </Command>
                                </PopoverContent>
                            </Popover>
                        }
                    </div>
                    <div>
                        {actionType &&
                            <div>
                                {actionType.inputs.map(i => {
                                    if (i.type == "text") {
                                        return <div key={i.key} className="mt-3">
                                            <Label
                                                className="mr-2"
                                                htmlFor={i.key}>
                                                {i.label}
                                            </Label>
                                            <Input
                                                className="w-[300px] justify-between inline-block"
                                                name={i.key}
                                                placeholder={i.placeholder}
                                                value={actionData[i.key]}
                                                onChange={e => setActionData(d => {
                                                    let abc = { ...actionData }
                                                    abc[i.key] = e.target.value;
                                                    return abc
                                                })}
                                                readOnly={!isEditable} />
                                        </div>
                                    } else if (i.type == "number") {
                                        return <div key={i.key} className="mt-3">
                                            <Label
                                                className="mr-2"
                                                htmlFor={i.key}>
                                                {i.label}
                                            </Label>
                                            <Input
                                                className="w-[300px] justify-between inline-block"
                                                name={i.key}
                                                placeholder={i.placeholder}
                                                value={actionData[i.key]}
                                                onChange={e => setActionData(d => {
                                                    let abc = { ...actionData }
                                                    const v = parseInt(e.target.value)
                                                    if (e.target.value.length == 0 || Number.isNaN(v)) {
                                                        abc[i.key] = "0"
                                                    } else if (!Number.isNaN(v) && Number.isSafeInteger(v)) {
                                                        abc[i.key] = v.toString()
                                                    }
                                                    return abc
                                                })}
                                                readOnly={!isEditable} />
                                        </div>
                                    } else if (i.type == "text-values") {
                                        return <div key={i.key} className="mt-3">
                                            <Label
                                                className="mr-2"
                                                htmlFor={i.key}>
                                                {i.label}
                                            </Label>
                                            <Input
                                                className="w-[300px] justify-between inline-block"
                                                name={i.key}
                                                placeholder={i.placeholder}
                                                value={actionData[i.key]}
                                                onChange={e => setActionData(d => {
                                                    let abc = { ...actionData }
                                                    abc[i.key] = i.values.map((v: string) => v.startsWith(e.target.value)).some((v: boolean) => v) ? e.target.value : abc[i.key]
                                                    return abc
                                                })}
                                                readOnly={!isEditable} />
                                        </div>
                                    } else {
                                        return <div key={i.key}>
                                            <Label
                                                className="mr-2"
                                                htmlFor={i.key}>
                                                Connection
                                            </Label>
                                            <Popover
                                                open={open[i.type]}
                                                onOpenChange={() => { const temp = { ...open }; temp[i.type] = !temp[i.type]; setOpen(temp) }}>
                                                <PopoverTrigger asChild>
                                                    <Button
                                                        variant="outline"
                                                        role="combobox"
                                                        aria-expanded={open[i.type]}
                                                        className="w-[300px] justify-between"
                                                    >{!('oauth_name' in actionData) ? "Select connection..." : actionData.oauth_name}</Button>
                                                </PopoverTrigger>
                                                <PopoverContent>
                                                    <Command>
                                                        <CommandInput
                                                            placeholder="Search connections..."
                                                            autoFocus={true} />
                                                        <CommandList>
                                                            <CommandEmpty>No connection found.</CommandEmpty>
                                                            <CommandGroup>
                                                                {connections.filter(c => !i.type.includes('.') || c.type == i.type.split('.')[1])
                                                                    .map((connection) => (
                                                                        <CommandItem
                                                                            value={connection.name}
                                                                            key={connection.name}
                                                                            onSelect={(value) => {
                                                                                const connection = connections.find(v => v.name.toLowerCase() == value.toLowerCase())
                                                                                if (!!connection) {
                                                                                    setActionData({
                                                                                        'oauth_name': connection.name,
                                                                                        'oauth_type': connection.type
                                                                                    })
                                                                                }
                                                                                else
                                                                                    setActionData({})

                                                                                const temp = { ...open }
                                                                                temp[i.type] = false
                                                                                setOpen(temp)
                                                                            }}>
                                                                            {connection.name}
                                                                        </CommandItem>
                                                                    ))}
                                                            </CommandGroup>
                                                        </CommandList>
                                                    </Command>
                                                </PopoverContent>
                                            </Popover>
                                        </div>
                                    }
                                })}
                            </div>
                        }
                        {actionType && actionType.value == ActionType.OBS_TRANSFORM &&
                            <div>
                                {obsTransformations.map(t =>
                                    <div
                                        key={t.label.toLowerCase()}
                                        className="mt-3">
                                        <Label
                                            className="mr-2"
                                            htmlFor={t.label.toLowerCase()}>
                                            {t.label.split("_").map(w => w.substring(0, 1).toUpperCase() + w.substring(1).toLowerCase()).join(" ")}
                                        </Label>
                                        <Input
                                            className="w-[300px] justify-between inline-block"
                                            name={t.label.toLowerCase()}
                                            placeholder={t.placeholder}
                                            value={actionData[t.label]}
                                            onChange={e => {
                                                let c = { ...actionData }
                                                c[t.label] = e.target.value
                                                setActionData(c)
                                            }}
                                            readOnly={!isEditable} />
                                    </div>
                                )}
                            </div>
                        }
                    </div>
                    {error &&
                        <div className="text-red-600 font-bold">
                            {error}
                        </div>
                    }
                    <div>
                        {isEditable &&
                            <Button
                                className="m-3"
                                onClick={() => Save(isNew)}>
                                {isNew ? "Add" : "Save"}
                            </Button>
                        }
                        {isEditable && !isNew &&
                            <Button
                                className="m-3"
                                onClick={() => Cancel(oldData)}>
                                Cancel
                            </Button>
                        }
                        {showEdit && !isEditable &&
                            <Button
                                className="m-3"
                                onClick={() => {
                                    setOldData({ n: actionName, t: actionType?.value, d: actionData })
                                    setIsEditable(true)
                                }}>
                                Edit
                            </Button>
                        }
                        {!isEditable &&
                            <Button
                                className="m-3 bg-red-500 hover:bg-red-600 align-bottom"
                                onClick={() => Delete()}>
                                <Trash2Icon />
                            </Button>
                        }
                        {!isNew &&
                            <Button
                                className="m-3 align-middle"
                                onClick={e => setIsMinimized(!isMinimized)}>
                                {isMinimized ? <Maximize2 /> : <Minimize2 />}
                            </Button>
                        }
                    </div>
                </div>
            }
        </div>
    );
}

export default RedemptionAction;