/**
 * @license MIT
 * author: Cube Dev Team
 * @cube-dev/ui-kit v0.98.7
 * Released under the MIT license.
 */

import { createElement, forwardRef, useContext, useInsertionEffect, useMemo, useRef, } from 'react';
import { isValidElementType } from 'react-is';
import { allocateClassName, inject, injectGlobal } from './injector';
import { BreakpointsContext } from './providers/BreakpointsProvider';
import { BASE_STYLES } from './styles/list';
import { cacheWrapper } from './utils/cache-wrapper';
import { getDisplayName } from './utils/getDisplayName';
import { mergeStyles } from './utils/mergeStyles';
import { modAttrs } from './utils/modAttrs';
import { processTokens, stringifyTokens } from './utils/processTokens';
import { renderStyles } from './utils/renderStyles';
import { stringifyStyles } from './utils/styles';
/**
 * Mapping of is* properties to their corresponding HTML attributes
 */
const IS_PROPERTIES_MAP = {
    isDisabled: 'disabled',
    isHidden: 'hidden',
    isChecked: 'checked',
};
/**
 * Precalculated entries for performance optimization
 */
const IS_PROPERTIES_ENTRIES = Object.entries(IS_PROPERTIES_MAP);
/**
 * Helper function to handle is* properties consistently
 * Transforms is* props to HTML attributes and adds corresponding data-* attributes
 */
function handleIsProperties(props) {
    for (const [isProperty, targetAttribute] of IS_PROPERTIES_ENTRIES) {
        if (isProperty in props) {
            props[targetAttribute] = props[isProperty];
            delete props[isProperty];
        }
        // Add data-* attribute if target attribute is truthy and doesn't already exist
        const dataAttribute = `data-${targetAttribute}`;
        if (!(dataAttribute in props) && props[targetAttribute]) {
            props[dataAttribute] = '';
        }
    }
}
/**
 * Simple hash function for internal cache keys
 */
