import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
    KviewDialog,
    KviewDropdown,
} from '@kview/ui-core/vue';

const flush = async (): Promise<void> => {
    await nextTick();
    await new Promise((resolve) => setTimeout(resolve, 0));
    await nextTick();
};

const originalResizeObserver = window.ResizeObserver;
const originalPointerEvent = window.PointerEvent;

beforeEach(() => {
    class ResizeObserverStub {
        observe(): void {}

        unobserve(): void {}

        disconnect(): void {}
    }

    Object.defineProperty(window, 'ResizeObserver', {
        configurable: true,
        value: ResizeObserverStub,
    });

    if (!window.PointerEvent) {
        Object.defineProperty(window, 'PointerEvent', {
            configurable: true,
            value: MouseEvent,
        });
    }

    Element.prototype.scrollIntoView = vi.fn();
    HTMLElement.prototype.hasPointerCapture = vi.fn(() => false);
    HTMLElement.prototype.releasePointerCapture = vi.fn();
});

afterEach(() => {
    document.body.innerHTML = '';
    Object.defineProperty(window, 'ResizeObserver', {
        configurable: true,
        value: originalResizeObserver,
    });
    Object.defineProperty(window, 'PointerEvent', {
        configurable: true,
        value: originalPointerEvent,
    });
    vi.restoreAllMocks();
});

describe('KviewDialog', () => {
    it('teleports a modal, traps its focus lifecycle and restores the trigger on Escape', async () => {
        const wrapper = mount({
            components: { KviewDialog },
            data: () => ({ open: false }),
            template: `
                <div id="dialog-background">
                    <button id="dialog-trigger" type="button" @click="open = true">Open settings</button>
                    <KviewDialog
                        id="test-settings-dialog"
                        v-model:open="open"
                        label="Settings"
                        description="Settings comparison"
                        initial-focus-selector="#dialog-close"
                    >
                        <button id="dialog-close" type="button" @click="open = false">Close</button>
                        <a href="/preferences">Blade comparison</a>
                    </KviewDialog>
                </div>
            `,
        }, { attachTo: document.body });

        const trigger = wrapper.get<HTMLButtonElement>('#dialog-trigger');
        trigger.element.focus();
        await trigger.trigger('click');
        await flush();

        const dialog = document.body.querySelector<HTMLElement>('#test-settings-dialog');
        expect(dialog).not.toBeNull();
        expect(dialog?.getAttribute('role')).toBe('dialog');
        expect(dialog?.getAttribute('aria-modal')).toBe('true');
        expect(dialog?.parentElement?.classList.contains('ui-dialog-layer')).toBe(true);
        expect(dialog?.parentElement?.parentElement).toBe(document.body);
        expect(dialog?.getAttribute('aria-labelledby')).toBeTruthy();
        expect(document.activeElement?.id).toBe('dialog-close');

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

        expect(document.body.querySelector('#test-settings-dialog')).toBeNull();
        expect(document.activeElement).toBe(trigger.element);
        wrapper.unmount();
    });

    it('keeps a portaled Select and a nested confirmation above the parent dialog', async () => {
        const wrapper = mount({
            components: { KviewDialog, KviewDropdown },
            data: () => ({
                open: false,
                confirmOpen: false,
                value: 'EUR',
            }),
            template: `
                <div>
                    <button id="nested-origin" type="button" @click="open = true">Open Settings</button>
                    <KviewDialog
                        id="nested-settings"
                        v-model:open="open"
                        label="Settings"
                        description="Nested settings contract"
                    >
                        <KviewDropdown
                            id="nested-currency"
                            v-model="value"
                            name="currency"
                            label="Currency"
                            :options="[
                                { value: 'EUR', label: 'EUR' },
                                { value: 'USD', label: 'USD' },
                            ]"
                        />
                        <button id="nested-delete" type="button" @click="confirmOpen = true">
                            Delete field
                        </button>
                        <KviewDialog
                            id="nested-confirm"
                            v-model:open="confirmOpen"
                            label="Delete field?"
                            description="Confirm dynamic field deletion"
                            initial-focus-selector="#nested-cancel"
                        >
                            <button id="nested-cancel" type="button" @click="confirmOpen = false">Cancel</button>
                        </KviewDialog>
                    </KviewDialog>
                </div>
            `,
        }, { attachTo: document.body });

        const origin = wrapper.get<HTMLButtonElement>('#nested-origin');
        origin.element.focus();
        await origin.trigger('click');
        await flush();

        const select = document.body.querySelector<HTMLButtonElement>('#nested-currency');
        expect(select).not.toBeNull();
        select?.dispatchEvent(new PointerEvent('pointerdown', {
            button: 0,
            bubbles: true,
        }));
        await flush();

        const menu = document.body.querySelector<HTMLElement>('.ui-dropdown-menu--reka');
        expect(menu?.classList.contains('ui-dropdown-menu--modal')).toBe(true);
        expect(menu?.closest('[data-reka-popper-content-wrapper]')?.parentElement).toBe(document.body);

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

        expect(document.body.querySelector('[role="listbox"]')).toBeNull();
        expect(document.body.querySelector('#nested-settings')).not.toBeNull();
        expect(document.activeElement).toBe(select);

        const deleteButton = document.body.querySelector<HTMLButtonElement>('#nested-delete');
        expect(deleteButton).not.toBeNull();
        deleteButton?.focus();
        deleteButton?.click();
        await flush();

        const layers = document.body.querySelectorAll('.ui-dialog-layer');
        expect(layers).toHaveLength(2);
        expect(layers[1]?.querySelector('#nested-confirm')).not.toBeNull();
        expect(document.activeElement?.id).toBe('nested-cancel');

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

        expect(document.body.querySelector('#nested-confirm')).toBeNull();
        expect(document.body.querySelector('#nested-settings')).not.toBeNull();
        expect(document.activeElement).toBe(deleteButton);

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

        expect(document.body.querySelector('#nested-settings')).toBeNull();
        expect(document.activeElement).toBe(origin.element);
        wrapper.unmount();
    });
});

