import React, {
    ComponentProps,
    DetailedHTMLProps,
    InputHTMLAttributes,
    LabelHTMLAttributes,
    ReactNode,
    RefObject,
    TextareaHTMLAttributes,
} from "react";
import { twMerge } from "tailwind-merge";
import Span from "../../layout/Span";
import Button from "../../layout/Button";
import { Eye, EyeOff, Info, InfoIcon, X } from "lucide-react";
import { AutocompleteOptions } from "../../types";
import twuiNumberfy from "../../utils/numberfy";
import Dropdown from "../../elements/Dropdown";
import Card from "../../elements/Card";
import Stack from "../../layout/Stack";
import NumberInputButtons from "./NumberInputButtons";
import twuiSlugToNormalText from "../../utils/slug-to-normal-text";
import twuiUseReady from "../../hooks/useReady";
import Row from "../../layout/Row";
import Paper from "../../elements/Paper";
import { TWUISelectValidityObject } from "../Select";

let timeout: any;
let validationFnTimeout: any;
let externalValueChangeTimeout: any;

export type InputProps<KeyType extends string> = DetailedHTMLProps<
    InputHTMLAttributes<HTMLInputElement>,
    HTMLInputElement
> &
    DetailedHTMLProps<
        TextareaHTMLAttributes<HTMLTextAreaElement>,
        HTMLTextAreaElement
    > & {
        label?: string;
        variant?: "normal" | "warning" | "error" | "inactive";
        prefix?: string | ReactNode;
        suffix?: string | ReactNode;
        suffixProps?: React.DetailedHTMLProps<
            React.HTMLAttributes<HTMLDivElement>,
            HTMLDivElement
        >;
        showLabel?: boolean;
        istextarea?: boolean;
        wrapperProps?: DetailedHTMLProps<
            InputHTMLAttributes<HTMLDivElement>,
            HTMLDivElement
        >;
        wrapperWrapperProps?: ComponentProps<typeof Stack>;
        labelProps?: DetailedHTMLProps<
            LabelHTMLAttributes<HTMLLabelElement>,
            HTMLLabelElement
        >;
        componentRef?: RefObject<any>;
        validationRegex?: RegExp;
        debounce?: number;
        invalidMessage?: string;
        validationFunction?: (
            value: string,
            element?: HTMLInputElement | HTMLTextAreaElement
        ) => Promise<TWUISelectValidityObject>;
        changeHandler?: (
            value: string,
            element?: HTMLInputElement | HTMLTextAreaElement
        ) => void;
        autoComplete?: (typeof AutocompleteOptions)[number];
        name?: KeyType;
        valueUpdate?: string;
        numberText?: boolean;
        setReady?: React.Dispatch<React.SetStateAction<boolean>>;
        decimal?: number;
        info?: string | ReactNode;
        ready?: boolean;
        validity?: TWUISelectValidityObject;
    };

let refreshes = 0;

/**
 * # Input Element
 * @className twui-input
 * @className twui-input-wrapper
 * @className twui-input-invalid
 * @className twui-clear-input-field-button
 */
