import React, { useState, Children, cloneElement, useRef, forwardRef, useImperativeHandle, useEffect, createContext, useContext } from 'react';
import { isForwardRef, isElement } from 'react-is'
import cx from 'classnames'
import { CompoundedComponent, DEFAULT_VALUES, FormContextProps, FormItemsProps, FormProps, footerProps, headerProps, bodyProps } from './types';
import { Label } from '../Label';
import { Required } from '../Required';
import { AssistiveText } from '../AssistiveText';
import moment from 'moment';

export const FormContext = createContext<FormContextProps>(DEFAULT_VALUES);

export const Form = forwardRef<any, FormProps>(({ children, onSubmit, onValidSubmit, onChangeValues, loading, className }, ref) => {

    const [values, setValues] = useState({});
    const [invalidValues, setInvalidValues] = useState({});
    const [submitAttempted, setSubmitAttempted] = useState(false);

    useImperativeHandle(ref, () => ({
        submit() {
            submit()
        },
        setValues(_values: {}) {
            setValues({ ...values, ..._values })
        },
        getValues() {
            return values;
        },
        resetValues() {
            setValues({});
            setInvalidValues({});
        },
        setInvalidValues(_values: {}) {
            setInvalidValues(_values);
        }
    }))

    const [childrenWithProps, refs] = addReferenceElements(children, useRef([]))

    const submit = () => {
        setSubmitAttempted(true)
        setInvalidValues([])
        refs?.current?.forEach((x: { validate: () => void }) => x.validate && x.validate())

        onSubmit && onSubmit(values, invalidValues)

        if (Object.keys(invalidValues).length == 0)
            onValidSubmit && onValidSubmit(values)
    }

    return (
        <FormContext.Provider value={{
            values, setValues: (_values) => {
                setValues(_values),
                    onChangeValues && onChangeValues(_values)
            }, invalidValues, setInvalidValues, submitAttempted, loading
        }}>
            <div className={cx("basic-form", className)}>
                {
                    loading &&
                    <div className="preloader">
                        <div className="sk-three-bounce">
                            <div className="sk-child sk-bounce1"></div>
                            <div className="sk-child sk-bounce2"></div>
                            <div className="sk-child sk-bounce3"></div>
                        </div>
                    </div>
                }
                {/* <form> */}
                {childrenWithProps}
            </div>
        </FormContext.Provider>
    )
}) as CompoundedComponent;

const addReferenceElements = (children: JSX.Element | JSX.Element[], refs: any) => {

    const childrenWithProps = Children.map(children, (child, index) => {
        let isPossibleOurElement = isElement(child) && isForwardRef(child) && child.props?.name != undefined
        let props: any = {}

        if (isPossibleOurElement) {
            props.ref = (element: []) => { element && refs.current.push(element) }
            return cloneElement(child, props);
        }

        if (!isPossibleOurElement && child?.props?.children) {
            const [_childrenWithProps, _refs] = addReferenceElements(child?.props?.children, refs)
            props.children = _childrenWithProps
            refs = _refs
            return cloneElement(child, props);
        }

        return child

    });

    return [childrenWithProps, refs]

}

