const roundByDPR = (value) => {
    const dpr = window.devicePixelRatio || 1;
    return Math.round(value * dpr) / dpr;
};

const attributeToOption = (attribute) => {
    attribute = attribute.replace('tooltip', '');
    return attribute.charAt(0).toLowerCase() + attribute.slice(1);
};

let visibilityListenerRegistered = false;
const handleVisibilityChange = () => {
    if (document.hidden) {
        // Скрываем все активные всплывающие подсказки
        for (const $tooltip of document.querySelectorAll('.tooltip')) {
            const ref = $tooltip._reference;
            if (ref?._tooltip) {
                // Очищаем таймеры появления при скрытии страницы
                clearTimeout(ref._tooltip._showTimeout);
                if (ref._tooltip.isVisible) {
                    ref._tooltip.hide({ immediately: true });
                }
            }
        }
    }
};

const templates = {
    arrow: () => `
        <div class="tooltip__arrow" style="pointer-events: none; position: absolute; transform: rotate(45deg);"></div>
    `,
    interactiveHelper: () => `
        <div class="tooltip__interactive-helper" style="position: absolute; z-index: -1;"></div>
    `,
};

export const createTooltip = ($el, content, options) => {
    options = {
        animation: [
            [{ opacity: 0 }, { opacity: 1 }],
            [{ opacity: 1 }, { opacity: 0 }],
        ],
        appendTo: document.body,
        arrow: true,
        delay: [0, 0],
        duration: [0, 0],
        easing: ['linear', 'linear'],
        hideOnClick: true, // Возможные значения: true, 'all', 'toggle'
        interactive: true,
        offset: [0, 8],
        placement: 'top',
        shiftPadding: [8, 0],
        theme: 'light',
        trigger: 'mouseenter click',
        virtualReference: undefined,
        zIndex: '',
        ...options,
    };

    for (const [key, value] of Object.entries($el.dataset)) {
        if (key.startsWith('tooltip')) {
            let parsedValue = value;
            try {
                parsedValue = JSON.parse(value);
            } catch {} // eslint-disable-line no-empty
            options[attributeToOption(key)] = parsedValue;
        }
    }

    let rafId;
    const listeners = [];

    $el._tooltip = {
        options,
        isVisible: false,
        $tooltip: undefined,
        $container: undefined,
        $arrow: undefined,
        $interactive: undefined,
        _showTimeout: undefined,
        _hideTimeout: undefined,
        _currentAnimation: undefined,
        autoUpdateCleanup: () => {},
        updatePosition: async () => {},

        setContent(updatedContent) {
            if (updatedContent !== undefined) {
                content = updatedContent;
            }
            if ($el._tooltip.$container) {
                if (content instanceof HTMLElement) {
                    $el._tooltip.$container.innerHTML = '';
                    $el._tooltip.$container.append(content);
                } else {
                    $el._tooltip.$container.innerHTML = content;
                }
            }
        },

        async updateOptions(updatedOptions = {}) {
            for (const [name, value] of Object.entries(updatedOptions)) {
                options[name] = value;
            }

            if (updatedOptions.arrow !== undefined && $el._tooltip.$tooltip) {
                if (options.arrow) {
                    if (!$el._tooltip.$arrow) {
                        $el._tooltip.$tooltip.querySelector('.tooltip__root').insertAdjacentHTML('afterbegin', templates.arrow());
                        $el._tooltip.$arrow = $el._tooltip.$tooltip.querySelector('.tooltip__arrow');
                    }
                } else {
                    $el._tooltip.$arrow?.remove();
                    $el._tooltip.$arrow = undefined;
                }
            }

            if (updatedOptions.interactive !== undefined) {
                for (const { el, event, listener } of listeners) {
                    el.removeEventListener(event, listener);
                }
                listeners.length = 0;
                registerListeners();
                if ($el._tooltip.$tooltip) {
                    if (options.interactive) {
                        if (!$el._tooltip.$interactive) {
                            $el._tooltip.$tooltip.querySelector('.tooltip__root').insertAdjacentHTML('afterbegin', templates.interactiveHelper());
                            $el._tooltip.$interactive =
                                $el._tooltip.$tooltip.querySelector('.tooltip__interactive-helper');
                        }
                    } else {
                        $el._tooltip.$interactive?.remove();
                        $el._tooltip.$interactive = undefined;
                    }
                }
            }

            if (updatedOptions.theme !== undefined && $el._tooltip.$tooltip) {
                const classIndex = [...$el._tooltip.$tooltip.classList].findIndex((className) =>
                    className.startsWith('tooltip_theme_'),
                );
                if (classIndex === -1) {
                    $el._tooltip.$tooltip.classList.add(`tooltip_theme_${options.theme}`);
                } else {
                    $el._tooltip.$tooltip.classList.replace(
                        $el._tooltip.$tooltip.classList[classIndex],
                        `tooltip_theme_${options.theme}`,
                    );
                }
            }

            if (updatedOptions.zIndex !== undefined && $el._tooltip.$tooltip) {
                Object.assign($el._tooltip.$tooltip.style, { zIndex: options.zIndex });
            }

            if ($el._tooltip.$tooltip) {
                await $el._tooltip.updatePosition();
            }
        },

        destroy() {
            if (!$el._tooltip) {
                return;
            }

            clearTimeout($el._tooltip._showTimeout);
            clearTimeout($el._tooltip._hideTimeout);
            $el._tooltip._currentAnimation?.cancel();
            cancelAnimationFrame(rafId);

            $el._tooltip.$tooltip?.remove();

            // Вызываем autoUpdateCleanup только если всплывающая подсказка была видна (иначе вызывать её не имеет смысла)
            if ($el._tooltip.isVisible) {
                $el._tooltip.autoUpdateCleanup();
            }

            for (const { el, event, listener } of listeners) {
                el.removeEventListener(event, listener);
            }
            listeners.length = 0;

            document.body.removeEventListener('click', $el._tooltip.hideOnClickListener);

            delete $el._tooltip;
        },
    };

    if (options.onCreate) {
        options.onCreate($el._tooltip);
    }

    $el._tooltip.show = async ({ immediately } = {}) => {
        clearTimeout($el._tooltip._hideTimeout);

        if (!$el._tooltip.$tooltip) {
            const { computePosition, offset, flip, shift, arrow } = await import('@floating-ui/dom');

            $el._tooltip.$tooltip = document.createElement('div');
            $el._tooltip.$tooltip._reference = $el;
            $el._tooltip.$tooltip.classList.add('tooltip', `tooltip_theme_${options.theme}`);
            Object.assign($el._tooltip.$tooltip.style, { zIndex: options.zIndex });

            $el._tooltip.$tooltip.innerHTML = `
                <div class="tooltip__root">
                    ${options.interactive ? templates.interactiveHelper() : ''}
                    ${options.arrow ? templates.arrow() : ''}
                    <div class="tooltip__container"></div>
                </div>
            `;

            $el._tooltip.$container = $el._tooltip.$tooltip.querySelector('.tooltip__container');
            $el._tooltip.$arrow = $el._tooltip.$tooltip.querySelector('.tooltip__arrow');
            $el._tooltip.$interactive = $el._tooltip.$tooltip.querySelector('.tooltip__interactive-helper');

            $el._tooltip.setContent();

            $el._tooltip.updatePosition = () => {
                cancelAnimationFrame(rafId);
                rafId = requestAnimationFrame(async () => {
                    const { x, y, placement, middlewareData } = await computePosition(
                        options.virtualReference ?? $el,
                        $el._tooltip.$tooltip,
                        {
                            placement: options.placement,
                            middleware: [
                                offset({ mainAxis: options.offset[1], crossAxis: options.offset[0] }),
                                flip(),
                                shift({
                                    padding: {
                                        top: options.shiftPadding[1],
                                        right: options.shiftPadding[0],
                                        bottom: options.shiftPadding[1],
                                        left: options.shiftPadding[0],
                                    },
                                }),
                                arrow({ element: $el._tooltip.$arrow }),
                            ],
                        },
                    );

                    Object.assign($el._tooltip.$tooltip.style, {
                        transform: `translate(${roundByDPR(x)}px, ${roundByDPR(y)}px)`,
                    });

                    const side = placement.split('-')[0];
                    const staticSide = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }[side];

                    $el._tooltip.$tooltip.classList.remove(
                        'tooltip_side_top',
                        'tooltip_side_bottom',
                        'tooltip_side_left',
                        'tooltip_side_right',
                    );
                    $el._tooltip.$tooltip.classList.add(`tooltip_side_${side}`);

                    if ($el._tooltip.$interactive) {
                        Object.assign($el._tooltip.$interactive.style, {
                            width: side === 'left' || side === 'right' ? `${options.offset[1]}px` : '100%',
                            height: side === 'top' || side === 'bottom' ? `${options.offset[1]}px` : '100%',
                            [staticSide]: `-${options.offset[1]}px`,
                        });
                    }

                    if ($el._tooltip.$arrow && middlewareData.arrow) {
                        const { x, y } = middlewareData.arrow;
                        Object.assign($el._tooltip.$arrow.style, {
                            left: x === undefined ? '' : `${x}px`,
                            top: y === undefined ? '' : `${y}px`,
                            [staticSide]: `-${$el._tooltip.$arrow.offsetWidth / 2}px`,
                        });
                    }
                });
            };

            if (options.onMount) {
                options.onMount($el._tooltip);
            }
        }

        const { autoUpdate } = await import('@floating-ui/dom');

        $el._tooltip._showTimeout = setTimeout(
            async () => {
                const isMouseEnterTrigger = options.trigger.includes('mouseenter');
                const isHovering = $el.matches(':hover');

                // Проверяем $el._tooltip на сущестование и актуальность показа всплывающей подсказки
                if (
                    !$el._tooltip ||
                    $el._tooltip.isVisible ||
                    (isMouseEnterTrigger && !isHovering) ||
                    document.hidden
                ) {
                    return;
                }

                (options.appendTo === 'parent' ? $el.parentElement : options.appendTo).append($el._tooltip.$tooltip);
                $el._tooltip.isVisible = true;
                $el._tooltip.autoUpdateCleanup = autoUpdate($el, $el._tooltip.$tooltip, $el._tooltip.updatePosition);

                if (options.hideOnClick && (options.trigger.includes('click') || options.trigger.includes('manual'))) {
                    document.body.addEventListener('click', $el._tooltip.hideOnClickListener);
                    listeners.push({
                        el: document.body,
                        event: 'click',
                        listener: $el._tooltip.hideOnClickListener,
                    });
                }

                if (options.onShow) {
                    options.onShow($el._tooltip);
                }

                try {
                    const animation = $el._tooltip.$tooltip.querySelector('.tooltip__root').animate(options.animation[0], {
                        duration: immediately ? 0 : options.duration[0],
                        easing: options.easing[0],
                    });
                    $el._tooltip._currentAnimation = animation;
                    await animation.finished;
                } catch {} // eslint-disable-line no-empty

                if (options.onShown) {
                    options.onShown($el._tooltip);
                }
            },
            immediately ? 0 : options.delay[0],
        );
    };

    $el._tooltip.hide = ({ immediately } = {}) => {
        clearTimeout($el._tooltip._showTimeout);
        $el._tooltip._hideTimeout = setTimeout(
            async () => {
                // Проверяем $el._tooltip на сущестование
                if (!$el._tooltip || !$el._tooltip.isVisible) {
                    return;
                }

                if (options.onHide) {
                    options.onHide($el._tooltip);
                }

                document.body.removeEventListener('click', $el._tooltip.hideOnClickListener);

                try {
                    const animation = $el._tooltip.$tooltip.querySelector('.tooltip__root').animate(options.animation[1], {
                        duration: immediately ? 0 : options.duration[1],
                        easing: options.easing[1],
                    });
                    $el._tooltip._currentAnimation = animation;
                    await animation.finished;
                } catch {} // eslint-disable-line no-empty

                // Ещё одна проверка на сущестование $el._tooltip после await
                if (!$el._tooltip) {
                    return;
                }

                if ($el._tooltip.$tooltip) {
                    $el._tooltip.$tooltip.remove();
                }
                $el._tooltip.isVisible = false;
                $el._tooltip.autoUpdateCleanup();

                if (options.onHidden) {
                    options.onHidden($el._tooltip);
                }
            },
            immediately ? 0 : options.delay[1],
        );
    };

    $el._tooltip.hideOnClickListener = ({ target }) => {
        if (!$el._tooltip.isVisible) {
            return;
        }

        if (options.hideOnClick === 'all') {
            $el._tooltip.hide();
        } else if (options.hideOnClick === 'toggle') {
            if ($el.contains(target)) {
                $el._tooltip.hide();
            }
        } else {
            // options.hideOnClick === true
            if ($el.contains(target) || !$el._tooltip.$tooltip.contains(target)) {
                $el._tooltip.hide();
            }
        }
    };

    $el._tooltip.clickListener = () => {
        if (document.hidden) {
            return;
        }
        if (!$el._tooltip.isVisible) {
            $el._tooltip.show();
        }
    };

    $el._tooltip.mouseEnterListener = (event) => {
        if (event.pointerType !== 'mouse' || document.hidden) {
            return;
        }
        $el._tooltip.show();
    };

    $el._tooltip.mouseLeaveListener = (event) => {
        if (event.pointerType !== 'mouse') {
            return;
        }

        if (options.interactive && event.relatedTarget && $el._tooltip.$tooltip?.contains(event.relatedTarget)) {
            $el._tooltip.$tooltip.addEventListener(
                'pointerleave',
                (event) => {
                    if (event.pointerType !== 'mouse') {
                        return;
                    }
                    if (!event.relatedTarget || !$el.contains(event.relatedTarget)) {
                        $el._tooltip.hide();
                    }
                },
                { once: true },
            );
        } else {
            $el._tooltip.hide();
        }
    };

    $el._tooltip.focusListener = () => {
        if (document.hidden) {
            return;
        }
        $el._tooltip.show();
    };

    $el._tooltip.blurListener = () => {
        $el._tooltip.hide();
    };

    const registerListeners = () => {
        for (const trigger of options.trigger.split(' ')) {
            switch (trigger) {
                case 'mouseenter': {
                    $el.addEventListener('pointerenter', $el._tooltip.mouseEnterListener);
                    listeners.push({ el: $el, event: 'pointerenter', listener: $el._tooltip.mouseEnterListener });

                    $el.addEventListener('pointerleave', $el._tooltip.mouseLeaveListener);
                    listeners.push({ el: $el, event: 'pointerleave', listener: $el._tooltip.mouseLeaveListener });
                    break;
                }
                case 'click': {
                    $el.addEventListener('click', $el._tooltip.clickListener);
                    listeners.push({ el: $el, event: 'click', listener: $el._tooltip.clickListener });

                    if (!options.interactive) {
                        $el.addEventListener('pointerleave', $el._tooltip.mouseLeaveListener);
                        listeners.push({ el: $el, event: 'pointerleave', listener: $el._tooltip.mouseLeaveListener });
                    }
                    break;
                }
                case 'focus': {
                    $el.addEventListener('focus', $el._tooltip.focusListener);
                    listeners.push({ el: $el, event: 'focus', listener: $el._tooltip.focusListener });
                    break;
                }
                case 'blur': {
                    $el.addEventListener('blur', $el._tooltip.blurListener);
                    listeners.push({ el: $el, event: 'blur', listener: $el._tooltip.blurListener });
                    break;
                }
            }
        }
    };

    registerListeners();

    if (!visibilityListenerRegistered) {
        document.addEventListener('visibilitychange', handleVisibilityChange);
        visibilityListenerRegistered = true;
    }
};