export default function Input<KeyType extends string>(
    inputProps: InputProps<KeyType>
) {
    const {
        label,
        variant,
        prefix,
        suffix,
        componentRef,
        labelProps,
        wrapperProps,
        wrapperWrapperProps,
        showLabel,
        istextarea,
        debounce,
        invalidMessage: initialInvalidMessage,
        autoComplete,
        validationFunction,
        validationRegex,
        valueUpdate,
        numberText,
        decimal,
        suffixProps,
        ready: existingReady,
        setReady: externalSetReady,
        info,
        changeHandler,
        validity: existingValidity,
        ...props
    } = inputProps;

    function getFinalValue(v: any) {
        if (numberText) {
            return (
                twuiNumberfy(v, decimal).toLocaleString() +
                (String(v).match(/\.$/) ? "." : "")
            );
        }

        return v;
    }

    const defaultInitialValue =
        props.defaultValue || props.value
            ? getFinalValue(props.defaultValue || props.value)
            : undefined;

    const [validity, setValidity] = React.useState<TWUISelectValidityObject>(
        existingValidity || {
            isValid: true,
        }
    );

    const inputRef = componentRef || React.useRef<HTMLInputElement>(null);
    const textAreaRef = componentRef || React.useRef<HTMLTextAreaElement>(null);
    const buttonDownRef = React.useRef(false);

    const [focus, setFocus] = React.useState(false);
    const [inputType, setInputType] = React.useState(
        numberText ? "text" : props.type
    );

    const DEFAULT_DEBOUNCE = 500;
    const finalDebounce = debounce || DEFAULT_DEBOUNCE;
    const finalLabel =
        label ||
        props.title ||
        props.placeholder ||
        (props.name ? twuiSlugToNormalText(props.name) : undefined);

    function getNormalizedValue(value: string) {
        if (numberText) {
            if (props.max && twuiNumberfy(value) > twuiNumberfy(props.max))
                return getFinalValue(props.max);

            if (props.min && twuiNumberfy(value) < twuiNumberfy(props.min))
                return getFinalValue(props.min);

            return getFinalValue(value);
        } else {
            return value;
        }
    }

    React.useEffect(() => {
        if (!existingValidity) return;
        setValidity(existingValidity);
    }, [existingValidity]);

    const updateValueFn = (
        val: string,
        el?: HTMLInputElement | HTMLTextAreaElement
    ) => {
        if (buttonDownRef.current) return;

        if (changeHandler) {
            window.clearTimeout(externalValueChangeTimeout);
            externalValueChangeTimeout = setTimeout(() => {
                changeHandler(val, el);
            }, finalDebounce);
        }

        if (typeof val == "string") {
            if (!val.match(/./)) {
                setValidity({ isValid: true });
                props.value = "";
                if (istextarea && textAreaRef.current) {
                    textAreaRef.current.value = "";
                } else if (inputRef?.current) {
                    inputRef.current.value = "";
                }
                return;
            }

            window.clearTimeout(timeout);

            if (validationRegex && !validationFunction) {
                timeout = setTimeout(() => {
                    setValidity({
                        isValid: validationRegex.test(val),
                        msg: "Value mismatch",
                    });
                }, finalDebounce);
            } else if (validationFunction) {
                window.clearTimeout(validationFnTimeout);

                validationFnTimeout = setTimeout(() => {
                    if (validationRegex && !validationRegex.test(val)) {
                        return;
                    }
                    validationFunction(val, el).then((res) => {
                        setValidity(res);
                    });
                }, finalDebounce);
            }
        }
    };

    React.useEffect(() => {
        if (typeof props.value !== "string" || !props.value.match(/./)) return;
        updateValueFn(String(props.value));
    }, [props.value]);

    function handleValueChange(
        e: React.ChangeEvent<HTMLInputElement> &
            React.ChangeEvent<HTMLTextAreaElement>
    ) {
        const newValue = e.target.value;
        updateValue(newValue, e.target);
        props.onChange?.(e);
    }

    function updateValue(
        v: string,
        el?: HTMLInputElement | HTMLTextAreaElement
    ) {
        if (istextarea && textAreaRef.current) {
        } else if (inputRef?.current) {
            inputRef.current.value = getFinalValue(v);
        }
        updateValueFn(v, el);
    }

    const targetComponent = istextarea ? (
        <textarea
            placeholder={
                props.name ? twuiSlugToNormalText(props.name) : undefined
            }
            {...props}
            className={twMerge(
                "w-full outline-none bg-transparent",
                "twui-textarea",
                props.className
            )}
            ref={textAreaRef}
            onFocus={(e) => {
                setFocus(true);
                props?.onFocus?.(e);
            }}
            onBlur={(e) => {
                setFocus(false);
                props?.onBlur?.(e);
            }}
            onChange={handleValueChange}
            autoComplete={autoComplete}
            rows={props.height ? Number(props.height) : props.rows || 2}
            defaultValue={defaultInitialValue}
            value={props.value ? getFinalValue(props.value) : undefined}
        />
    ) : (
        <input
            placeholder={
                props.name ? twuiSlugToNormalText(props.name) : undefined
            }
            {...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={inputRef}
            onFocus={(e) => {
                setFocus(true);
                props?.onFocus?.(e);
            }}
            onBlur={(e) => {
                setFocus(false);
                props?.onBlur?.(e);
            }}
            onChange={handleValueChange}
            type={inputType}
            defaultValue={defaultInitialValue}
            value={props.value ? getFinalValue(props.value) : undefined}
        />
    );

    return (
        <Stack
            title={`${finalLabel}${props.required ? " (Required)" : ""}`}
            {...wrapperWrapperProps}
            className={twMerge(
                "w-full gap-1.5 relative z-0 hover:z-10",
                wrapperWrapperProps?.className
            )}
        >
            <div
                {...wrapperProps}
                className={twMerge(
                    "relative flex items-center gap-2 rounded-default px-3 py-2 outline-1",
                    "hover:[&_.twui-clear-input-field-button]:opacity-100",
                    "w-full border-none",
                    focus && validity.isValid
                        ? "outline-slate-700 dark:outline-white/50"
                        : "outline-slate-300 dark:outline-white/20",
                    focus && validity.isValid
                        ? "outline-slate-700 dark:outline-white/50"
                        : "outline-slate-300 dark:outline-white/20",
                    variant == "warning" &&
                        validity.isValid &&
                        "outline-yellow-500 dark:outline-yellow-300",
                    variant == "error" &&
                        validity.isValid &&
                        "border-red-500 dark:border-red-300 outline-red-500 dark:outline-red-300",
                    variant == "inactive" &&
                        validity.isValid &&
                        "opacity-40 pointer-events-none",
                    "bg-white dark:bg-background-dark",
                    validity.isValid
                        ? ""
                        : "border-orange-500 outline-orange-500 dark:border-orange-500 dark:outline-orange-500 twui-input-invalid",
                    props.readOnly
                        ? props.type == "password"
                            ? "opacity-50"
                            : "opacity-50 pointer-events-none"
                        : undefined,
                    "twui-input-wrapper",
                    wrapperProps?.className
                )}
            >
                {showLabel && (
                    <label
                        htmlFor={props.name}
                        {...labelProps}
                        className={twMerge(
                            "text-xs absolute -top-2.5 left-2 text-foreground-light/80 bg-background-light",
                            "dark:text-foreground-dark/80 dark:bg-background-dark whitespace-nowrap",
                            "overflow-hidden overflow-ellipsis z-20 px-1.5 rounded-t-default",
                            "twui-input-label",
                            labelProps?.className
                        )}
                    >
                        {finalLabel}

                        {props.required ? (
                            <span className="text-secondary ml-1">*</span>
                        ) : (
                            ""
                        )}
                    </label>
                )}

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

                {targetComponent}

                {props.type == "search" || props.readOnly ? null : (
                    <div
                        title="Clear Input Field"
                        className={twMerge(
                            "p-1 -my-2 -mx-1 opacity-0 cursor-pointer",
                            "bg-background-light dark:bg-background-dark",
                            "twui-clear-input-field-button"
                        )}
                        onClick={(e) => {
                            e.preventDefault();

                            if (inputRef.current) {
                                inputRef.current.value = "";
                            }
                            if (textAreaRef.current) {
                                textAreaRef.current.value = "";
                            }

                            updateValue("");
                        }}
                    >
                        <X size={15} />
                    </div>
                )}

                {props.type == "password" ? (
                    <div
                        title={
                            inputType == "password"
                                ? "View Psasword"
                                : "Hide Password"
                        }
                        className={twMerge("p-1 -my-2 -mx-1")}
                        onClick={(e) => {
                            e.preventDefault();
                            if (inputType == "password") {
                                setInputType("text");
                            } else {
                                setInputType("password");
                            }
                        }}
                    >
                        {inputType == "password" ? (
                            <Eye size={15} />
                        ) : (
                            <EyeOff size={15} />
                        )}
                    </div>
                ) : null}

                {suffix ? (
                    <div
                        {...suffixProps}
                        className={twMerge(
                            "opacity-60 pointer-events-none whitespace-nowrap",
                            suffixProps?.className
                        )}
                    >
                        {suffix}
                    </div>
                ) : null}

                {numberText ? (
                    <NumberInputButtons
                        updateValue={updateValue}
                        inputRef={inputRef}
                        getNormalizedValue={getNormalizedValue}
                        max={props.max}
                        min={props.min}
                        step={props.step}
                        buttonDownRef={buttonDownRef}
                    />
                ) : null}
            </div>
            {info && (
                <Dropdown
                    target={
                        <Row className="gap-1">
                            <Info size={12} className="opacity-40" />
                            <Span size="smaller" className="opacity-70">
                                {info}
                            </Span>
                        </Row>
                    }
                    openDebounce={700}
                    hoverOpen
                >
                    <Paper
                        className={twMerge(
                            "min-w-[250px] shadow-lg shadow-slate-200 dark:shadow-white/10",
                            "max-w-[300px] w-full"
                        )}
                    >
                        <Stack className="gap-2 items-center">
                            <Row className="gap-1">
                                <InfoIcon size={15} opacity={0.4} />
                                <Span className="text-xs opacity-50">Info</Span>
                            </Row>
                            <Span className="text-center">{info}</Span>
                        </Stack>
                    </Paper>
                </Dropdown>
            )}
            {!validity.isValid && validity.msg ? (
                <Span className="text-warning whitespace-nowrap" size="smaller">
                    {validity.msg || "Invalid"}
                </Span>
            ) : undefined}
        </Stack>
    );
}
