import { deepEqual } from "@powerhousedao/design-system/ui/lib";
import React, { useEffect } from "react";
import { useFormContext } from "react-hook-form";
// Workaround to set the value of an input element
// following react core team recommendation
// https://github.com/facebook/react/issues/10135
function setNativeValue(element, value) {
    const valueSetter = Object.getOwnPropertyDescriptor(element, "value")?.set;
    const prototype = Object.getPrototypeOf(element);
    const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, "value")?.set;
    if (valueSetter && valueSetter !== prototypeValueSetter) {
        prototypeValueSetter?.call(element, value);
    }
    else {
        valueSetter?.call(element, value);
    }
}
// apply transformers to the value
function _applyTransformers(transformers, value, filter = "blur") {
    return transformers.reduce((value, transformer) => {
        if (typeof transformer === "function") {
            if (filter === "blur" || filter === "all") {
                // array of transformers are only applied on blur
                return transformer(value);
            }
            return value;
        }
        // it is a transformer object
        if ((transformer.options?.trigger ?? "blur") !== filter &&
            filter !== "all") {
            return value;
        }
        // if no set, we assume true
        if (transformer.options?.if === undefined || transformer.options.if) {
            return transformer.transformer(value);
        }
        return value;
    }, value);
}
/**
 * Apply transformers to the value of the input element. A transformer is a function that
 * will be applied to the value of the input element modifying the value.
 *
 * @param transformers - An array of transformers. A transformer is an object with a
 * `transformer` property, which is a function that will be applied to the value of
 * the input element, and an optional `options` property. The `options` property can
 * have a `trigger` property, which is the event that triggers the transformer, and
 * an `if` property, which is a boolean that indicates if the transformer should be
 * applied.
 * @param children - The input element to apply the transformers to as children
 *
 * @example
 * <ValueTransformer transformers={[
 *   {
 *     transformer: (value) => value.toUpperCase(),
 *     options: {
 *       trigger: "change",
 *     },
 *   },
 * ]}>
 *   <Input />
 * </ValueTransformer>
 *
 * @example
 * <ValueTransformer transformers={[
 *   {
 *     // trim the value on blur
 *     transformer: (value) => (value as string).trim(),
 *   },
 * ]}>
 *   <Input />
 * </ValueTransformer>
 */
export function ValueTransformer({ transformers, children, }) {
    const formContext = useFormContext();
    const setValue = formContext?.setValue;
    useEffect(() => {
        // apply all the transformers on mount to prevent untransformed values
        // if the field is not touched by the user
        const value = children.props.value;
        const transformedValue = _applyTransformers(transformers, value, "all");
        if (!deepEqual(transformedValue, value)) {
            if (typeof setValue === "function") {
                setValue(children.props.name, transformedValue);
            }
            else {
                // if setValue is not available, then we're not inside a form context
                // so we need to update the value using onChange if available
                children.props.onChange?.({
                    target: { value: transformedValue },
                });
            }
        }
    }, [transformers, setValue]);
    return React.cloneElement(children, {
        // @ts-expect-error - React.HTMLAttributes is not typed
        ...children.props,
        onChange: (event) => {
            // apply transformers on change
            const transformedValue = _applyTransformers(transformers, event.target.value, "change");
            if (transformedValue !== event.target.value && setValue) {
                setValue(children.props.name, transformedValue);
                setNativeValue(event.target, transformedValue);
            }
            // call the original onChange
            children.props.onChange?.(event);
        },
        // apply transformers on blur
        onBlur: (event) => {
            // call the original onBlur
            children.props.onBlur?.(event);
            // apply the transformers
            const target = event.target;
            const transformedValue = _applyTransformers(transformers, target.value, "blur");
            if (!deepEqual(transformedValue, target.value)) {
                // only dispatch change if the value has changed
                setNativeValue(target, transformedValue);
                const changeEvent = new Event("change", { bubbles: true });
                target.dispatchEvent(changeEvent);
            }
        },
        onKeyDown: (event) => {
            // call the original onKeyDown
            children.props.onKeyDown?.(event);
            // apply the transformers
            if (event.key === "Enter") {
                const target = event.target;
                const transformedValue = _applyTransformers(transformers, target.value, "keyDown");
                // only dispatch change if the value has changed
                if (!deepEqual(transformedValue, target.value)) {
                    setNativeValue(target, transformedValue);
                    const changeEvent = new Event("change", { bubbles: true });
                    target.dispatchEvent(changeEvent);
                }
            }
        },
    });
}
//# sourceMappingURL=value-transformer.js.map