import '@kview/ui-core/js/ui-floating-layer';
import { afterEach, describe, expect, it, vi } from 'vitest';

interface FloatingController {
    close: (options?: { restore?: boolean; reason?: string }) => FloatingController;
    destroy: (options?: { restore?: boolean }) => void;
    isConnected: () => boolean;
    isOpen: () => boolean;
    open: () => FloatingController;
}

interface FloatingRuntime {
    createController: (
        trigger: HTMLElement,
        surface: HTMLElement,
        options?: Record<string, unknown>,
    ) => FloatingController | null;
    init: (root?: ParentNode) => void;
}

const floatingRuntime = () => (window as Window & {
    kviewFloating?: FloatingRuntime;
}).kviewFloating;

const elementById = <ElementType extends HTMLElement>(id: string): ElementType => {
    const element = document.getElementById(id);

    if (!element) {
        throw new Error(`Missing test element #${id}.`);
    }

    return element as ElementType;
};

const nextFrame = () => new Promise<void>((resolve) => {
    window.requestAnimationFrame(() => resolve());
});

const stackIndex = (surface: HTMLElement) => Number.parseInt(
    surface.style.getPropertyValue('--ui-floating-stack-index'),
    10,
);

let controllers: FloatingController[] = [];

const createController = (
    trigger: HTMLElement,
    surface: HTMLElement,
    options: Record<string, unknown>,
) => {
    const controller = floatingRuntime()?.createController(trigger, surface, options);

    if (!controller) {
        throw new Error('The floating-layer runtime did not create a controller.');
    }

    controllers.push(controller);

    return controller;
};

afterEach(() => {
    [...controllers].reverse().forEach((controller) => controller.destroy({ restore: false }));
    controllers = [];
    document.body.innerHTML = '';
});

