// node modules
import React, { ChangeEvent, FC, useState, KeyboardEvent, useRef, useEffect } from 'react';
import keys from 'lodash/keys';
import debounce from 'lodash/debounce';

// local components
import { Input, InputProps } from "../input";
import { Typography } from "../typography";


// local files
import { validator, numberValidator } from "./validator";
import { theming } from "../../theme";
import { useStyles } from "./styles";
import {TypographyVariants} from "../../constants";

const NUMBER_TYPE = 'number';

const numberKeys = {
    lessThen: true,
    moreThen: true,
    equal: true
}

export type ValidationQueryType = {
    value: string | number,
    errorMessage?: string
}

export type ValidationTypeProps = {
    value: 'emptyString' | 'email' | 'number',
    errorMessage?: string,
    lessThen?: ValidationQueryType,
    moreThen?: ValidationQueryType,
    equal?: ValidationQueryType
}

export interface ValidatedInputProps extends InputProps {
    toggleValidationListener?: any,
    errorText?: boolean,
    onError?: (invalid: boolean) => void,
    validationQuery?: {
        minLength?: ValidationQueryType,
        maxLength?: ValidationQueryType,
        type?: ValidationTypeProps
    },
    rootValidatedInputClass?: string,
    onKeyDown?: (e: KeyboardEvent) => void,
    onBlur?: (e: any) => void,
    errorDelay?: boolean,
    errorDelayTime?: number,
    value: string
    autoFocus?: boolean,
    maxLength?: number | string,
}


export const ValidatedInput: FC<ValidatedInputProps> = ({
    toggleValidationListener,
    errorText,
    onError,
    onChange,
    validationQuery = { type: 'emptyString' },
    errorDelay = false,
    errorDelayTime = 300,
    rootValidatedInputClass = '',
    onBlur,
    autoFocus,
    ...otherProps
}) => {
    const touched = useRef<any>(Boolean(otherProps.value));
    const { errorMessageClass, validatedInputWrapper, hidden } = useStyles({ theme: theming.useTheme() })
    const [errors, setErrors] = useState<any>([]);
    const { type } = validationQuery;
    // @ts-ignore
    const isNumberType = Boolean(type && (type.value === NUMBER_TYPE));

    const toggleError = (errors: any) => {
        if (onError) onError(Boolean(errors.length))

        setErrors(errors);
    }

    const { current: delayedToggleError } = useRef(
        debounce(toggleError, errorDelayTime)
    );

    const handleBlur = (e: any) => {
        if(!touched.current) {
            touched.current = true

            const { value = '' } = e.target;

            if (!isNumberType) validate(value);
            else validateNumber(value);
        }

        if(onBlur) {
            onBlur(e);
        }
    }

    const validate = (value: string) => {
        const errors = keys(validationQuery).reduce((err, key) => {
            // @ts-ignore
            const paramValidator = validator[key];

            if (paramValidator) {
                // @ts-ignore
                const param = validationQuery[key];
                // @ts-ignore
                const error = paramValidator(value, param);
                // @ts-ignore
                if (error) err.push(param);
            }

            return err;
        }, [])

        if (errorDelay) delayedToggleError(errors)
        else toggleError(errors);
    }

    const validateNumber = (value: string) => {
        const errors = keys(type).reduce((err, key) => {
            // @ts-ignore
            const isCorrectKey = Boolean(numberKeys[key]);

            if(isCorrectKey) {
                // @ts-ignore
                const param = type[key];
                // @ts-ignore
                const error = numberValidator[key](Number(value), param)
                // @ts-ignore
                if(error) err.push(param)
            }

            return err;
        }, []);

        if (errorDelay) delayedToggleError(errors)
        else toggleError(errors);
    }


    const handleChange = (e: ChangeEvent<HTMLInputElement> | Event) => {
        if(!touched.current) {
            touched.current = true
        }
        // @ts-ignore
        const { value } = e.target;

        if (!isNumberType) validate(value);
        else validateNumber(value)

        if(onChange) onChange(e);
    }

    const handleKeyDown = (e: KeyboardEvent) => {
        const { onKeyDown } = otherProps;

        if (onKeyDown) onKeyDown(e);

        if (!isNumberType) return;

        const regexp = /[0-9]/;
        const isBackspace = e.key === 'Backspace';
        const isNotNumber = !regexp.test(e.key) && !isBackspace;

        if (isNotNumber) e.preventDefault();
    };

    useEffect(() => {
        if (touched.current) {
            const { value = "" } = otherProps;

            if (!isNumberType) validate(value);
            else validateNumber(value)
        }

    }, [toggleValidationListener])

    const isErrorMessageExist = errors[0] && errors[0].errorMessage;

    return (
        <div className={`${validatedInputWrapper} ${rootValidatedInputClass}`}>
            <Input
                autoFocus={autoFocus}
                onBlur={handleBlur}
                onKeyDown={handleKeyDown}
                onChange={handleChange}
                error={Boolean(errors[0])}
                {...(isNumberType ? { pattern: "[0-9]*" }: {})}
                {...otherProps}
            />
            <Typography
                className={`${errorMessageClass} ${isErrorMessageExist ? '' : hidden}`}
                variant={TypographyVariants.label}>
                {errors[0] ? errors[0].errorMessage : '1'}
            </Typography>
        </div>
    )
}
