'use client';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { castValue } from '../../lib/value-cast.js';
import { deepEqual } from '../../lib/deep-equal.js';
import { isEmpty } from '../../lib/is-empty.js';
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { FormServerErrorMessage } from '../fragments/form-message/form-server-error-message.js';
import { defaultOnError } from './utils.js';
/**
 * A form component that integrates with react-hook-form for form state management and validation.
 *
 * @example
 * ```tsx
 * // Basic usage
 * <Form onSubmit={handleSubmit}>
 *   <StringField name="email" label="Email" />
 *   <Button type="submit">Submit</Button>
 * </Form>
 *
 * // With form methods access
 * <Form onSubmit={handleSubmit}>
 *   {({ formState: { isSubmitting } }) => (
 *     <>
 *       <StringField name="email" label="Email" />
 *       <Button type="submit" disabled={isSubmitting}>
 *         {isSubmitting ? 'Submitting...' : 'Submit'}
 *       </Button>
 *     </>
 *   )}
 * </Form>
 *
 * // With default values
 * <Form
 *   onSubmit={handleSubmit}
 *   defaultValues={{
 *     email: 'user@example.com'
 *   }}
 * >
 *   <StringField name="email" label="Email" />
 * </Form>
 * ```
 */
const Form = forwardRef(({ children, onSubmit, resetOnSuccessfulSubmit = false, submitChangesOnly = false, defaultValues, renderSubmitErrorsManually = false, submissionErrorMatcher = defaultOnError, className, extraFormProps, }, ref) => {
    const formId = useId();
    const methods = useForm({
        defaultValues,
        criteriaMode: 'all', // display all errors at once
        ...extraFormProps,
    });
    useImperativeHandle(ref, () => methods, [methods]);
    useEffect(() => {
        if (resetOnSuccessfulSubmit && methods.formState.isSubmitSuccessful) {
            methods.reset({ ...(defaultValues ?? {}) });
        }
    }, [resetOnSuccessfulSubmit, methods, methods.formState.isSubmitSuccessful, defaultValues]);
    const wrappedOnSubmit = useCallback(async (rawData) => {
        try {
            let data = rawData;
            // we should make sure that empty fields are submitted as `null`
            // react-hook-form doesn't submit fields with `undefined` values
            // so we need to add them to the data as `null`
            Object.keys(methods.control._fields).forEach((fieldName) => {
                if (!Object.keys(data).includes(fieldName)) {
                    data[fieldName] = null;
                }
            });
            if (submitChangesOnly && !!defaultValues) {
                // remove fields that didn't change it's value
                data = Object.fromEntries(Object.entries(rawData).filter(([fieldName, value]) => !deepEqual(value, defaultValues[fieldName])));
            }
            // at this point all the fields that need to be submitted are in the data
            // we just need to make sure that "empty" values are submitted as `null`
            data = Object.fromEntries(Object.entries(data).map(([fieldName, value]) => [fieldName, isEmpty(value) ? null : value]));
            // exclude or cast data if needed to prevent submitting wrong data type
            const form = document.getElementById(formId);
            Object.keys(data).map((key) => {
                try {
                    const value = data[key];
                    const field = form?.querySelector(`[name="${key}"]`);
                    const fieldToExclude = field?.getAttribute('data-exclude');
                    if (fieldToExclude === 'true') {
                        delete data[key];
                        return;
                    }
                    if (value !== null) {
                        const dataCast = field?.getAttribute('data-cast');
                        if (dataCast) {
                            data[key] = castValue(value, dataCast);
                        }
                    }
                }
                catch (error) {
                    // print out the error but continue the execution
                    console.error(error);
                }
            });
            // we need to return the promise from the onSubmit callback if there is one
            // so react-hook-form can wait for it to resolve and update the form state correctly
            // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
            return await onSubmit(data);
        }
        catch (error) {
            const errors = submissionErrorMatcher(error, Object.keys(methods.control._fields));
            Object.entries(errors).forEach(([key, value]) => {
                methods.setError(key, {
                    type: 'serverError',
                    message: value,
                    types: {
                        serverError: value,
                    },
                });
            });
        }
    }, [methods, submitChangesOnly, defaultValues, formId, onSubmit, submissionErrorMatcher]);
    const triggerSubmit = useCallback(() => {
        // trigger form submit event
        const form = document.getElementById(formId);
        if (form) {
            form.dispatchEvent(new Event('submit', { bubbles: true }));
        }
    }, [formId]);
    return (_jsxs(FormProvider, { ...methods, children: [!renderSubmitErrorsManually && methods.formState.errors.root?.serverError.message && (_jsx("div", { className: "mb-2", children: _jsx(FormServerErrorMessage, {}) })), _jsx("form", { id: formId, onSubmit: methods.handleSubmit(wrappedOnSubmit), className: className, noValidate: true, children: typeof children === 'function'
                    ? children({
                        ...methods,
                        triggerSubmit,
                        formId,
                    })
                    : children })] }));
});
Form.displayName = 'Form';
export { Form };
//# sourceMappingURL=form.js.map