import React, {
    DetailedHTMLProps,
    InputHTMLAttributes,
    LabelHTMLAttributes,
    RefObject,
    TextareaHTMLAttributes,
} from "react";
import { twMerge } from "tailwind-merge";
import Span from "../layout/Span";

let timeout: any;

const autocompleteOptions = [
    // Personal Information
    "name",
    "honorific-prefix",
    "given-name",
    "additional-name",
    "family-name",
    "honorific-suffix",
    "nickname",

    // Contact Information
    "email",
    "username",
    "new-password",
    "current-password",
    "one-time-code",
    "organization-title",
    "organization",

    // Address Fields
    "street-address",
    "address-line1",
    "address-line2",
    "address-line3",
    "address-level4",
    "address-level3",
    "address-level2",
    "address-level1",
    "country",
    "country-name",
    "postal-code",

    // Phone Numbers
    "tel",
    "tel-country-code",
    "tel-national",
    "tel-area-code",
    "tel-local",
    "tel-extension",

    // Dates
    "bday",
    "bday-day",
    "bday-month",
    "bday-year",

    // Payment Information
    "cc-name",
    "cc-given-name",
    "cc-additional-name",
    "cc-family-name",
    "cc-number",
    "cc-exp",
    "cc-exp-month",
    "cc-exp-year",
    "cc-csc",
    "cc-type",

    // Additional Options
    "sex",
    "url",
    "photo",

    // Special Values
    "on", // Enables autofill (default)
    "off", // Disables autofill
] as const;

export type InputProps<KeyType extends string> = DetailedHTMLProps<
    InputHTMLAttributes<HTMLInputElement>,
    HTMLInputElement
> &
    DetailedHTMLProps<
        TextareaHTMLAttributes<HTMLTextAreaElement>,
        HTMLTextAreaElement
    > & {
        label?: string;
        variant?: "normal" | "warning" | "error" | "inactive";
        prefix?: string | React.ReactNode;
        suffix?: string | React.ReactNode;
        showLabel?: boolean;
        istextarea?: boolean;
        wrapperProps?: DetailedHTMLProps<
            InputHTMLAttributes<HTMLDivElement>,
            HTMLDivElement
        >;
        labelProps?: DetailedHTMLProps<
            LabelHTMLAttributes<HTMLLabelElement>,
            HTMLLabelElement
        >;
        componentRef?: RefObject<any>;
        validationRegex?: RegExp;
        debounce?: number;
        invalidMessage?: string;
        validationFunction?: (value: string) => Promise<boolean>;
        autoComplete?: (typeof autocompleteOptions)[number];
        name?: KeyType;
        valueUpdate?: string;
    };

/**
 * # Input Element
 * @className twui-input
 * @className twui-input-wrapper
 * @className twui-input-invalid
 */
export default function Input<KeyType extends string>({
    label,
    variant,
    prefix,
    suffix,
    componentRef,
    labelProps,
    wrapperProps,
    showLabel,
    istextarea,
    debounce,
    invalidMessage,
    autoComplete,
    validationFunction,
    validationRegex,
    valueUpdate,
    ...props
}: InputProps<KeyType>) {
    const [focus, setFocus] = React.useState(false);
    const [value, setValue] = React.useState(
        props.value
            ? String(props.value)
            : props.defaultValue
            ? String(props.defaultValue)
            : ""
    );

    delete props.defaultValue;

    const [isValid, setIsValid] = React.useState(true);

    const DEFAULT_DEBOUNCE = 500;
    const finalDebounce = debounce || DEFAULT_DEBOUNCE;

    React.useEffect(() => {
        if (!value.match(/./)) return setIsValid(true);
        window.clearTimeout(timeout);

        if (validationRegex) {
            timeout = setTimeout(() => {
                setIsValid(validationRegex.test(value));
            }, finalDebounce);
        }

        if (validationFunction) {
            timeout = setTimeout(() => {
                validationFunction(value).then((res) => {
                    setIsValid(res);
                });
            }, finalDebounce);
        }
    }, [value]);

    React.useEffect(() => {
        if (!props.value) return;
        setValue(String(props.value));
    }, [props.value]);

    const targetComponent = istextarea ? (
        <textarea
            {...props}
            className={twMerge(
                "w-full outline-none bg-transparent",
                "twui-textarea",
                props.className
            )}
            ref={componentRef}
            onFocus={(e) => {
                setFocus(true);
                props?.onFocus?.(e);
            }}
            onBlur={(e) => {
                setFocus(false);
                props?.onBlur?.(e);
            }}
            value={value}
            onChange={(e) => setValue(e.target.value)}
            autoComplete={autoComplete}
            rows={props.height ? Number(props.height) : 4}
        />
    ) : (
        <input
            {...props}
            className={twMerge(
                "w-full outline-none bg-transparent border-none",
                "hover:border-none hover:outline-none focus:border-none focus:outline-none",
                "dark:bg-transparent dark:outline-none dark:border-none",
                "p-0",
                "twui-input",
                props.className
            )}
            ref={componentRef}
            onFocus={(e) => {
                setFocus(true);
                props?.onFocus?.(e);
            }}
            onBlur={(e) => {
                setFocus(false);
                props?.onBlur?.(e);
            }}
            value={value}
            onChange={(e) => {
                setValue(e.target.value);
                props?.onChange?.(e);
            }}
        />
    );

    return (
        <div
            {...wrapperProps}
            className={twMerge(
                "relative flex items-center gap-2 border rounded-md px-3 py-2 outline outline-1",
                focus && isValid
                    ? "border-slate-700 dark:border-white/50"
                    : "border-slate-300 dark:border-white/20",
                focus && isValid
                    ? "outline-slate-700 dark:outline-white/50"
                    : "outline-slate-300 dark:outline-white/20",
                variant == "warning" &&
                    isValid &&
                    "border-yellow-500 dark:border-yellow-300 outline-yellow-500 dark:outline-yellow-300",
                variant == "error" &&
                    isValid &&
                    "border-red-500 dark:border-red-300 outline-red-500 dark:outline-red-300",
                variant == "inactive" &&
                    isValid &&
                    "opacity-40 pointer-events-none",
                "bg-white dark:bg-black",
                isValid
                    ? ""
                    : "border-orange-500 outline-orange-500 twui-input-invalid",
                props.readOnly && "opacity-50 pointer-events-none",
                "twui-input-wrapper",
                wrapperProps?.className
            )}
        >
            {showLabel && (
                <label
                    htmlFor={props.name}
                    {...labelProps}
                    className={twMerge(
                        "text-xs absolute -top-2.5 left-2 text-slate-500 bg-white px-1.5 rounded-t",
                        "dark:text-white/60 dark:bg-black",
                        "twui-input-label",
                        labelProps?.className
                    )}
                >
                    {label || props.placeholder || props.name}
                </label>
            )}

            {prefix && (
                <div className="opacity-60 pointer-events-none whitespace-nowrap">
                    {prefix}
                </div>
            )}

            {targetComponent}

            {suffix && (
                <div className="opacity-60 pointer-events-none whitespace-nowrap">
                    {suffix}
                </div>
            )}
            {!isValid && (
                <Span className="opacity-30 pointer-events-none whitespace-nowrap">
                    {invalidMessage || "Invalid"}
                </Span>
            )}
        </div>
    );
}