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

export type InputProps<KeyType extends string> = Omit<
    DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
    "prefix" | "suffix"
> &
    Omit<
        DetailedHTMLProps<
            TextareaHTMLAttributes<HTMLTextAreaElement>,
            HTMLTextAreaElement
        >,
        "prefix" | "suffix"
    > & {
        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) => void;
        autoComplete?: (typeof AutocompleteOptions)[number];
        name?: KeyType;
        valueUpdate?: string;
        numberText?: boolean;
        rawNumber?: boolean;
        setReady?: React.Dispatch<React.SetStateAction<boolean>>;
        decimal?: number;
        info?: string | ReactNode;
        ready?: boolean;
        validity?: TWUISelectValidityObject;
        clearInputProps?: React.DetailedHTMLProps<
            React.HTMLAttributes<HTMLDivElement>,
            HTMLDivElement
        >;
        // refreshDefaultValue?: number;
    };

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,
        clearInputProps,
        rawNumber,
        // refreshDefaultValue,
        ...props
    } = inputProps;

    const componentRefreshesRef = useRef(0);
    let timeoutRef = useRef<any>(null);
    let validationFnTimeoutRef = useRef<any>(null);
    let externalValueChangeTimeoutRef = useRef<any>(null);

    refreshes++;
    componentRefreshesRef.current++;

    function getFinalValue(v: any) {
        if (rawNumber) return twuiNumberfy(v);
        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 [value, setValue] = React.useState(
        props.defaultValue ? String(props.defaultValue) : "",
    );
    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);

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

    const updateValueFn = (val: string) => {
        if (buttonDownRef.current) return;

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

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

            window.clearTimeout(timeoutRef.current);

            if (validationRegex) {
                timeoutRef.current = setTimeout(() => {
                    setValidity({
                        isValid: validationRegex.test(val),
                        msg: "Value mismatch",
                    });
                }, finalDebounce);
            }

            if (validationFunction) {
                window.clearTimeout(validationFnTimeoutRef.current);

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

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

    // React.useEffect(() => {
    //     if (!refreshDefaultValue) return;
    //     console.log("Name:", props.title || props.name);
    //     console.log("props.defaultValue", props.defaultValue);
    //     // setValue(String(props.defaultValue || ""));
    // }, [refreshDefaultValue]);

    React.useEffect(() => {
        // if (!existingReady) return;
        if (istextarea && textAreaRef.current) {
        } else if (inputRef?.current) {
            inputRef.current.value = getFinalValue(value);
        }
        updateValueFn(value);
    }, [value]);

    function handleValueChange(
        e: React.ChangeEvent<HTMLInputElement> &
            React.ChangeEvent<HTMLTextAreaElement>,
    ) {
        const newValue = e.target.value;
        setValue(newValue);
        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);
    // }

    const targetComponent = istextarea ? (
        <textarea
            placeholder={
                props.name ? twuiSlugToNormalText(props.name) : undefined
            }
            {...props}
            className={twMerge(
                "w-full outline-none bg-transparent grow",
                "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 grow",
                "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}
            autoComplete={autoComplete}
            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-100",
                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 && prefix}

                {targetComponent}

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

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

                            setValue("");
                            clearInputProps?.onClick?.(e);
                        }}
                    >
                        <X className="w-full h-full" />
                    </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 ? suffix : null}

                {numberText ? (
                    <NumberInputButtons
                        setValue={setValue}
                        inputRef={inputRef}
                        value={value}
                        max={props.max}
                        min={props.min}
                        step={props.step}
                        buttonDownRef={buttonDownRef}
                        decimal={decimal}
                    />
                ) : null}
            </div>
            {info && (
                <Dropdown
                    target={
                        <Row className="gap-1">
                            <Info size={12} className="opacity-40" />
                            <Span
                                size="smaller"
                                className="opacity-70 hover:opacity-100"
                            >
                                {info}
                            </Span>
                        </Row>
                    }
                    openDebounce={700}
                    className="z-1000"
                    hoverOpen
                >
                    <Paper
                        className={twMerge(
                            "min-w-[250px] shadow-lg shadow-slate-200 dark:shadow-white/10",
                            "max-w-[300px] w-full bg-background-light! dark:bg-background-dark! z-1000",
                        )}
                    >
                        <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>
    );
}
