import { ChevronDown, Info, LucideProps } from "lucide-react";
import React, {
    ComponentProps,
    DetailedHTMLProps,
    Dispatch,
    InputHTMLAttributes,
    LabelHTMLAttributes,
    ReactNode,
    RefObject,
    SelectHTMLAttributes,
    SetStateAction,
} from "react";
import { twMerge } from "tailwind-merge";
import Row from "../layout/Row";
import Dropdown from "../elements/Dropdown";
import Card from "../elements/Card";
import Span from "../layout/Span";
import Stack from "../layout/Stack";
import twuiSlugify from "../utils/slugify";
import twuiSlugToNormalText from "../utils/slug-to-normal-text";

export type TWUISelectValidityObject = {
    isValid?: boolean;
    msg?: string;
};

export type TWUISelectOptionObject<
    KeyType extends string = string,
    T extends { [k: string]: any } = { [k: string]: any }
> = {
    title?: string;
    value: KeyType;
    default?: boolean;
    data?: T;
};

export type TWUISelectProps<
    KeyType extends string,
    T extends { [k: string]: any } = { [k: string]: any }
> = DetailedHTMLProps<
    SelectHTMLAttributes<HTMLSelectElement>,
    HTMLSelectElement
> & {
    options: TWUISelectOptionObject<KeyType, T>[];
    label?: string;
    showLabel?: boolean;
    wrapperProps?: DetailedHTMLProps<
        InputHTMLAttributes<HTMLDivElement>,
        HTMLDivElement
    >;
    wrapperWrapperProps?: ComponentProps<typeof Stack>;
    labelProps?: DetailedHTMLProps<
        LabelHTMLAttributes<HTMLLabelElement>,
        HTMLLabelElement
    >;
    componentRef?: RefObject<HTMLSelectElement>;
    iconProps?: LucideProps;
    changeHandler?: (value: KeyType, data?: T) => void;
    info?: string | ReactNode;
    validateValueFn?: (value: string) => Promise<TWUISelectValidityObject>;
    dispatchState?: Dispatch<SetStateAction<T | undefined>>;
    name?: KeyType;
};

export type TWUISelectValueObject<
    KeyType extends string,
    T extends { [k: string]: any } = { [k: string]: any }
> = {
    value: KeyType;
    data?: T;
};

/**
 * # Select Element
 * @className twui-select-wrapper
 * @className twui-select
 * @className twui-select-dropdown-icon
 */
export default function Select<
    KeyType extends string,
    T extends { [k: string]: any } = { [k: string]: any }
>({
    label,
    options,
    componentRef,
    labelProps,
    wrapperProps,
    showLabel,
    iconProps,
    changeHandler,
    info,
    validateValueFn,
    wrapperWrapperProps,
    dispatchState,
    ...props
}: TWUISelectProps<KeyType, T>) {
    const [validity, setValidity] = React.useState<TWUISelectValidityObject>({
        isValid: true,
    });

    const selectRef = componentRef || React.useRef<HTMLSelectElement>(null);

    const [value, setValue] = React.useState<TWUISelectValueObject<KeyType, T>>(
        {
            value: options[0]?.value,
            data: options[0]?.data,
        }
    );

    React.useEffect(() => {
        setTimeout(() => {
            requestAnimationFrame(() => {
                const currentSelectValue = selectRef.current?.value;

                if (currentSelectValue && validateValueFn) {
                    validateValueFn(currentSelectValue).then((res) => {
                        setValidity(res);
                    });
                }
            });
        }, 200);
    }, []);

    React.useEffect(() => {
        dispatchState?.(value.data);
    }, [value]);

    const selectID = label
        ? twuiSlugify(label)
        : props.name
        ? twuiSlugify(props.name)
        : props.title
        ? twuiSlugify(props.title)
        : `select-${Math.round(Math.random() * 1000000)}`;

    return (
        <Stack
            {...wrapperWrapperProps}
            className={twMerge("gap-1", wrapperWrapperProps?.className)}
        >
            <div
                {...wrapperProps}
                className={twMerge(
                    "relative w-full flex items-center border rounded-default",
                    "border-slate-300 dark:border-white/20 pr-2",
                    "focus:border-slate-700 dark:focus:border-white/50",
                    "outline-slate-300 dark:outline-white/20",
                    "focus:outline-slate-700 dark:focus:outline-white/50",
                    "bg-white dark:bg-background-dark",
                    validity.isValid ? "" : "outline-warning border-warning",
                    wrapperProps?.className
                )}
            >
                {showLabel && (
                    <label
                        htmlFor={selectID}
                        {...labelProps}
                        className={twMerge(
                            "text-xs absolute -top-2.5 left-2 text-foreground-light/80 bg-background-light",
                            "dark:text-foreground-dark/70 dark:bg-background-dark px-1.5 rounded-t",
                            "twui-input-label",
                            labelProps?.className
                        )}
                    >
                        {label || props.title || props.name}
                    </label>
                )}

                <select
                    id={selectID}
                    aria-label={props["aria-label"] || props.title}
                    {...props}
                    className={twMerge(
                        "w-full pl-3 py-2 rounded-default appearance-none pr-8",
                        "grow !border-none !outline-none",
                        "twui-select",
                        props.className
                    )}
                    ref={selectRef}
                    value={
                        options.flat().find((opt) => opt.default)?.value ||
                        undefined
                    }
                    onChange={(e) => {
                        const targetValue = options.find(
                            (opt) => opt.value == e.target.value
                        );

                        if (targetValue) {
                            setValue(targetValue);
                        }

                        changeHandler?.(
                            e.target.value as (typeof options)[number]["value"],
                            targetValue?.data
                        );

                        props.onChange?.(e);

                        validateValueFn?.(e.target.value).then((res) => {
                            setValidity(res);
                        });
                    }}
                >
                    {options.flat().map((option, index) => {
                        const optionTitle =
                            option.title || twuiSlugToNormalText(option.value);

                        return (
                            <option key={index} value={option.value}>
                                {optionTitle}
                            </option>
                        );
                    })}
                </select>

                <ChevronDown
                    size={20}
                    {...iconProps}
                    className={twMerge(
                        "pointer-events-none -ml-6",
                        iconProps?.className
                    )}
                />

                {info && (
                    <Dropdown
                        target={
                            <div title="Select Info Button">
                                <Info size={20} />
                            </div>
                        }
                        hoverOpen
                    >
                        <Card className="min-w-[250px] p-6">
                            {typeof info == "string" ? (
                                <Span>{info}</Span>
                            ) : (
                                info
                            )}
                        </Card>
                    </Dropdown>
                )}
            </div>
            {!validity.isValid && validity.msg ? (
                <Span size="smaller" className="text-warning">
                    {validity.msg}
                </Span>
            ) : undefined}
        </Stack>
    );
}
