import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
    KviewCheckbox,
    KviewMenu,
    KviewSeparator,
} 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('KviewCheckbox', () => {
    it('toggles between unchecked, checked and indeterminate with correct ARIA', async () => {
        const wrapper = mount({
            components: { KviewCheckbox },
            data: () => ({ value: 'indeterminate' as boolean | 'indeterminate' }),
            template: `
                <form>
                    <KviewCheckbox
                        id="select_all"
                        name="select_all"
                        v-model="value"
                        label="Select all"
                    />
                </form>
            `,
        }, { attachTo: document.body });

        const control = wrapper.get<HTMLButtonElement>('#select_all');
        expect(control.attributes('role')).toBe('checkbox');
        expect(control.attributes('aria-checked')).toBe('mixed');

        await control.trigger('click');
        await flush();

        expect(wrapper.vm.value).toBe(true);
        expect(control.attributes('aria-checked')).toBe('true');

        await control.trigger('click');
        await flush();

        expect(wrapper.vm.value).toBe(false);
        expect(control.attributes('aria-checked')).toBe('false');

        wrapper.unmount();
    });

    it('associates its description for assistive technology', () => {
        const wrapper = mount({
            components: { KviewCheckbox },
            data: () => ({ value: false }),
            template: `
                <KviewCheckbox
                    id="notify"
                    name="notify"
                    v-model="value"
                    label="Email notifications"
                    description="Sent once per day."
                />
            `,
        }, { attachTo: document.body });

        const control = wrapper.get<HTMLButtonElement>('#notify');
        const describedBy = control.attributes('aria-describedby');
        expect(describedBy).toBeTruthy();
        expect(document.getElementById(describedBy as string)?.textContent).toBe('Sent once per day.');

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

describe('KviewSeparator', () => {
    it('is decorative by default and exposes role=separator when it carries meaning', () => {
        const decorative = mount(KviewSeparator);
        expect(decorative.attributes('role')).toBe('none');

        const semantic = mount(KviewSeparator, {
            props: { decorative: false, orientation: 'vertical' },
        });
        expect(semantic.attributes('role')).toBe('separator');
        expect(semantic.attributes('aria-orientation')).toBe('vertical');
    });
});

describe('KviewMenu', () => {
    it('opens from its trigger, navigates with arrow keys, and emits on select', async () => {
        const onSelect = vi.fn();
        const wrapper = mount({
            components: { KviewMenu },
            data: () => ({
                items: [
                    { value: 'edit', label: 'Edit', icon: 'edit' },
                    { value: 'delete', label: 'Delete', danger: true, separatorBefore: true },
                ],
            }),
            methods: { onSelect },
            template: `
                <KviewMenu :items="items" label="Row actions" @select="onSelect">
                    <button id="menu-trigger" type="button">Actions</button>
                </KviewMenu>
            `,
        }, { attachTo: document.body });

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

        const menu = document.body.querySelector<HTMLElement>('[role="menu"]');
        expect(menu).not.toBeNull();
        expect(menu?.getAttribute('aria-label')).toBe('Row actions');

        const menuItems = Array.from(document.body.querySelectorAll<HTMLElement>('[role="menuitem"]'));
        expect(menuItems).toHaveLength(2);
        expect(menuItems[0]?.textContent).toContain('Edit');
        expect(menuItems[1]?.textContent).toContain('Delete');

        const deleteItem = menuItems[1] as HTMLElement;
        deleteItem.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }));
        deleteItem.click();
        await flush();

        expect(onSelect).toHaveBeenCalledTimes(1);
        expect(onSelect.mock.calls[0]?.[0]).toMatchObject({ value: 'delete' });
        expect(document.body.querySelector('[role="menu"]')).toBeNull();

        wrapper.unmount();
    });

    it('closes on Escape and restores focus to the trigger', async () => {
        const wrapper = mount({
            components: { KviewMenu },
            data: () => ({
                items: [{ value: 'view', label: 'View' }],
            }),
            template: `
                <KviewMenu :items="items">
                    <button id="menu-trigger-2" type="button">Open</button>
                </KviewMenu>
            `,
        }, { attachTo: document.body });

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

        expect(document.body.querySelector('[role="menu"]')).not.toBeNull();

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

        expect(document.body.querySelector('[role="menu"]')).toBeNull();
        expect(document.activeElement).toBe(trigger.element);

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