describe('kView floating-layer nesting', () => {
    it('ignores touch tooltip disclosure while preserving mouse hover and current labels', async () => {
        document.body.innerHTML = `
            <button
                id="navigation-toggle"
                type="button"
                aria-label="Hide navigation"
                data-ui-tooltip-auto="true"
            >
                <svg aria-hidden="true"></svg>
            </button>
        `;

        const trigger = elementById<HTMLButtonElement>('navigation-toggle');
        floatingRuntime()?.init(document);
        const tooltipId = trigger.getAttribute('aria-describedby');
        const tooltip = tooltipId ? elementById<HTMLElement>(tooltipId) : null;

        expect(tooltip).not.toBeNull();
        expect(tooltip?.getAttribute('aria-hidden')).toBe('true');

        const touchEnter = new Event('pointerenter');
        Object.defineProperty(touchEnter, 'pointerType', { value: 'touch' });
        trigger.dispatchEvent(touchEnter);

        expect(tooltip?.getAttribute('aria-hidden')).toBe('true');
        expect(tooltip?.classList.contains('is-open')).toBe(false);

        const matches = vi.spyOn(trigger, 'matches').mockImplementation((selector) => (
            selector === ':focus-visible'
                ? false
                : Element.prototype.matches.call(trigger, selector)
        ));
        trigger.focus();

        expect(document.activeElement).toBe(trigger);
        expect(tooltip?.getAttribute('aria-hidden')).toBe('true');
        expect(tooltip?.classList.contains('is-open')).toBe(false);

        trigger.blur();
        matches.mockRestore();

        trigger.setAttribute('aria-label', 'Show navigation');
        const mouseEnter = new Event('pointerenter');
        Object.defineProperty(mouseEnter, 'pointerType', { value: 'mouse' });
        trigger.dispatchEvent(mouseEnter);

        expect(tooltip?.getAttribute('aria-hidden')).toBe('false');
        expect(tooltip?.classList.contains('is-open')).toBe(true);
        expect(tooltip?.textContent).toBe('Show navigation');

        trigger.dispatchEvent(new Event('pointerleave'));
        await nextFrame();
        expect(tooltip?.getAttribute('aria-hidden')).toBe('true');
    });

    it('keeps lifecycle on the trigger while sizing and positioning from a distinct target', async () => {
        document.body.innerHTML = `
            <form id="search-form">
                <input id="search-trigger" aria-expanded="false">
                <div id="search-surface" inert></div>
            </form>
        `;

        const form = elementById<HTMLFormElement>('search-form');
        const trigger = elementById<HTMLInputElement>('search-trigger');
        const surface = elementById('search-surface');
        form.getBoundingClientRect = () => ({
            bottom: 80,
            height: 40,
            left: 120,
            right: 540,
            top: 40,
            width: 420,
            x: 120,
            y: 40,
            toJSON: () => ({}),
        });
        trigger.getBoundingClientRect = () => ({
            bottom: 76,
            height: 32,
            left: 168,
            right: 348,
            top: 44,
            width: 180,
            x: 168,
            y: 44,
            toJSON: () => ({}),
        });
        surface.getBoundingClientRect = () => {
            const width = Number.parseFloat(surface.style.width) || 0;

            return {
                bottom: 120,
                height: 120,
                left: 0,
                right: width,
                top: 0,
                width,
                x: 0,
                y: 0,
                toJSON: () => ({}),
            };
        };

        const controller = createController(trigger, surface, {
            layer: 'popover',
            owner: form,
            positionTarget: form,
            placement: 'bottom-start',
            matchWidth: true,
            manageVisibility: false,
            manageInert: true,
            manageExpanded: true,
        });

        expect(surface.hasAttribute('inert')).toBe(true);
        trigger.focus();
        controller.open();
        await nextFrame();

        expect(trigger.getAttribute('aria-expanded')).toBe('true');
        expect(surface.hasAttribute('inert')).toBe(false);
        expect(surface.style.width).toBe('420px');
        expect(surface.style.left).toBe('120px');
        expect(surface.style.top).toBe('88px');

        document.dispatchEvent(new KeyboardEvent('keydown', {
            key: 'Escape',
            bubbles: true,
            cancelable: true,
        }));

        expect(controller.isOpen()).toBe(false);
        expect(trigger.getAttribute('aria-expanded')).toBe('false');
        expect(surface.hasAttribute('inert')).toBe(true);
        expect(document.activeElement).toBe(trigger);
    });

    it('reparents a pre-initialized popover into its modal and preserves its lifecycle', async () => {
        document.body.innerHTML = `
            <main id="page">
                <button id="modal-trigger" type="button" aria-expanded="false">Open settings</button>
                <div id="modal-surface" role="dialog" aria-modal="true" hidden>
                    <button id="modal-first" type="button">Close settings</button>
                    <div id="popover-owner">
                        <button id="popover-trigger" type="button" aria-expanded="false">Data type</button>
                        <div id="popover-surface" role="listbox" hidden>
                            <button id="selected-option" type="button" role="option" aria-selected="true">String</button>
                        </div>
                    </div>
                </div>
            </main>
        `;

        const page = elementById('page');
        const modalTrigger = elementById<HTMLButtonElement>('modal-trigger');
        const modalSurface = elementById('modal-surface');
        const modalFirst = elementById<HTMLButtonElement>('modal-first');
        const popoverOwner = elementById('popover-owner');
        const popoverTrigger = elementById<HTMLButtonElement>('popover-trigger');
        const popoverSurface = elementById('popover-surface');
        const selectedOption = elementById<HTMLButtonElement>('selected-option');
        const popoverController = createController(popoverTrigger, popoverSurface, {
            layer: 'popover',
            owner: popoverOwner,
            positioner: () => null,
            initialFocus: '#selected-option',
        });

        expect(popoverSurface.parentElement?.dataset.uiFloatingRoot).toBe('popover');

        const modalController = createController(modalTrigger, modalSurface, {
            layer: 'modal',
            owner: page,
            positioner: () => null,
            trapFocus: true,
            initialFocus: '#modal-first',
        });

        modalTrigger.focus();
        modalController.open();
        await nextFrame();

        const modalLayer = modalSurface.closest<HTMLElement>('[data-ui-floating-root="modal"]');
        expect(modalLayer).not.toBeNull();
        expect(document.activeElement).toBe(modalFirst);

        popoverController.open();
        await nextFrame();

        const contextRoot = popoverSurface.parentElement;
        expect(popoverSurface.hidden).toBe(false);
        expect(popoverTrigger.getAttribute('aria-expanded')).toBe('true');
        expect(contextRoot?.dataset.uiFloatingContextRoot).toBe('popover');
        expect(modalLayer?.contains(popoverSurface)).toBe(true);
        expect(document.activeElement).toBe(selectedOption);

        const tab = new KeyboardEvent('keydown', {
            key: 'Tab',
            bubbles: true,
            cancelable: true,
        });
        document.dispatchEvent(tab);

        expect(tab.defaultPrevented).toBe(true);
        expect(document.activeElement).toBe(modalFirst);
        expect(popoverController.isOpen()).toBe(true);
        expect(modalController.isOpen()).toBe(true);

        document.dispatchEvent(new KeyboardEvent('keydown', {
            key: 'Escape',
            bubbles: true,
            cancelable: true,
        }));

        expect(popoverController.isOpen()).toBe(false);
        expect(popoverSurface.hidden).toBe(true);
        expect(popoverTrigger.getAttribute('aria-expanded')).toBe('false');
        expect(document.activeElement).toBe(popoverTrigger);
        expect(modalController.isOpen()).toBe(true);
        expect(modalSurface.hidden).toBe(false);

        popoverController.open();
        await nextFrame();
        popoverOwner.remove();
        await Promise.resolve();
        await nextFrame();

        expect(popoverController.isConnected()).toBe(false);
        expect(popoverSurface.isConnected).toBe(false);
        expect(contextRoot?.isConnected).toBe(false);
    });

    it('stacks nested popovers initialized child-first and cascades the modal close', async () => {
        document.body.innerHTML = `
            <main id="nested-page">
                <button id="nested-modal-trigger" type="button">Open settings</button>
                <div id="nested-modal-surface" role="dialog" aria-modal="true" hidden>
                    <button id="nested-modal-first" type="button">Close settings</button>
                    <div id="parent-popover-owner">
                        <button id="parent-popover-trigger" type="button" aria-expanded="false">Table settings</button>
                        <div id="parent-popover-surface" role="dialog" hidden>
                            <button id="parent-popover-action" type="button">Columns</button>
                            <div id="child-popover-owner">
                                <button id="child-popover-trigger" type="button" aria-expanded="false">Column type</button>
                                <div id="child-popover-surface" role="listbox" hidden>
                                    <button id="child-popover-option" type="button" role="option">String</button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </main>
        `;

        const page = elementById('nested-page');
        const modalTrigger = elementById<HTMLButtonElement>('nested-modal-trigger');
        const modalSurface = elementById('nested-modal-surface');
        const modalFirst = elementById<HTMLButtonElement>('nested-modal-first');
        const parentOwner = elementById('parent-popover-owner');
        const parentTrigger = elementById<HTMLButtonElement>('parent-popover-trigger');
        const parentSurface = elementById('parent-popover-surface');
        const childOwner = elementById('child-popover-owner');
        const childTrigger = elementById<HTMLButtonElement>('child-popover-trigger');
        const childSurface = elementById('child-popover-surface');
        const childOption = elementById<HTMLButtonElement>('child-popover-option');
        const childController = createController(childTrigger, childSurface, {
            layer: 'popover',
            owner: childOwner,
            positioner: () => null,
            initialFocus: '#child-popover-option',
        });
        const parentController = createController(parentTrigger, parentSurface, {
            layer: 'popover',
            owner: parentOwner,
            positioner: () => null,
            initialFocus: '#parent-popover-action',
        });
        const modalController = createController(modalTrigger, modalSurface, {
            layer: 'modal',
            owner: page,
            positioner: () => null,
            trapFocus: true,
            initialFocus: '#nested-modal-first',
        });

        modalTrigger.focus();
        modalController.open();
        await nextFrame();
        parentController.open();
        await nextFrame();
        childController.open();
        await nextFrame();

        expect(parentSurface.parentElement?.dataset.uiFloatingContextRoot).toBe('popover');
        expect(childSurface.parentElement).toBe(parentSurface.parentElement);
        expect(stackIndex(childSurface)).toBeGreaterThan(stackIndex(parentSurface));
        expect(document.activeElement).toBe(childOption);

        const tab = new KeyboardEvent('keydown', {
            key: 'Tab',
            bubbles: true,
            cancelable: true,
        });
        document.dispatchEvent(tab);

        expect(tab.defaultPrevented).toBe(true);
        expect(document.activeElement).toBe(modalFirst);
        expect(childController.isOpen()).toBe(true);
        expect(parentController.isOpen()).toBe(true);

        document.dispatchEvent(new KeyboardEvent('keydown', {
            key: 'Escape',
            bubbles: true,
            cancelable: true,
        }));

        expect(childController.isOpen()).toBe(false);
        expect(parentController.isOpen()).toBe(true);
        expect(modalController.isOpen()).toBe(true);
        expect(document.activeElement).toBe(childTrigger);

        childController.open();
        await nextFrame();
        modalController.close({ reason: 'test-parent-close' });

        expect(childController.isOpen()).toBe(false);
        expect(parentController.isOpen()).toBe(false);
        expect(modalController.isOpen()).toBe(false);
        expect(document.activeElement).toBe(modalTrigger);
        expect(document.activeElement).not.toBe(childTrigger);
    });

    it('mounts a child modal contextually and closes it with its parent', async () => {
        document.body.innerHTML = `
            <main id="modal-page">
                <button id="outer-modal-trigger" type="button">Open settings</button>
                <div id="outer-modal-surface" role="dialog" aria-modal="true" hidden>
                    <button id="outer-modal-first" type="button">Close settings</button>
                    <button id="child-modal-trigger" type="button">Delete field</button>
                    <div id="child-modal-surface" role="alertdialog" aria-modal="true" hidden>
                        <button id="child-modal-safe-action" type="button">Cancel deletion</button>
                    </div>
                </div>
            </main>
        `;

        const page = elementById('modal-page');
        const outerTrigger = elementById<HTMLButtonElement>('outer-modal-trigger');
        const outerSurface = elementById('outer-modal-surface');
        const childTrigger = elementById<HTMLButtonElement>('child-modal-trigger');
        const childSurface = elementById('child-modal-surface');
        const childSafeAction = elementById<HTMLButtonElement>('child-modal-safe-action');
        const childController = createController(childTrigger, childSurface, {
            layer: 'modal',
            owner: outerSurface,
            positioner: () => null,
            trapFocus: true,
            initialFocus: '#child-modal-safe-action',
        });
        const outerController = createController(outerTrigger, outerSurface, {
            layer: 'modal',
            owner: page,
            positioner: () => null,
            trapFocus: true,
            initialFocus: '#outer-modal-first',
        });

        outerTrigger.focus();
        outerController.open();
        await nextFrame();
        childController.open();
        await nextFrame();

        expect(childSurface.parentElement?.dataset.uiFloatingContextRoot).toBe('modal');
        expect(outerSurface.contains(childSurface)).toBe(true);
        expect(document.activeElement).toBe(childSafeAction);

        document.dispatchEvent(new KeyboardEvent('keydown', {
            key: 'Escape',
            bubbles: true,
            cancelable: true,
        }));

        expect(childController.isOpen()).toBe(false);
        expect(outerController.isOpen()).toBe(true);
        expect(document.activeElement).toBe(childTrigger);

        childController.open();
        await nextFrame();
        outerController.close({ reason: 'test-parent-close' });

        expect(childController.isOpen()).toBe(false);
        expect(outerController.isOpen()).toBe(false);
        expect(document.activeElement).toBe(outerTrigger);

        outerController.open();
        await nextFrame();
        childController.open();
        await nextFrame();
        outerController.destroy({ restore: true });

        expect(childController.isOpen()).toBe(false);
        expect(outerController.isOpen()).toBe(false);
        expect(childSurface.hidden).toBe(true);
    });
});