describe('KviewDropdown', () => {
    it('keeps the kView field contract while Reka owns keyboard and listbox behavior', async () => {
        const wrapper = mount({
            components: { KviewDropdown },
            data: () => ({ value: '' }),
            template: `
                <form>
                    <KviewDropdown
                        id="record_type"
                        v-model="value"
                        name="record_type"
                        label="Record type"
                        hint="Choose one record type."
                        :options="[
                            { value: '', label: 'Automatic' },
                            { value: 'fund', label: 'Fund' },
                            { value: 'investor', label: 'Investor', meta: 'Business record' },
                        ]"
                    />
                </form>
            `,
        }, { attachTo: document.body });

        const trigger = wrapper.get<HTMLButtonElement>('#record_type');
        expect(trigger.attributes('role')).toBe('combobox');
        expect(trigger.attributes('aria-describedby')).toContain('record_type_hint');
        expect(wrapper.get<HTMLInputElement>('input[name="record_type"]').element.value).toBe('');

        trigger.element.dispatchEvent(new PointerEvent('pointerdown', {
            button: 0,
            bubbles: true,
        }));
        await flush();

        const listbox = document.body.querySelector<HTMLElement>('[role="listbox"]');
        expect(listbox).not.toBeNull();
        expect(listbox?.parentElement).not.toBe(wrapper.element);
        const selected = listbox?.querySelector<HTMLElement>('[role="option"][aria-selected="true"]');
        expect(selected?.classList.contains('is-selected')).toBe(true);
        expect(selected?.querySelector('.ui-dropdown-option-check')).not.toBeNull();

        const investor = Array.from(document.body.querySelectorAll<HTMLElement>('[role="option"]'))
            .find((option) => option.textContent?.includes('Investor'));
        investor?.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }));
        investor?.click();
        await flush();

        expect(wrapper.get<HTMLInputElement>('input[name="record_type"]').element.value).toBe('investor');
        expect(trigger.text()).toContain('Investor');

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

        expect(document.body.querySelector('[role="listbox"]')).toBeNull();
        expect(document.activeElement).toBe(trigger.element);
        wrapper.unmount();
    });

    it('can hide the decorative selected indicator in a compact consumer', async () => {
        const wrapper = mount({
            components: { KviewDropdown },
            data: () => ({ value: '25' }),
            template: `
                <KviewDropdown
                    id="page_size"
                    v-model="value"
                    name="items_per_page"
                    :options="[
                        { value: '25', label: '25' },
                        { value: '50', label: '50' },
                    ]"
                    :show-selected-indicator="false"
                />
            `,
        }, { attachTo: document.body });

        const trigger = wrapper.get<HTMLButtonElement>('#page_size');
        trigger.element.dispatchEvent(new PointerEvent('pointerdown', {
            button: 0,
            bubbles: true,
        }));
        await flush();

        const selected = document.body.querySelector<HTMLElement>(
            '[role="option"][aria-selected="true"]',
        );
        expect(selected).not.toBeNull();
        expect(selected?.classList.contains('is-selected')).toBe(true);
        expect(selected?.querySelector('.ui-dropdown-option-check')).toBeNull();
        expect(wrapper.get<HTMLInputElement>('input[name="items_per_page"]').element.value).toBe('25');

        wrapper.unmount();
    });
});
