'use client';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
// packages/ui/src/crud/components/EntityFormRenderer.tsx
/**
 * @fileoverview EntityFormRenderer component
 * @description Dumb renderer that composes form from entity definition.
 * All orchestration logic lives in useEntityForm.
 *
 * @version 0.0.7
 * @since 0.0.1
 * @author AMBROISE PARK Consulting
 */
import { useEffect, useId, useMemo, useState } from 'react';
import { FormProvider } from 'react-hook-form';
import { Badge, Button, DropdownMenu, Grid, Stack, Spinner, } from '@donotdev/components';
import { hasRoleAccess, useTranslation } from '@donotdev/core';
import { isCrudModuleAvailable, DisplayFieldRenderer, FormFieldRenderer, UploadProvider, useEntityForm, useUnsavedChangesWarning, useFormStore, useFieldConditions, } from '../crudImports';
import { useNavigate } from '../../routing';
import { useAuthSafe } from '../../utils/useAuthSafe';
/**
 * EntityFormRenderer - Dumb component that renders a form from entity definition.
 *
 * All orchestration (uploads, validation, status tracking) is handled by useEntityForm.
 * This component just:
 * - Generates formId
 * - Renders fields
 * - Renders submit button with status from useEntityForm
 *
 * @version 0.0.7
 * @since 0.0.1
 * @author AMBROISE PARK Consulting
 */