// Generate unique cache key for style deduplication
function generateStyleCacheKey(styleKey, contextKey) {
    // Use null character as separator for better performance and no collision risk
    return contextKey ? `${styleKey}\0${contextKey}` : styleKey;
}
// Implementation
export function tasty(Component, options) {
    if (typeof Component === 'string') {
        return tastyGlobal(Component, options);
    }
    if (isValidElementType(Component)) {
        return tastyWrap(Component, options);
    }
    return tastyElement(Component);
}
// Internal specialized implementations
function tastyGlobal(selector, styles) {
    const _StyleDeclarationComponent = ({ breakpoints, }) => {
        let contextBreakpoints = useContext(BreakpointsContext);
        const breakpointsList = (breakpoints ?? contextBreakpoints) || [980];
        const disposeRef = useRef(null);
        const styleResults = useMemo(() => {
            if (!styles)
                return [];
            return renderStyles(styles, breakpointsList, selector);
        }, [selector, styles, breakpointsList]);
        // Inject styles at insertion phase; cleanup on change/unmount
        useInsertionEffect(() => {
            disposeRef.current?.();
            if (styleResults.length === 0)
                return;
            const { dispose } = injectGlobal(styleResults);
            disposeRef.current = dispose;
            return () => {
                disposeRef.current?.();
                disposeRef.current = null;
            };
        }, [styleResults]);
        return null;
    };
    _StyleDeclarationComponent.displayName = `TastyStyleDeclaration(${selector})`;
    return _StyleDeclarationComponent;
}
function tastyWrap(Component, options) {
    let { as: extendTag, element: extendElement, ...defaultProps } = (options ?? {});
    let propsWithStyles = ['styles'].concat(Object.keys(defaultProps).filter((prop) => prop.endsWith('Styles')));
    const _WrappedComponent = forwardRef((props, ref) => {
        const { as, element, ...restProps } = props;
        const propsWithStylesValues = propsWithStyles.map((prop) => props[prop]);
        const mergedStylesMap = useMemo(() => {
            return propsWithStyles.reduce((map, prop) => {
                const restValue = restProps[prop];
                const defaultValue = defaultProps[prop];
                if (restValue != null && defaultValue != null) {
                    map[prop] = mergeStyles(defaultValue, restValue);
                }
                else {
                    map[prop] = restValue ?? defaultValue;
                }
                return map;
            }, {});
        }, [propsWithStylesValues]);
        const elementProps = {
            ...defaultProps,
            ...restProps,
            ...mergedStylesMap,
            as: as ?? extendTag,
            element: element || extendElement,
            ref,
        };
        return createElement(Component, elementProps);
    });
    _WrappedComponent.displayName = `TastyWrappedComponent(${getDisplayName(Component, defaultProps.qa ?? extendTag ?? 'Anonymous')})`;
    return _WrappedComponent;
}
function tastyElement(tastyOptions) {
    let { as: originalAs = 'div', element: defaultElement, styles: defaultStyles, styleProps, variants, tokens: defaultTokens, ...defaultProps } = tastyOptions;
    let _TastyComponent;
    if (variants) {
        const variantEntries = Object.entries(variants);
        const variantComponents = variantEntries.reduce((map, [variant, variantStyles]) => {
            map[variant] = tastyElement({
                as: originalAs,
                styles: mergeStyles(defaultStyles, variantStyles),
                styleProps,
                tokens: defaultTokens,
                ...defaultProps,
            });
            return map;
        }, {});
        if (!variantComponents['default']) {
            variantComponents['default'] = tastyElement({
                as: originalAs,
                styles: defaultStyles,
                styleProps,
                tokens: defaultTokens,
                ...defaultProps,
            });
        }
        // eslint-disable-next-line react/display-name
        _TastyComponent = forwardRef((allProps, ref) => {
            const { variant, ...restProps } = allProps;
            const Component = variantComponents[variant || 'default'] ??
                variantComponents['default'];
            const componentProps = restProps;
            const elementProps = {
                ...componentProps,
                ref,
            };
            return createElement(Component, elementProps);
        });
    }
    else {
        /**
         * An additional optimization that allows to avoid rendering styles across various instances
         * of the same element if no custom styles are provided via `styles` prop or direct style props.
         */
        const renderDefaultStyles = cacheWrapper((breakpoints) => {
            // Return rules without className - injector will add it
            return renderStyles(defaultStyles || {}, breakpoints);
        });
        let { qa: defaultQa, qaVal: defaultQaVal, ...otherDefaultProps } = defaultProps ?? {};
        // eslint-disable-next-line react/display-name
        _TastyComponent = forwardRef((allProps, ref) => {
            let { as, styles, variant: _omitVariant, breakpoints, mods, element, qa, qaVal, className: userClassName, tokens, style, ...otherProps } = allProps;
            // Optimize propStyles extraction - avoid creating empty objects
            let propStyles = null;
            const propsToCheck = styleProps
                ? styleProps.concat(BASE_STYLES)
                : BASE_STYLES;
            for (const prop of propsToCheck) {
                const key = prop;
                if (!propStyles)
                    propStyles = {};
                if (key in otherProps) {
                    propStyles[key] = otherProps[key];
                    delete otherProps[key];
                }
            }
            if (!styles ||
                (styles && Object.keys(styles).length === 0)) {
                styles = undefined;
            }
            let contextBreakpoints = useContext(BreakpointsContext);
            breakpoints = breakpoints ?? contextBreakpoints;
            // Memoize breakpoints key once
            const breakpointsKey = useMemo(() => breakpoints?.join(',') || '', [breakpoints?.join(',')]);
            const propStylesKey = stringifyStyles(propStyles);
            // Optimize style computation and cache key generation
            const { allStyles, cacheKey, useDefaultStyles } = useMemo(() => {
                const hasStyles = styles && Object.keys(styles).length > 0;
                const hasPropStyles = propStyles && Object.keys(propStyles).length > 0;
                const useDefault = !hasStyles && !hasPropStyles;
                const merged = useDefault
                    ? defaultStyles
                    : mergeStyles(defaultStyles, styles, propStyles);
                // Generate cache key for style deduplication
                const styleKey = stringifyStyles(merged || {});
                const key = generateStyleCacheKey(styleKey, breakpointsKey ? `bp:${breakpointsKey}` : undefined);
                return {
                    allStyles: merged,
                    cacheKey: key,
                    useDefaultStyles: useDefault,
                };
            }, [styles, propStylesKey, breakpointsKey]);
            // Compute rules synchronously; inject via insertion effect
            const directResult = useMemo(() => {
                if (useDefaultStyles) {
                    return renderDefaultStyles(breakpoints);
                }
                else if (allStyles && Object.keys(allStyles).length > 0) {
                    // Return rules without className - injector will add it
                    return renderStyles(allStyles, breakpoints);
                }
                else {
                    return { rules: [], className: '' };
                }
            }, [useDefaultStyles, allStyles, breakpointsKey, cacheKey]);
            const disposeRef = useRef(null);
            // Allocate className in render phase (safe for React Strict Mode)
            const allocatedClassName = useMemo(() => {
                if (!directResult.rules.length || !cacheKey)
                    return '';
                const { className } = allocateClassName(cacheKey);
                return className;
            }, [directResult.rules.length, cacheKey]);
            // Inject styles in insertion effect (avoids render phase side effects)
            useInsertionEffect(() => {
                // Cleanup previous disposal reference
                disposeRef.current?.();
                if (directResult.rules.length > 0) {
                    const injectionResult = inject(directResult.rules, { cacheKey });
                    disposeRef.current = injectionResult.dispose;
                }
                else {
                    disposeRef.current = null;
                }
                return () => {
                    disposeRef.current?.();
                    disposeRef.current = null;
                };
            }, [directResult.rules, cacheKey]);
            const injectedClassName = allocatedClassName;
            // Merge default tokens with instance tokens (instance overrides defaults)
            const tokensKey = stringifyTokens(tokens);
            const mergedTokens = useMemo(() => {
                if (!defaultTokens && !tokens)
                    return undefined;
                if (!defaultTokens)
                    return tokens;
                if (!tokens)
                    return defaultTokens;
                return { ...defaultTokens, ...tokens };
            }, [tokensKey]);
            // Process merged tokens into inline style properties
            const processedTokenStyle = useMemo(() => {
                return processTokens(mergedTokens);
            }, [mergedTokens]);
            // Merge processed tokens with explicit style prop (style has priority)
            const mergedStyle = useMemo(() => {
                if (!processedTokenStyle && !style)
                    return undefined;
                if (!processedTokenStyle)
                    return style;
                if (!style)
                    return processedTokenStyle;
                return { ...processedTokenStyle, ...style };
            }, [processedTokenStyle, style]);
            let modProps;
            if (mods) {
                const modsObject = mods;
                modProps = modAttrs(modsObject);
            }
            // Merge user className with injected className
            const finalClassName = [
                userClassName || '',
                injectedClassName,
            ]
                .filter(Boolean)
                .join(' ');
            const elementProps = {
                'data-element': element || defaultElement,
                'data-qa': qa || defaultQa,
                'data-qaval': qaVal || defaultQaVal,
                ...otherDefaultProps,
                ...(modProps || {}),
                ...otherProps,
                className: finalClassName,
                style: mergedStyle,
                ref,
            };
            // Apply the helper to handle is* properties
            handleIsProperties(elementProps);
            // NEW: Use plain createElement instead of styled Element
            const renderedElement = createElement(as ?? originalAs, elementProps);
            // Note: Empty className is normal for elements with no styles
            return renderedElement;
        });
    }
    _TastyComponent.displayName = `TastyComponent(${defaultProps.qa || originalAs})`;
    return _TastyComponent;
}
export const Element = tasty({});

        