import React from 'react';
import { useApp, useComponents, useValidate } from '../hooks';
import { Formik } from 'formik';
import PropTypes from 'prop-types';

export default function Form({
    action,
    method,
    onSubmit,
    storage,
    backgroundSync,
    outboxId,
    preserve,
    modelConf,
    data = {},
    error,
    children
}) {
    const app = useApp(),
        validate = useValidate(),
        { FormRoot } = useComponents();

    if (backgroundSync === undefined) {
        backgroundSync = app.config.backgroundSync;
    }

    async function handleSubmit(
        values,
        { setSubmitting, setTouched, setErrors }
    ) {
        if (onSubmit) {
            const result = await onSubmit(values);
            if (!result) {
                setSubmitting(false);
                return;
            }
        }

        const has_files = checkForFiles(values);

        const [item, error] = await app.submitForm({
            url: action,
            storage,
            backgroundSync,
            has_files,
            outboxId,
            preserve,
            data: {
                _method: method,
                ...values
            }
        });

        if (error) {
            const errors = parseApiError(item.error, values);
            setErrors(errors);
            setTouched(errors, false);
        }

        setSubmitting(false);
    }

    const errors = parseApiError(error, data);

    return (
        <Formik
            initialValues={data}
            initialErrors={errors}
            initialTouched={errors}
            validate={values => validate(values, modelConf)}
            validateOnBlur={true}
            validateOnChange={false}
            onSubmit={handleSubmit}
            enableReinitialize={true}
        >
            <FormRoot>{children}</FormRoot>
        </Formik>
    );
}

Form.propTypes = {
    action: PropTypes.string,
    method: PropTypes.string,
    onSubmit: PropTypes.func,
    storage: PropTypes.string,
    backgroundSync: PropTypes.bool,
    outboxId: PropTypes.number,
    preserve: PropTypes.arrayOf(PropTypes.string),
    modelConf: PropTypes.object,
    data: PropTypes.object,
    error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
    children: PropTypes.node
};

function parseApiError(error, values) {
    if (!error) {
        return;
    }
    const errors = {};
    if (typeof error === 'string') {
        errors['__other__'] = error;
    } else {
        Object.entries(error).map(([key, error]) => {
            if (!(key in values)) {
                key = '__other__';
            }
            if (Array.isArray(error)) {
                if (typeof error[0] === 'object') {
                    errors[key] = error.map((err, i) =>
                        parseApiError(err, (values[key] || [])[i] || {})
                    );
                    return;
                }
            } else if (typeof error === 'object') {
                errors[key] = parseApiError(error, values[key] || {});
                return;
            } else {
                error = [error];
            }
            if (errors[key]) {
                error = [errors[key], ...error];
            }
            errors[key] = error.join('; ');
        });
    }
    return errors;
}

function checkForFiles(values) {
    if (!values || typeof values !== 'object') {
        return false;
    }
    if (values.name && values.type && values.body) {
        return true;
    }
    if (Array.isArray(values)) {
        return values.some(checkForFiles);
    }
    return Object.values(values).some(checkForFiles);
}