export function EntityFormRenderer({ entity, onSubmit, t, className = '', submitText, loading = false, defaultValues, submitVariant = 'primary', secondaryButtonText, secondaryButtonVariant = 'outline', onSecondarySubmit, viewerRole: viewerRoleProp, operation = defaultValues ? 'edit' : 'create', formId: externalFormId, cancelText, cancelPath, successPath, onCancel, warnOnUnsavedChanges = true, unsavedChangesMessage, hideVisibilityInfo = false, }) {
    // Safe guard: isCrudModuleAvailable is a module-level constant (immutable after load).
    // Hooks below are either ALL called or NONE — no Rules-of-Hooks violation at runtime.
    // eslint-disable-next-line react-hooks/rules-of-hooks
    if (!isCrudModuleAvailable)
        return null;
    const navigate = useNavigate();
    // Auto-detect role from auth; prop overrides (e.g. View-As preview)
    const authRole = useAuthSafe('userRole');
    const viewerRole = viewerRoleProp ?? authRole;
    // Generate stable form ID
    const generatedFormId = useId();
    const formId = externalFormId ?? `entity-form-${entity.name}-${generatedFormId}`;
    // Entity + crud namespaces so translateLabel can resolve crud:status.* etc.
    const { t: translationFn } = useTranslation([entity.namespace, 'crud']);
    const { t: tCrud } = useTranslation('crud');
    const translate = t || translationFn;
    // Visibility badges (developer-facing) are for admins/super only
    const isAdminOrSuper = viewerRole === 'admin' || viewerRole === 'super';
    const showVisibilityBadges = !hideVisibilityInfo && isAdminOrSuper;
    // Preview dropdown (View As) is available for user/admin/super
    const canPreviewRole = viewerRole === 'user' || viewerRole === 'admin' || viewerRole === 'super';
    // Preview role state for View As toggle
    const [previewRole, setPreviewRole] = useState(viewerRole === 'admin' || viewerRole === 'super' || viewerRole === 'user'
        ? viewerRole
        : 'guest');
    // Visibility → Badge variant mapping (inline, no separate file needed)
    const visibilityBadgeVariant = {
        guest: 'muted',
        user: 'secondary',
        admin: 'warning',
        super: 'destructive',
        technical: 'accent',
        owner: 'secondary',
    };
    // Role hierarchy for filtering (higher index = more access)
    const roleHierarchy = ['guest', 'user', 'admin', 'super'];
    // Role display labels (hardcoded English - universal technical terms)
    const roleLabels = {
        guest: 'Guest',
        user: 'User',
        admin: 'Admin',
        super: 'Super',
        technical: 'System',
    };
    // Preview dropdown menu items (roles current viewer can assume)
    const previewMenuItems = useMemo(() => roleHierarchy
        .filter((role) => hasRoleAccess(viewerRole, role))
        .map((role) => ({
        label: roleLabels[role],
        checked: previewRole === role,
        onClick: () => setPreviewRole(role),
    })), [previewRole, viewerRole]);
    // useEntityForm handles all orchestration
    const form = useEntityForm(entity, {
        formId,
        operation,
        defaultValues,
        viewerRole,
        t: translate,
    });
    const { control, handleSubmit, formState: { errors }, fields: rawFields, formStatus, uploadProgress, cleanup, isDirty, hasUserInteracted, resetForm, } = form;
    const isDirtyForBlocking = isDirty && hasUserInteracted;
    // Filter fields based on previewRole when visibility info is shown
    const renderableFields = useMemo(() => {
        if (!canPreviewRole)
            return rawFields;
        const previewRoleIndex = roleHierarchy.indexOf(previewRole);
        return rawFields.filter(({ config }) => {
            const fieldVisibility = config.visibility;
            // 'hidden' fields are never shown, 'technical' always shown as read-only
            if (fieldVisibility === 'hidden')
                return false;
            if (fieldVisibility === 'technical')
                return true;
            const fieldRoleIndex = roleHierarchy.indexOf(fieldVisibility);
            return fieldRoleIndex <= previewRoleIndex;
        });
    }, [rawFields, canPreviewRole, previewRole]);
    // Evaluate field conditions (visible/disabled/required/readonly) against live form values
    const fieldConditions = useFieldConditions(renderableFields, control);
    // Sync isDirty to FormStore (single source of truth)
    // Use getState() to avoid subscribing to the action reference (Zustand anti-pattern)
    useEffect(() => {
        if (formId) {
            useFormStore.getState().setIsDirty(formId, isDirtyForBlocking);
        }
    }, [formId, isDirtyForBlocking]);
    // Cleanup on unmount
    useEffect(() => {
        return cleanup;
    }, [cleanup]);
    // Compute i18n messages once (DRY, performance)
    const unsavedChangesLeaveMessage = useMemo(() => unsavedChangesMessage ||
        tCrud('messages.unsavedChangesLeave', {
            defaultValue: 'You have unsaved changes. Are you sure you want to leave?',
        }), [unsavedChangesMessage, tCrud]);
    // Warn about unsaved changes when navigating away (browser refresh/close)
    // beforeunload is the only place we can't toast — browser requires native dialog
    useUnsavedChangesWarning({
        isDirty: isDirtyForBlocking,
        enabled: warnOnUnsavedChanges,
        message: unsavedChangesLeaveMessage,
    });
    // Handle cancel — auto-save ensures draft is persisted, no confirm needed
    const handleCancel = () => {
        resetForm();
        if (onCancel) {
            onCancel();
        }
        else if (cancelPath) {
            navigate(cancelPath);
        }
        else {
            navigate('back');
        }
    };
    // Wrap onSubmit to await the result before navigating
    // handleSubmit from react-hook-form expects: (data: EntityData) => void | Promise<void>
    // Our onSubmit prop is (data: T) => void | Promise<void>, where T extends EntityRecord
    // EntityData should be compatible with T, so we can safely pass it
    const handleFormSubmit = async (data) => {
        try {
            // Await onSubmit so navigation only happens after successful submission
            // Type assertion is safe: EntityData is the validated form data, T is EntityRecord
            await onSubmit(data);
            // Navigate only after successful submission
            if (successPath) {
                navigate(successPath);
            }
            else {
                // Default: navigate back (user came from list, go back there)
                navigate('back');
            }
        }
        catch (error) {
            // onSubmit rejected — stay on form so user can correct errors
            // Error display is handled by the caller's onSubmit implementation
            console.error(error);
        }
    };
    // Determine if cancel button should show
    // Show by default, hide only if cancelText is explicitly null
    const showCancelButton = cancelText !== null;
    const cancelButtonText = cancelText ?? tCrud('form.cancel', { defaultValue: 'Cancel' });
    // Common button container style (sticky footer)
    const buttonContainerStyle = {
        gridColumn: '1 / -1',
        position: 'sticky',
        bottom: 0,
        backgroundColor: 'var(--background)',
        borderTop: '1px solid var(--border)',
        paddingTop: 'var(--gap-md)',
        paddingBottom: 'var(--gap-md)',
        zIndex: 10,
        marginTop: 'var(--gap-lg)',
    };
    // Auto-detect loading for edit forms waiting for data
    const isLoading = loading || (operation === 'edit' && !defaultValues);
    // Compute button state from form status
    const buttonState = useMemo(() => {
        if (formStatus === 'uploading') {
            return {
                loading: true,
                loadingText: uploadProgress < 100
                    ? tCrud('form.uploading', {
                        progress: Math.round(uploadProgress),
                        defaultValue: `Uploading ${Math.round(uploadProgress)}%...`,
                    })
                    : tCrud('form.processing', { defaultValue: 'Processing...' }),
                progress: uploadProgress,
            };
        }
        if (formStatus === 'validating') {
            return {
                loading: true,
                loadingText: tCrud('form.validating', {
                    defaultValue: 'Validating...',
                }),
            };
        }
        if (formStatus === 'submitting') {
            return {
                loading: true,
                loadingText: tCrud('form.saving', { defaultValue: 'Saving...' }),
            };
        }
        return { loading: false };
    }, [formStatus, uploadProgress, tCrud]);
    return (_jsx(FormProvider, { ...form, children: _jsx(UploadProvider, { formId: formId, children: _jsxs("div", { style: {
                    position: 'relative',
                    width: '100%',
                    gridColumn: '1 / -1',
                    display: 'contents',
                }, children: [isLoading && _jsx(Spinner, { overlay: true }), _jsxs("form", { onSubmit: handleSubmit(handleFormSubmit), noValidate: true, className: className, style: { width: '100%', gridColumn: '1 / -1', display: 'contents' }, children: [renderableFields.map(({ name, config, editable }) => {
                                // Evaluate dynamic conditions for this field
                                const cond = fieldConditions[name];
                                if (cond && !cond.visible)
                                    return null;
                                // Apply condition overrides to field config
                                const effectiveConfig = cond
                                    ? {
                                        ...config,
                                        // Merge dynamic required into validation (condition overrides static)
                                        ...(cond.required && config.validation
                                            ? { validation: { ...config.validation, required: true } }
                                            : cond.required
                                                ? { validation: { required: true } }
                                                : {}),
                                    }
                                    : config;
                                // Condition-based disabled/readonly override editability
                                const effectiveEditable = editable && !cond?.disabled && !cond?.readonly;
                                // Show visibility badge for non-guest fields when visibility badges are enabled (admins/super only)
                                const showBadge = showVisibilityBadges && config.visibility !== 'guest';
                                const badgeVariant = config.visibility !== 'hidden'
                                    ? visibilityBadgeVariant[config.visibility]
                                    : undefined;
                                const badgeLabel = roleLabels[config.visibility] ??
                                    config.visibility;
                                // Wrap field with visibility badge when enabled
                                const fieldElement = !effectiveEditable ? (_jsx(DisplayFieldRenderer, { name: name, config: effectiveConfig, value: defaultValues?.[name], t: translate }, name)) : (_jsx(FormFieldRenderer, { name: name, config: effectiveConfig, control: control, errors: errors, t: translate }, name));
                                // If no badge needed, return field directly
                                if (!showBadge)
                                    return fieldElement;
                                // Wrap field with badge indicator
                                return (_jsxs("div", { style: { position: 'relative' }, children: [_jsx(Badge, { as: "span", variant: badgeVariant, style: {
                                                position: 'absolute',
                                                top: 0,
                                                insetInlineEnd: 0,
                                                fontSize: 'var(--font-size-xs)',
                                                zIndex: 1,
                                            }, children: badgeLabel }), fieldElement] }, name));
                            }), (() => {
                                // Define all possible buttons (conditionally included)
                                const buttons = [];
                                // Cancel button
                                if (showCancelButton && cancelButtonText) {
                                    buttons.push(_jsx(Button, { type: "button", onClick: handleCancel, disabled: buttonState.loading, variant: "outline", className: "dndev-w-full", children: cancelButtonText }, "cancel"));
                                }
                                // Preview dropdown (user/admin/super only)
                                if (canPreviewRole) {
                                    const previewVariant = visibilityBadgeVariant[previewRole] || 'muted';
                                    buttons.push(_jsx(DropdownMenu, { trigger: _jsxs(Button, { type: "button", variant: previewVariant, disabled: buttonState.loading, className: "dndev-w-full", children: [tCrud('visibility.preview', {
                                                    defaultValue: 'Preview',
                                                }), ": ", roleLabels[previewRole]] }), items: previewMenuItems }, "preview"));
                                }
                                // Secondary submit button
                                if (secondaryButtonText) {
                                    buttons.push(_jsx(Button, { type: "button", onClick: () => {
                                            if (onSecondarySubmit) {
                                                const currentValues = form.getValues();
                                                onSecondarySubmit(currentValues);
                                            }
                                        }, loading: buttonState.loading, loadingText: buttonState.loadingText, progress: buttonState.progress, variant: secondaryButtonVariant, className: "dndev-w-full", children: secondaryButtonText }, "secondary"));
                                }
                                // Primary submit button (always shown)
                                buttons.push(_jsx(Button, { type: "submit", loading: buttonState.loading, loadingText: buttonState.loadingText, progress: buttonState.progress, variant: submitVariant, className: "dndev-w-full", children: submitText ||
                                        tCrud('form.submit', { defaultValue: 'Submit' }) }, "submit"));
                                const buttonCount = buttons.length;
                                // Single button: use Stack
                                if (buttonCount === 1) {
                                    return (_jsx(Stack, { direction: "column", gap: "tight", style: buttonContainerStyle, children: buttons[0] }));
                                }
                                // Multiple buttons: responsive stacked on mobile, proportional on tablet+
                                const colTemplate = buttonCount === 2
                                    ? '1fr 2fr' // Cancel/Preview + Submit
                                    : buttonCount === 3
                                        ? '1fr 1fr 2fr' // Cancel + Preview + Submit
                                        : '1fr 1fr 1fr 2fr'; // Cancel + Preview + Secondary + Submit
                                return (_jsx(Grid, { cols: ['1fr', colTemplate, colTemplate, colTemplate], gap: "tight", style: buttonContainerStyle, children: buttons }));
                            })()] })] }) }) }));
}
export default EntityFormRenderer;
