/**
 * Workaround для бага позиционирования Vuetify connected-location-strategy.
 *
 * Когда активатор `v-select`/`v-autocomplete` находится внутри
 * `position: fixed`-контейнера (кастомный дровер, диалог), Vuetify включает
 * ветку `activatorFixed` (`isFixedPosition()` → true). Её `getIntrinsicSize()`
 * вычитает `el.style.left` из измеренной геометрии оверлея; на переходном
 * кадре, когда контент ещё отрисован в нулевой позиции, а инлайновый
 * `style.left` уже не нулевой, `contentBox.x` становится отрицательным и
 * стратегия аккумулирует смещение — меню уезжает на кратное X активатора
 * (за край экрана).
 *
 * Обычно гонку сглаживают пересчёты, размазанные по анимации открытия. Под
 * `prefers-reduced-motion: reduce` (умолчание Windows Server) анимации нет —
 * один пересчёт на «плохом» кадре остаётся финальным.
 *
 * Фикс: дождаться, пока контент оверлея отрисован и геометрически стабилен,
 * затем один раз послать `resize` — Vuetify пересчитает позицию по уже
 * устоявшейся геометрии и поставит меню корректно. Безопасно при motion ON
 * (пересчёт по стабильной геометрии идемпотентен) и для не-fixed контейнеров.
 *
 * Привязывать к `@update:menu` нужного `v-autocomplete`/`v-select`.
 */
// Ядро: дождаться, пока геометрия последнего открытого меню устаканится, и один
// раз послать resize — Vuetify пересчитает позицию по уже стабильной геометрии.
function scheduleStabilize(): void {
    if (typeof window === 'undefined') return;

    let prevLeft = Number.NaN;
    let stableFrames = 0;
    let totalFrames = 0;

    const tick = (): void => {
        // Последний открытый overlay-menu (на случай вложенных оверлеев).
        const menus = document.querySelectorAll<HTMLElement>('.v-overlay.v-menu .v-overlay__content');
        const el = menus[menus.length - 1];

        if (el && el.getBoundingClientRect().width > 0) {
            const left = Math.round(el.getBoundingClientRect().left);
            stableFrames = left === prevLeft ? stableFrames + 1 : 0;
            prevLeft = left;
            // 3 кадра без движения = геометрия устоялась → один чистый пересчёт.
            if (stableFrames >= 3) {
                window.dispatchEvent(new Event('resize'));
                return;
            }
        }

        // Предохранитель ~1.5 c: не зацикливаться, если оверлей не появился.
        if (++totalFrames < 90) requestAnimationFrame(tick);
    };

    requestAnimationFrame(tick);
}

let installed = false;

/**
 * Глобально включает обход бага позиционирования меню Vuetify: один
 * `MutationObserver` ловит появление любого `.v-overlay.v-menu` в DOM и
 * запускает стабилизацию позиции. Вешать один раз при запуске приложения —
 * покрывает все `v-select`/`v-autocomplete`/`v-menu`, текущие и будущие, без
 * ручной разметки в шаблонах.
 *
 * Идемпотентна (повторный вызов — noop). SSR-safe. Возвращает teardown
 * (отключить наблюдатель — нужно тестам и на случай явной остановки).
 */
export function installMenuRepositionFix(): () => void {
    const noop = (): void => {};
    if (installed) return noop;
    if (
        typeof window === 'undefined' ||
        typeof document === 'undefined' ||
        typeof MutationObserver === 'undefined' ||
        !document.body
    ) {
        return noop;
    }

    installed = true;
    const observer = new MutationObserver((mutations) => {
        for (const m of mutations) {
            for (const node of m.addedNodes) {
                if (!(node instanceof HTMLElement)) continue;
                if (node.matches('.v-overlay.v-menu') || node.querySelector('.v-overlay.v-menu')) {
                    scheduleStabilize();
                    return; // одного запуска на пачку мутаций достаточно
                }
            }
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });

    return () => {
        observer.disconnect();
        installed = false;
    };
}
