import React__default, { useState, useEffect, useRef, useImperativeHandle, useId } from 'react';
import { useShowClear } from '@jobber/hooks';
import { AnimatePresence, motion } from 'framer-motion';
import { tokens } from '@jobber/design';
import classnames from 'classnames';
import { B as Button } from './Button-es.js';
import { I as Icon } from './Icon-es.js';
import { T as Text } from './Text-es.js';
import { u as useFormFieldFocus } from './useFormFieldFocus-es.js';
import { I as InputValidation } from './InputValidation-es.js';
import { S as Spinner } from './Spinner-es.js';
import { _ as __rest } from './tslib.es6-es.js';
import { useFormContext, useForm, useController } from 'react-hook-form';

var formFieldStyles = {"container":"YL-mNv-Bl6g-","wrapper":"_8lhbGTQ-hhg-","disabled":"Tz9LK9ABKMk-","horizontalWrapper":"b5mv1x1H7YE-","textarea":"hGr6YW4AeLM-","safari":"QBCWi9GBgMs-","timeInputLabel":"_0pmqVa2zSE4-","miniLabel":"F1t76G6bDKo-","large":"_9tjyT9QUtP8-","text":"QmMiyoAWp-g-","invalid":"XWDSfe6weSY-","small":"Sw5O4I0lMJg-","inline":"SaORbL7SYWY-","center":"ozy2UoT2vsg-","right":"_3TJdT91YD3c-","maxLength":"W6GrMqLy2qk-","inputWrapper":"-LmjnYRU0r0-","childrenWrapper":"yVXYv6hkuOA-","input":"vtSDcuzNr9Q-","emptyPhoneNumber":"MVhuQuzUBUs-","label":"Dgk00tzlODA-","select":"NwQGiWBWIsc-","externalLabel":"Qb8zQ8n-8vc-","postfix":"yTDzs9h1otI-","affixIcon":"m0YpdssD2dY-","suffix":"_-3mMnjSh6ok-","affixLabel":"-Wzcb0pBh5I-","description":"DHX5ijY3xIw-","toolbar":"AL-2brNI7dk-","spinning":"_8Rzv7CcDW80-"};

/**
 * @internal Reach out to UX Foundations if using this component since it is possible it might change
 */
function AffixLabel({ label, variation = "prefix", labelRef, }) {
    const affixLabelClass = classnames(formFieldStyles.affixLabel, {
        [formFieldStyles.suffix]: variation === "suffix",
    });
    if (!label)
        return null;
    return (React__default.createElement("div", { ref: labelRef, className: affixLabelClass }, label));
}
function AffixIcon({ icon, onClick, ariaLabel, variation = "prefix", size, }) {
    const affixIconClass = classnames(formFieldStyles.affixIcon, {
        [formFieldStyles.suffix]: variation === "suffix",
    });
    const iconSize = size === "small" ? "small" : "base";
    if (!icon)
        return null;
    return (React__default.createElement("div", { className: affixIconClass }, onClick ? (React__default.createElement(Button
    /**
     * We can cast the ariaLabel here as a `Suffix`
     * requires an ariaLabel if there is an action
     */
    , { 
        /**
         * We can cast the ariaLabel here as a `Suffix`
         * requires an ariaLabel if there is an action
         */
        ariaLabel: ariaLabel, icon: icon, onClick: onClick, variation: "subtle", type: "tertiary", size: iconSize })) : (React__default.createElement(Icon, { name: icon, size: iconSize, color: "greyBlue" }))));
}

function FormFieldDescription({ id, description, visible = true, }) {
    if (!visible)
        return null;
    const useStringFormat = !description || typeof description === "string";
    return (React__default.createElement("div", { id: id, className: formFieldStyles.description }, useStringFormat ? (React__default.createElement(Text, { size: "small", variation: "subdued" }, description)) : (description)));
}

var styles = {"clearInput":"YmRTd-KeXv4-","spinning":"B25z9B8I3gs-"};

function ClearAction({ onClick, visible, }) {
    if (!visible)
        return null;
    return (React__default.createElement("button", { className: styles.clearInput, onClick: onClick, type: "button", "aria-label": "Clear input", "data-testid": "ATL-FormField-clearButton" },
        React__default.createElement(Icon, { name: "remove", size: "small" })));
}

