'use client';
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ import isDeepEqual from 'deep-equal';
import { useRouter } from 'next/navigation.js';
import { serialize } from 'object-to-formdata';
import { wait } from 'payload/utilities';
import QueryString from 'qs';
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react';
import { toast } from 'react-toastify';
import { useDebouncedEffect } from '../../hooks/useDebouncedEffect.js';
import useThrottledEffect from '../../hooks/useThrottledEffect.js';
import { useAuth } from '../../providers/Auth/index.js';
import { useConfig } from '../../providers/Config/index.js';
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js';
import { useFormQueryParams } from '../../providers/FormQueryParams/index.js';
import { useLocale } from '../../providers/Locale/index.js';
import { useOperation } from '../../providers/OperationProvider/index.js';
import { useTranslation } from '../../providers/Translation/index.js';
import { requests } from '../../utilities/api.js';
import { getFormState } from '../../utilities/getFormState.js';
import { FormContext, FormFieldsContext, FormWatchContext, ModifiedContext, ProcessingContext, SubmittedContext } from './context.js';
import errorMessages from './errorMessages.js';
import { fieldReducer } from './fieldReducer.js';
import getDataByPathFunc from './getDataByPath.js';
import getSiblingDataFunc from './getSiblingData.js';
import initContextState from './initContextState.js';
import { mergeServerFormState } from './mergeServerFormState.js';
import reduceFieldsToValues from './reduceFieldsToValues.js';
const baseClass = 'form';
const Form = (props)=>{
    const { id, collectionSlug, getDocPreferences, globalSlug } = useDocumentInfo();
    const { action, children, className, disableSuccessStatus, disabled, // fields: fieldsFromProps = collection?.fields || global?.fields,
    beforeSubmit, handleResponse, initialState, onChange, onSubmit, onSuccess, redirect, submitted: submittedFromProps, uuid, waitForAutocomplete } = props;
    const method = 'method' in props ? props.method : undefined;
    const router = useRouter();
    const { code: locale } = useLocale();
    const { i18n, t } = useTranslation();
    const { refreshCookie, user } = useAuth();
    const operation = useOperation();
    const { formQueryParams } = useFormQueryParams();
    const config = useConfig();
    const { routes: { api: apiRoute }, serverURL } = config;
    const [modified, setModified] = useState(false);
    const [processing, setProcessing] = useState(false);
    const [submitted, setSubmitted] = useState(false);
    const formRef = useRef(null);
    const contextRef = useRef({});
    const fieldsReducer = useReducer(fieldReducer, {}, ()=>initialState);
    /**
   * `fields` is the current, up-to-date state/data of all fields in the form. It can be modified by using dispatchFields,
   * which calls the fieldReducer, which then updates the state.
   */ const [fields, dispatchFields] = fieldsReducer;
    contextRef.current.fields = fields;
    const validateForm = useCallback(async ()=>{
        const validatedFieldState = {};
        let isValid = true;
        const dataFromContext = contextRef.current.getData();
        const data = dataFromContext;
        const validationPromises = Object.entries(contextRef.current.fields).map(async ([path, field])=>{
            const validatedField = {
                ...field,
                valid: true
            };
            if (field.passesCondition !== false) {
                let validationResult = true;
                if (typeof field.validate === 'function') {
                    let valueToValidate = field.value;
                    if (field?.rows && Array.isArray(field.rows)) {
                        valueToValidate = contextRef.current.getDataByPath(path);
                    }
                    validationResult = await field.validate(valueToValidate, {
                        id,
                        config,
                        data,
                        operation,
                        siblingData: contextRef.current.getSiblingData(path),
                        t,
                        user
                    });
                }
                if (typeof validationResult === 'string') {
                    validatedField.errorMessage = validationResult;
                    validatedField.valid = false;
                    isValid = false;
                }
            }
            validatedFieldState[path] = validatedField;
        });
        await Promise.all(validationPromises);
        if (!isDeepEqual(contextRef.current.fields, validatedFieldState)) {
            dispatchFields({
                type: 'REPLACE_STATE',
                state: validatedFieldState
            });
        }
        return isValid;
    }, [
        id,
        user,
        operation,
        t,
        dispatchFields,
        config
    ]);
    const submit = useCallback(async (options = {}, e)=>{
        const { action: actionArg, method: methodToUse = method, overrides = {}, skipValidation } = options;
        if (disabled) {
            if (e) {
                e.preventDefault();
            }
            return;
        }
        if (e) {
            e.stopPropagation();
            e.preventDefault();
        }
        setProcessing(true);
        setSubmitted(true);
        if (waitForAutocomplete) await wait(100);
        // Execute server side validations
        if (Array.isArray(beforeSubmit)) {
            let revalidatedFormState;
            await beforeSubmit.reduce(async (priorOnChange, beforeSubmitFn)=>{
                await priorOnChange;
                const result = await beforeSubmitFn({
                    formState: fields
                });
                revalidatedFormState = result;
            }, Promise.resolve());
            const isValid = Object.entries(revalidatedFormState).every(([, field])=>field.valid);
            if (!isValid) {
                setProcessing(false);
                return dispatchFields({
                    type: 'REPLACE_STATE',
                    state: revalidatedFormState
                });
            }
        }
        const isValid = skipValidation ? true : await contextRef.current.validateForm();
        // If not valid, prevent submission
        if (!isValid) {
            toast.error(t('error:correctInvalidFields'));
            setProcessing(false);
            return;
        }
        // If submit handler comes through via props, run that
        if (onSubmit) {
            const data = {
                ...reduceFieldsToValues(fields, true),
                ...overrides
            };
            onSubmit(fields, data);
        }
        const formData = contextRef.current.createFormData(overrides);
        try {
            let res;
            const actionEndpoint = actionArg || (typeof action === 'string' ? `${action}${QueryString.stringify(formQueryParams, {
                addQueryPrefix: true
            })}` : null);
            if (actionEndpoint) {
                res = await requests[methodToUse.toLowerCase()](actionEndpoint, {
                    body: formData,
                    headers: {
                        'Accept-Language': i18n.language
                    }
                });
            } else if (typeof action === 'function') {
                res = await action(formData);
            }
            setModified(false);
            if (typeof handleResponse === 'function') {
                handleResponse(res);
                return;
            }
            setProcessing(false);
            const contentType = res.headers.get('content-type');
            const isJSON = contentType && contentType.indexOf('application/json') !== -1;
            let json = {};
            if (isJSON) json = await res.json();
            if (res.status < 400) {
                setSubmitted(false);
                if (typeof onSuccess === 'function') onSuccess(json);
                if (redirect) {
                    router.push(redirect);
                } else if (!disableSuccessStatus) {
                    toast.success(json.message || t('general:submissionSuccessful'), {
                        autoClose: 3000
                    });
                }
            } else {
                contextRef.current = {
                    ...contextRef.current
                } // triggers rerender of all components that subscribe to form
                ;
                if (json.message) {
                    toast.error(json.message);
                    return;
                }
                if (Array.isArray(json.errors)) {
                    const [fieldErrors, nonFieldErrors] = json.errors.reduce(([fieldErrs, nonFieldErrs], err)=>{
                        const newFieldErrs = [];
                        const newNonFieldErrs = [];
                        if (err?.message) {
                            newNonFieldErrs.push(err);
                        }
                        if (Array.isArray(err?.data)) {
                            err.data.forEach((dataError)=>{
                                if (dataError?.field) {
                                    newFieldErrs.push(dataError);
                                } else {
                                    newNonFieldErrs.push(dataError);
                                }
                            });
                        }
                        return [
                            [
                                ...fieldErrs,
                                ...newFieldErrs
                            ],
                            [
                                ...nonFieldErrs,
                                ...newNonFieldErrs
                            ]
                        ];
                    }, [
                        [],
                        []
                    ]);
                    fieldErrors.forEach((err)=>{
                        dispatchFields({
                            type: 'UPDATE',
                            ...contextRef.current?.fields?.[err.field] || {},
                            errorMessage: err.message,
                            path: err.field,
                            valid: false
                        });
                    });
                    nonFieldErrors.forEach((err)=>{
                        toast.error(err.message || t('error:unknown'));
                    });
                    return;
                }
                const message = errorMessages?.[res.status] || res?.statusText || t('error:unknown');
                toast.error(message);
            }
        } catch (err) {
            setProcessing(false);
            toast.error(err);
        }
    }, [
        action,
        disableSuccessStatus,
        disabled,
        dispatchFields,
        fields,
        handleResponse,
        method,
        onSubmit,
        onSuccess,
        redirect,
        router,
        t,
        i18n,
        waitForAutocomplete,
        beforeSubmit,
        formQueryParams
    ]);
    const getFields = useCallback(()=>contextRef.current.fields, []);
    const getField = useCallback((path)=>contextRef.current.fields[path], []);
    const getData = useCallback(()=>reduceFieldsToValues(contextRef.current.fields, true), []);
    const getSiblingData = useCallback((path)=>getSiblingDataFunc(contextRef.current.fields, path), []);
    const getDataByPath = useCallback((path)=>getDataByPathFunc(contextRef.current.fields, path), []);
    const createFormData = useCallback((overrides = {})=>{
        const data = reduceFieldsToValues(contextRef.current.fields, true);
        const file = data?.file;
        if (file) {
            delete data.file;
        }
        const dataWithOverrides = {
            ...data,
            ...overrides
        };
        const dataToSerialize = {
            _payload: JSON.stringify(dataWithOverrides),
            file
        };
        // nullAsUndefineds is important to allow uploads and relationship fields to clear themselves
        const formData = serialize(dataToSerialize, {
            indices: true,
            nullsAsUndefineds: false
        });
        return formData;
    }, []);
    const reset = useCallback(async (data)=>{
        const newState = await getFormState({
            apiRoute,
            body: {
                id,
                collectionSlug,
                data,
                globalSlug,
                operation,
                schemaPath: collectionSlug || globalSlug
            },
            serverURL
        });
        contextRef.current = {
            ...initContextState
        };
        setModified(false);
        dispatchFields({
            type: 'REPLACE_STATE',
            state: newState
        });
    }, [
        apiRoute,
        collectionSlug,
        dispatchFields,
        globalSlug,
        id,
        operation,
        serverURL
    ]);
    const replaceState = useCallback((state)=>{
        contextRef.current = {
            ...initContextState
        };
        setModified(false);
        dispatchFields({
            type: 'REPLACE_STATE',
            state
        });
    }, [
        dispatchFields
    ]);
    const getFieldStateByPath = useCallback(async ({ data, path })=>{
        const fieldSchema = await getFormState({
            apiRoute,
            body: {
                collectionSlug,
                data,
                globalSlug,
                /**
            Turns: 'arrayField.0.group123field.arrayField.0.textField'
            Into: 'arrayField.group123field.arrayField.textField'
          */ schemaPath: `${collectionSlug || globalSlug}.${path}`.replace(/\.\d+\./g, '.')
            },
            serverURL
        });
        return fieldSchema;
    }, [
        apiRoute,
        collectionSlug,
        globalSlug,
        serverURL
    ]);
    const addFieldRow = useCallback(async ({ data, path, rowIndex })=>{
        const subFieldState = await getFieldStateByPath({
            data,
            path
        });
        dispatchFields({
            type: 'ADD_ROW',
            blockType: data?.blockType,
            path,
            rowIndex,
            subFieldState
        });
    }, [
        getFieldStateByPath,
        dispatchFields
    ]);
    const removeFieldRow = useCallback(({ path, rowIndex })=>{
        dispatchFields({
            type: 'REMOVE_ROW',
            path,
            rowIndex
        });
    }, [
        dispatchFields
    ]);
    const replaceFieldRow = useCallback(async ({ data, path, rowIndex })=>{
        const subFieldState = await getFieldStateByPath({
            data,
            path
        });
        dispatchFields({
            type: 'REPLACE_ROW',
            blockType: data?.blockType,
            path,
            rowIndex,
            subFieldState
        });
    }, [
        getFieldStateByPath,
        dispatchFields
    ]);
    contextRef.current.submit = submit;
    contextRef.current.getFields = getFields;
    contextRef.current.getField = getField;
    contextRef.current.getData = getData;
    contextRef.current.getSiblingData = getSiblingData;
    contextRef.current.getDataByPath = getDataByPath;
    contextRef.current.validateForm = validateForm;
    contextRef.current.createFormData = createFormData;
    contextRef.current.setModified = setModified;
    contextRef.current.setProcessing = setProcessing;
    contextRef.current.setSubmitted = setSubmitted;
    contextRef.current.disabled = disabled;
    contextRef.current.formRef = formRef;
    contextRef.current.reset = reset;
    contextRef.current.replaceState = replaceState;
    contextRef.current.dispatchFields = dispatchFields;
    contextRef.current.addFieldRow = addFieldRow;
    contextRef.current.removeFieldRow = removeFieldRow;
    contextRef.current.replaceFieldRow = replaceFieldRow;
    contextRef.current.uuid = uuid;
    useEffect(()=>{
        if (typeof submittedFromProps === 'boolean') setSubmitted(submittedFromProps);
    }, [
        submittedFromProps
    ]);
    useEffect(()=>{
        if (initialState) {
            contextRef.current = {
                ...initContextState
            };
            dispatchFields({
                type: 'REPLACE_STATE',
                state: initialState
            });
        }
    }, [
        initialState,
        dispatchFields
    ]);
    useThrottledEffect(()=>{
        refreshCookie();
    }, 15000, [
        fields
    ]);
    useEffect(()=>{
        contextRef.current = {
            ...contextRef.current
        } // triggers rerender of all components that subscribe to form
        ;
        setModified(false);
    }, [
        locale
    ]);
    const classes = [
        className,
        baseClass
    ].filter(Boolean).join(' ');
    useDebouncedEffect(()=>{
        const executeOnChange = async ()=>{
            if (Array.isArray(onChange)) {
                let revalidatedFormState;
                await onChange.reduce(async (priorOnChange, onChangeFn)=>{
                    await priorOnChange;
                    const result = await onChangeFn({
                        formState: fields
                    });
                    revalidatedFormState = result;
                }, Promise.resolve());
                const { changed, newState } = mergeServerFormState(fields, revalidatedFormState);
                if (changed) {
                    dispatchFields({
                        type: 'REPLACE_STATE',
                        optimize: false,
                        state: newState
                    });
                }
            }
        };
        void executeOnChange() // eslint-disable-line @typescript-eslint/no-floating-promises
        ;
    }, 150, [
        fields,
        dispatchFields,
        onChange
    ]);
    const actionString = typeof action === 'string' ? `${action}${QueryString.stringify(formQueryParams, {
        addQueryPrefix: true
    })}` : '';
    return /*#__PURE__*/ React.createElement("form", {
        action: method ? actionString : action,
        className: classes,
        method: method,
        noValidate: true,
        onSubmit: (e)=>contextRef.current.submit({}, e),
        ref: formRef
    }, /*#__PURE__*/ React.createElement(FormContext.Provider, {
        value: contextRef.current
    }, /*#__PURE__*/ React.createElement(FormWatchContext.Provider, {
        value: {
            fields,
            ...contextRef.current
        }
    }, /*#__PURE__*/ React.createElement(SubmittedContext.Provider, {
        value: submitted
    }, /*#__PURE__*/ React.createElement(ProcessingContext.Provider, {
        value: processing
    }, /*#__PURE__*/ React.createElement(ModifiedContext.Provider, {
        value: modified
    }, /*#__PURE__*/ React.createElement(FormFieldsContext.Provider, {
        value: fieldsReducer
    }, children)))))));
};
export default Form;

//# sourceMappingURL=index.js.map