Form.Item = forwardRef(({ children, name, label, rules, className }: FormItemsProps, ref) => {

    const { values, setValues, invalidValues, setInvalidValues, loading }: FormContextProps = useContext(FormContext);
    const [errorMessage, setErrorMessage] = useState<string>()
    const value = values[name]

    useImperativeHandle(ref, () => ({
        validate() {
            isValid(value)
        }
    }))

    useEffect(() => {
        if (!value) {
            values[name] = null
            setValues({ ...values })
        }
    }, [value])

    let requiredValidation = rules?.find((r: any) => r?.required);
    let regexValidation = rules?.find((x: any) => x.regex)
    let functionValidation: any = rules?.find((x: any) => typeof (x) == "function");
    let minValidation: any = rules?.find((r: any) => r.min != undefined);
    let maxValidation: any = rules?.find((r: any) => r.max != undefined);
    let minDateValidation: any = rules?.find((r: any) => r.minDate != undefined);
    let maxDateValidation: any = rules?.find((r: any) => r.maxDate != undefined);
    let emailValidation: any = rules?.find((r: any) => r.email != undefined);
    let phoneValidation: any = rules?.find((r: any) => r.phone != undefined);
    let cellphoneValidation: any = rules?.find((r: any) => r.cellphone != undefined);

    const isValid = (newValue: any) => {
        let result: boolean = true
        let newErrorMessage

        //required
        if (requiredValidation && (!newValue || newValue == undefined || newValue?.length < 1)) {
            result = false
            newErrorMessage = requiredValidation?.message || 'Required Field'
        }

        //regex validation
        if (!newErrorMessage && regexValidation && !regexValidation.regex?.test(newValue.toString())) {
            result = false
            newErrorMessage = regexValidation.message || 'Invalid Field'
        }

        //function validation
        if (!newErrorMessage && functionValidation) {
            newErrorMessage = functionValidation(values, newValue)
            if (newErrorMessage) result = false
        }

        //min validation
        if (!newErrorMessage && minValidation && (newValue && (isNaN(newValue) || parseFloat(newValue) < parseFloat(minValidation?.min)))) {
            result = false;
            newErrorMessage = minValidation?.message || `Minimun value must be ${minValidation?.min}`;
        }

        //max validation
        if (!newErrorMessage && maxValidation && (newValue && (isNaN(newValue) || parseFloat(newValue) > parseFloat(maxValidation?.max)))) {
            result = false;
            newErrorMessage = maxValidation?.message || `Maximun value must be ${maxValidation?.max}`;
        }

        //minDate validation
        if (!newErrorMessage && minDateValidation && newValue) {
            const dateValue = moment(newValue);
            const minDate = moment(minDateValidation?.minDate);

            if (!dateValue?.isValid() || dateValue < minDate) {
                result = false;
                newErrorMessage = minDateValidation?.message || `Minimun value must be ${minDateValidation?.minDate?.toString()}`;
            }
        }

        //maxDate validation
        if (!newErrorMessage && maxDateValidation && newValue) {
            const dateValue = moment(newValue);
            const maxDate = moment(maxDateValidation?.maxDate);

            if (!dateValue?.isValid() || dateValue > maxDate) {
                result = false;
                newErrorMessage = maxDateValidation?.message || `Maximun value must be ${maxDateValidation?.maxDate?.toString()}`;
            }
        }

        //email validation
        if (!newErrorMessage && emailValidation && (newValue && !/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(newValue))) {
            result = false;
            newErrorMessage = emailValidation?.message || `Invalid email`;
        }

        //phone validation
        if (!newErrorMessage && phoneValidation && (newValue && !/^\(?(\d{3})\)?[- ]?(\d{3})[- ]?(\d{4})$/.test(newValue))) {
            result = false;
            newErrorMessage = phoneValidation?.message || `Invalid phone number`;
        }

        //cellphone validation
        if (!newErrorMessage && cellphoneValidation && (newValue && !/^[+]{1}(?:[0-9\-\(\)\/\.]\s?){6, 15}[0-9]{1}$/.test(newValue))) {
            result = false;
            newErrorMessage = cellphoneValidation?.message || `Invalid phone number`;
        }

        setErrorMessage(newErrorMessage)

        if (result) delete invalidValues[name]
        else invalidValues[name] = newValue

        setInvalidValues(invalidValues)
    }

    const onChange = (_value: []) => {
        values[name] = _value
        isValid(_value)
        setValues({
            ...values
        })
    }

    const childrenWithProps = Children.map(children, (child) => {
        return cloneElement(child, {
            value: value ? value : "",
            checked: value ? true : false,
            name,
            onChange: (_value: []) => {
                if (child?.props?.onChange) child?.props?.onChange(_value);
                onChange(_value);
            },
            className: errorMessage ? 'is-invalid' :
                child?.props?.className,
            loading
        });
    });


    return (
        <div
            style={{
                display: "flex",
                flexDirection: "column",
                alignItems: "flex-start",
                marginBottom: '16px'
            }}
            className={cx("forItem mb-3 col", className)}>
            <div style={{ marginBottom: 5 }}>
                {requiredValidation && <Required />}
                {label && <Label>{label}</Label>}
            </div>
            {childrenWithProps}
            {errorMessage && <AssistiveText style={{ marginTop: 5 }} warning={true} focused={false}>{errorMessage}</AssistiveText>}
        </div>
    )
})

Form.useForm = () => {
    let ref: any = useRef()

    return {
        ref: ref,
        setValues: (_values: any) => { ref?.current?.setValues(_values) },
        getValues: () => { ref?.current?.getValues() },
        resetValues: () => { ref?.current?.resetValues() },
        submit: () => { ref?.current?.submit() },
        setInvalidValues: () => { ref?.current?.setInvalidValues() }
    }
}