function useToolbar(props) {
    const isToolbarVisible = props.toolbar !== undefined &&
        (props.toolbarVisibility === "always" || props.focused);
    const toolbarAnimationEnd = { opacity: 0, height: 0 };
    const toolbarAnimationStart = { opacity: 1, height: "auto" };
    return {
        isToolbarVisible,
        toolbarAnimationEnd,
        toolbarAnimationStart,
    };
}

function useIsSafari() {
    return (globalThis === null || globalThis === void 0 ? void 0 : globalThis.navigator)
        ? /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
        : false;
}

/**
 * Hook for getting the correct styles for the FormField and its children
 */
function useFormFieldWrapperStyles({ size, align, placeholder, value, invalid, error, max, prefixRef, suffixRef, maxLength, type, disabled, inline, showMiniLabel = true, }) {
    const isSafari = useIsSafari();
    const wrapperClasses = classnames(formFieldStyles.wrapper, size && formFieldStyles[size], align && formFieldStyles[align], {
        [formFieldStyles.miniLabel]: (showMiniLabel && placeholder && value !== "") ||
            (placeholder && type === "select") ||
            // Naively assume that if the the type is tel, it is the InputPhoneNumber
            (placeholder && type === "tel"),
        [formFieldStyles.text]: type === "textarea" || type === "text",
        [formFieldStyles.textarea]: type === "textarea",
        [formFieldStyles.safari]: isSafari && type === "textarea",
        [formFieldStyles.select]: type === "select",
        [formFieldStyles.invalid]: invalid !== null && invalid !== void 0 ? invalid : error,
        [formFieldStyles.disabled]: disabled,
        [formFieldStyles.maxLength]: maxLength,
        [formFieldStyles.timeInputLabel]: placeholder && type === "time" && placeholder && value === "",
    });
    const containerClasses = classnames(formFieldStyles.container, {
        [formFieldStyles.inline]: inline,
    });
    const wrapperInlineStyle = {
        ["--formField-maxLength"]: maxLength || max,
    };
    const [labelStyle, setLabelStyle] = useState({
        paddingLeft: undefined,
        paddingRight: undefined,
    });
    useEffect(() => {
        var _a, _b;
        setLabelStyle(getAffixPaddding({
            value,
            type,
            prefixWidth: ((_a = prefixRef === null || prefixRef === void 0 ? void 0 : prefixRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0,
            suffixWidth: ((_b = suffixRef === null || suffixRef === void 0 ? void 0 : suffixRef.current) === null || _b === void 0 ? void 0 : _b.offsetWidth) || 0,
        }));
    }, [value]);
    return {
        inputStyle: formFieldStyles.input,
        wrapperClasses,
        containerClasses,
        wrapperInlineStyle,
        labelStyle,
        setLabelStyle,
    };
}
function getAffixPaddding({ value, type, prefixWidth, suffixWidth, }) {
    const hasValue = value !== "";
    const newPadding = {
        paddingLeft: undefined,
        paddingRight: undefined,
    };
    // Naively assume that if the the type is tel, it is the InputPhoneNumber
    if (type === "tel")
        return newPadding;
    if (prefixWidth && !hasValue) {
        newPadding.paddingLeft = offset(prefixWidth);
    }
    if (suffixWidth && !hasValue) {
        newPadding.paddingRight = offset(suffixWidth);
    }
    function offset(width) {
        return `calc(${width}px + var(--space-smallest)`;
    }
    return newPadding;
}

function FormFieldWrapper({ align, description, descriptionIdentifier, placeholder, value, children, invalid, error, size, prefix, suffix, max, maxLength, type, disabled, inline, identifier, clearable, onClear, readonly, toolbar, toolbarVisibility = "while-editing", showMiniLabel = true, wrapperRef, }) {
    const prefixRef = useRef(null);
    const suffixRef = useRef(null);
    const { wrapperClasses, containerClasses, wrapperInlineStyle, labelStyle } = useFormFieldWrapperStyles({
        align,
        max,
        maxLength,
        prefixRef,
        suffixRef,
        placeholder,
        value,
        invalid,
        error,
        type,
        disabled,
        inline,
        size,
        showMiniLabel,
    });
    const { focused } = useFormFieldFocus({ wrapperRef });
    const showClear = useShowClear({
        clearable,
        multiline: type === "textarea",
        focused,
        hasValue: Boolean(value),
        disabled,
        readonly,
    });
    const { isToolbarVisible, toolbarAnimationEnd, toolbarAnimationStart } = useToolbar({
        focused,
        toolbar,
        toolbarVisibility,
    });
    return (React__default.createElement("div", { className: containerClasses },
        React__default.createElement("div", { className: wrapperClasses, style: wrapperInlineStyle, "data-testid": "Form-Field-Wrapper", ref: wrapperRef },
            React__default.createElement(FormFieldInputHorizontalWrapper, null,
                React__default.createElement(AffixIcon, Object.assign({}, prefix, { size: size })),
                React__default.createElement(FormFieldInputWrapperStyles, null,
                    (showMiniLabel || !value) && (React__default.createElement(FormFieldLabel, { htmlFor: identifier, style: (prefixRef === null || prefixRef === void 0 ? void 0 : prefixRef.current) || (suffixRef === null || suffixRef === void 0 ? void 0 : suffixRef.current)
                            ? labelStyle
                            : undefined }, placeholder)),
                    React__default.createElement(AffixLabel, Object.assign({}, prefix, { labelRef: prefixRef })),
                    React__default.createElement(FormFieldWrapperMain, null, children),
                    React__default.createElement(AffixLabel, Object.assign({}, suffix, { labelRef: suffixRef, variation: "suffix" }))),
                React__default.createElement(ClearAction, { onClick: onClear, visible: showClear }),
                React__default.createElement(AffixIcon, Object.assign({}, suffix, { variation: "suffix", size: size }))),
            React__default.createElement(FormFieldWrapperToolbar, { toolbarVisibility: toolbarVisibility, isToolbarVisible: isToolbarVisible, toolbarAnimationEnd: toolbarAnimationEnd, toolbarAnimationStart: toolbarAnimationStart, toolbar: toolbar })),
        React__default.createElement(FormFieldDescription, { visible: !!description && !inline, id: descriptionIdentifier, description: description }),
        React__default.createElement(InputValidation, { message: error, visible: !!error && !inline })));
}
/**
 * @internal Reach out to UX Foundations if using this component since it is possible it might change
 */
function FormFieldInputHorizontalWrapper({ children, }) {
    return React__default.createElement("div", { className: formFieldStyles.horizontalWrapper }, children);
}
/**
 * @internal Reach out to UX Foundations if using this component since it is possible it might change
 */
function FormFieldInputWrapperStyles({ children, }) {
    return React__default.createElement("div", { className: formFieldStyles.inputWrapper }, children);
}
/**
 * @internal Reach out to UX Foundations if using this component since it is possible it might change
 */
function FormFieldWrapperMain({ children, tabIndex = -1, }) {
    return (React__default.createElement("div", { className: formFieldStyles.childrenWrapper, tabIndex: tabIndex }, children));
}
function FormFieldLabel({ children, htmlFor, style, external = false, }) {
    if (!children)
        return null;
    return (React__default.createElement("label", { className: external ? formFieldStyles.externalLabel : formFieldStyles.label, htmlFor: htmlFor, style: style }, children));
}
/**
 * @internal Reach out to UX Foundations if using this component since it is possible it might change
 */
function FormFieldWrapperToolbar({ toolbar, isToolbarVisible, toolbarAnimationEnd, toolbarAnimationStart, toolbarVisibility, }) {
    return (React__default.createElement(AnimatePresence, { initial: toolbarVisibility === "always" ? false : true }, isToolbarVisible && (React__default.createElement(motion.div, { key: "toolbar", initial: toolbarAnimationEnd, animate: toolbarAnimationStart, exit: toolbarAnimationEnd, transition: {
            duration: tokens["timing-base"] / 1000,
            ease: "easeInOut",
        }, tabIndex: -1 },
        React__default.createElement("div", { className: formFieldStyles.toolbar, "data-testid": "ATL-InputText-Toolbar" }, toolbar)))));
}

function FormFieldPostFix({ variation, visible = true, className, }) {
    if (!visible)
        return null;
    return (React__default.createElement("span", { className: classnames(formFieldStyles.postfix, className) }, variation === "select" ? (React__default.createElement(Icon, { name: "arrowDown" })) : (React__default.createElement(Spinner, { size: "small" }))));
}

/**
 * Combines the actions from the props of the FormField with the actions from react-hook-form. This is used to
 * manage the form state of a field through react-hook-form while providing support for additional callbacks
 */
function useAtlantisFormFieldActions({ name, onChange, inputRef, onControllerChange, onControllerBlur, onEnter, readonly, type, setValue, onFocus, onBlur, onValidation, }) {
    function handleClear() {
        var _a;
        handleBlur();
        setValue(name, "", { shouldValidate: true });
        onChange && onChange("");
        (_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
    }
    function handleChange(event) {
        let newValue;
        newValue = event.currentTarget.value;
        if (type === "number" && newValue.length > 0) {
            newValue = parseFloat(newValue);
        }
        onChange && onChange(newValue, event);
        onControllerChange(event);
    }
    function handleKeyDown(event) {
        if (!onEnter)
            return;
        if (event.key !== "Enter")
            return;
        if (event.shiftKey || event.ctrlKey)
            return;
        event.preventDefault();
        onEnter && onEnter(event);
    }
    function handleFocus(event) {
        const target = event.currentTarget;
        if (target.select) {
            setTimeout(() => readonly && target.select());
        }
        onFocus && onFocus();
    }
    function handleBlur() {
        onBlur && onBlur();
        onControllerBlur();
    }
    function handleValidation(message) {
        onValidation && onValidation(message);
    }
    return {
        handleClear,
        handleChange,
        handleKeyDown,
        handleFocus,
        handleBlur,
        handleValidation,
    };
}

/**
 * Provides the props for the html fields rendered by the FormField component
 */
function useAtlantisFormField({ id, nameProp, name, useControllerField: useControllerField, description, disabled, readonly, keyboard, autofocus, pattern, type, value, handleChange, handleBlur, handleFocus, inline, validations, handleKeyDown, handleValidation, errorMessage, }) {
    const descriptionIdentifier = `descriptionUUID--${id}`;
    const fieldProps = Object.assign(Object.assign(Object.assign({}, useControllerField), { id, className: classnames(formFieldStyles.input, {
            [formFieldStyles.emptyPhoneNumber]: shouldAddPhoneNumberClass(type, value, pattern),
        }), name: (validations || nameProp) && name, disabled: disabled, readOnly: readonly, inputMode: keyboard, onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, autoFocus: autofocus }), (description &&
        !inline && { "aria-describedby": descriptionIdentifier }));
    const textFieldProps = Object.assign(Object.assign({}, fieldProps), { autoFocus: autofocus, onKeyDown: handleKeyDown });
    useEffect(() => handleValidation(errorMessage), [errorMessage]);
    return { textFieldProps, fieldProps, descriptionIdentifier };
}
function shouldAddPhoneNumberClass(type, value, pattern) {
    return type === "tel" && !value && pattern && pattern[0] === "(";
}

function useAtlantisFormFieldName({ id, nameProp, }) {
    /**
     * Generate a name if one is not supplied, this is the name
     * that will be used for react-hook-form and not neccessarily
     * attached to the DOM
     */
    const name = nameProp ? nameProp : `generatedName--${id}`;
    return { name };
}

/**
 * Given an array of refs, merge them into a single ref callback.
 * This is useful for cases where you need to pass a multiple refs to a component.
 */
function mergeRefs(refs) {
    return value => {
        refs.forEach(ref => {
            if (typeof ref === "function") {
                ref(value);
            }
            else if (ref != null) {
                ref.current = value;
            }
        });
    };
}

/**
 * Hook used to manage the form state of a field through react-hook-form
 */
function useAtlantisReactHookForm({ actionsRef, name, defaultValue, value, validations, inputRef, }) {
    var _a;
    const formContext = useFormContext();
    // If there isn't a Form Context being provided, get a form for this field.
    const { control, setValue, watch } = formContext !== null && formContext !== void 0 ? formContext : useForm({ mode: "onTouched" });
    useImperativeHandle(actionsRef, () => ({
        setValue: newValue => {
            setValue(name, newValue, { shouldValidate: true });
        },
    }));
    const { field, fieldState: { error }, } = useController({
        name,
        control,
        rules: validations,
        defaultValue: (_a = value !== null && value !== void 0 ? value : defaultValue) !== null && _a !== void 0 ? _a : "",
    });
    const { onChange, onBlur, ref: fieldRef } = field, useControllerField = __rest(field, ["onChange", "onBlur", "ref"]);
    useEffect(() => {
        if (value != undefined) {
            setValue(name, value);
        }
    }, [value, watch(name)]);
    const inputRefs = mergeRefs([inputRef, fieldRef]);
    return {
        inputRefs,
        useControllerField,
        setValue,
        errorMessage: (error === null || error === void 0 ? void 0 : error.message) || "",
        onControllerChange: onChange,
        onControllerBlur: onBlur,
    };
}

function FormField(props) {
    // Warning: do not move useId into FormFieldInternal. This must be here to avoid
    // a problem where useId isn't stable across multiple StrictMode renders.
    // https://github.com/facebook/react/issues/27103
    const generatedId = useId();
    const id = props.id || generatedId;
    return React__default.createElement(FormFieldInternal, Object.assign({}, props, { id: id }));
}
function FormFieldInternal(props) {
    var _a;
    const { actionsRef, autocomplete = true, children, defaultValue, description, disabled, id, inputRef, inline, keyboard, max, maxLength, min, name: nameProp, pattern, readonly, rows, loading, type = "text", validations, value, onChange, onEnter, onFocus, onBlur, onValidation, onKeyUp, clearable = "never", autofocus, } = props;
    const { name } = useAtlantisFormFieldName({ id, nameProp });
    const { errorMessage, inputRefs, useControllerField, setValue, onControllerBlur, onControllerChange, } = useAtlantisReactHookForm({
        actionsRef,
        name,
        defaultValue,
        value,
        validations,
        inputRef,
    });
    const { handleValidation, handleBlur, handleChange, handleClear, handleFocus, handleKeyDown, } = useAtlantisFormFieldActions({
        inputRef,
        onChange,
        onEnter,
        readonly,
        type,
        onFocus,
        setValue,
        onBlur,
        onValidation,
        onControllerBlur,
        onControllerChange,
        name,
    });
    const { textFieldProps, fieldProps, descriptionIdentifier } = useAtlantisFormField({
        id,
        useControllerField,
        name,
        nameProp,
        description,
        validations: !!validations,
        disabled,
        readonly,
        keyboard,
        autofocus,
        value,
        type,
        pattern,
        handleChange,
        handleBlur,
        handleFocus,
        inline,
        errorMessage,
        handleValidation,
        handleKeyDown,
    });
    // Ensure focus tracking works by providing a wrapperRef when none is passed
    const internalWrapperRef = useRef(null);
    const wrapperRef = (_a = props.wrapperRef) !== null && _a !== void 0 ? _a : internalWrapperRef;
    return (React__default.createElement(FormFieldWrapper, Object.assign({}, props, { value: useControllerField.value, error: errorMessage, identifier: id, descriptionIdentifier: descriptionIdentifier, clearable: clearable, onClear: handleClear, wrapperRef: wrapperRef }), renderField()));
    function renderField() {
        switch (type) {
            case "select":
                return (React__default.createElement(React__default.Fragment, null,
                    React__default.createElement("select", Object.assign({}, fieldProps), children),
                    React__default.createElement(FormFieldPostFix, { variation: "select" })));
            case "textarea":
                return React__default.createElement("textarea", Object.assign({}, textFieldProps, { rows: rows, ref: inputRefs }));
            default:
                return (React__default.createElement(React__default.Fragment, null,
                    React__default.createElement("input", Object.assign({}, textFieldProps, { autoComplete: setAutocomplete(autocomplete), type: type, maxLength: maxLength, max: max, min: min, ref: inputRefs, onKeyUp: onKeyUp })),
                    loading && React__default.createElement(FormFieldPostFix, { variation: "spinner" }),
                    children));
        }
    }
}
function setAutocomplete(autocompleteSetting) {
    if (autocompleteSetting === true) {
        return undefined;
    }
    else if (autocompleteSetting === false) {
        return "off";
    }
    return autocompleteSetting;
}

export { AffixIcon as A, FormFieldInputHorizontalWrapper as F, useAtlantisFormFieldActions as a, useAtlantisFormFieldName as b, useAtlantisReactHookForm as c, FormFieldInputWrapperStyles as d, FormFieldLabel as e, FormFieldWrapper as f, FormFieldWrapperMain as g, FormFieldWrapperToolbar as h, AffixLabel as i, useFormFieldWrapperStyles as j, FormField as k, formFieldStyles as l, FormFieldPostFix as m, mergeRefs as n, useAtlantisFormField as u };
