import { mount } from '@vue/test-utils';
import {
    nextTick,
    ref,
} from 'vue';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
    KviewConfirmDialog,
    KviewDialog,
    KviewUiProvider,
} from '@kview/ui-core/vue';
import type { KviewConfirmVariant } 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('KviewConfirmDialog', () => {
    it('uses the provider translations when its copy is not overridden', async () => {
        const wrapper = mount({
            components: {
                KviewConfirmDialog,
                KviewUiProvider,
            },
            data: () => ({ open: true }),
            template: `
                <KviewUiProvider :messages="{
                    common: { cancel: 'Annuler', confirm: 'Confirmer' },
                    confirm: {
                        title: 'Confirmer l’action',
                        message: 'Voulez-vous vraiment continuer ?',
                    },
                }">
                    <KviewConfirmDialog id="localized-confirm" v-model:open="open" />
                </KviewUiProvider>
            `,
        }, { attachTo: document.body });

        await flush();
        const dialog = document.body.querySelector<HTMLElement>('#localized-confirm');

        expect(dialog?.textContent).toContain('Confirmer l’action');
        expect(dialog?.textContent).toContain('Voulez-vous vraiment continuer ?');
        expect(dialog?.querySelector('.ui-confirm-dialog__cancel')?.textContent?.trim()).toBe('Annuler');
        expect(dialog?.querySelector('.ui-confirm-dialog__accept')?.textContent?.trim()).toBe('Confirmer');
        wrapper.unmount();
    });

    it('renders the shared alert-dialog contract and guards confirm while disabled or pending', async () => {
        const open = ref(false);
        const pending = ref(false);
        const disabled = ref(false);
        const variant = ref<KviewConfirmVariant>('danger');
        const onConfirm = vi.fn();
        const onCancel = vi.fn();
        const wrapper = mount({
            components: { KviewConfirmDialog },
            setup: () => ({
                disabled,
                onCancel,
                onConfirm,
                open,
                pending,
                variant,
            }),
            template: `
                <div>
                    <button id="delete-trigger" type="button" aria-haspopup="dialog" @click="open = true">
                        Delete rate
                    </button>
                    <KviewConfirmDialog
                        id="delete-rate-confirm"
                        v-model:open="open"
                        title="Delete exchange rate?"
                        message="This exchange-rate observation will be permanently removed."
                        confirm-label="Delete exchange rate"
                        :variant="variant"
                        :pending="pending"
                        :disabled="disabled"
                        @confirm="onConfirm"
                        @cancel="onCancel"
                    />
                </div>
            `,
        }, { attachTo: document.body });

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

        const dialog = document.body.querySelector<HTMLElement>('#delete-rate-confirm');
        const cancel = document.body.querySelector<HTMLButtonElement>('.ui-confirm-dialog__cancel');
        const accept = document.body.querySelector<HTMLButtonElement>('.ui-confirm-dialog__accept');

        expect(dialog?.getAttribute('role')).toBe('alertdialog');
        expect(dialog?.getAttribute('aria-modal')).toBe('true');
        expect(dialog?.parentElement?.parentElement).toBe(document.body);
        expect(dialog?.classList.contains('ui-confirm-dialog__surface')).toBe(true);
        expect(dialog?.querySelector('.ui-confirm-dialog__copy')?.getAttribute('aria-hidden')).toBe('true');
        expect(dialog?.querySelector('.ui-confirm-dialog__copy h2')?.textContent).toBe('Delete exchange rate?');
        expect(dialog?.querySelector('.ui-confirm-dialog__copy p')?.textContent).toContain('permanently removed');
        expect(dialog?.querySelector('.ui-confirm-dialog__icon svg')).not.toBeNull();
        expect(cancel?.textContent?.trim()).toBe('Cancel');
        expect(accept?.classList.contains('btn-danger')).toBe(true);
        expect(document.activeElement).toBe(cancel);

        accept?.click();
        expect(onConfirm).toHaveBeenCalledTimes(1);
        expect(document.body.querySelector('#delete-rate-confirm')).not.toBeNull();

        variant.value = 'primary';
        await flush();
        expect(accept?.classList.contains('btn-primary')).toBe(true);
        expect(accept?.classList.contains('btn-danger')).toBe(false);

        disabled.value = true;
        await flush();
        expect(accept?.disabled).toBe(true);
        accept?.click();
        expect(onConfirm).toHaveBeenCalledTimes(1);

        disabled.value = false;
        pending.value = true;
        await flush();
        expect(accept?.disabled).toBe(true);
        expect(accept?.getAttribute('aria-busy')).toBe('true');
        expect(cancel?.disabled).toBe(true);

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

        expect(document.body.querySelector('#delete-rate-confirm')).not.toBeNull();
        expect(onCancel).not.toHaveBeenCalled();

        pending.value = false;
        await flush();
        cancel?.click();
        await flush();

        expect(onCancel).toHaveBeenCalledWith('cancel');
        expect(document.body.querySelector('#delete-rate-confirm')).toBeNull();
        expect(document.activeElement).toBe(trigger.element);
        wrapper.unmount();
    });

    it('exposes show/close and treats the backdrop as a focus-restoring cancellation', async () => {
        const trigger = document.createElement('button');
        const onCancel = vi.fn();

        trigger.type = 'button';
        trigger.textContent = 'Open confirmation';
        document.body.append(trigger);

        const wrapper = mount(KviewConfirmDialog, {
            props: {
                id: 'exposed-confirm',
                title: 'Confirm action?',
                message: 'This action cannot be undone.',
                onCancel,
            },
            attachTo: document.body,
        });

        trigger.focus();
        wrapper.vm.show(trigger);
        await flush();

        expect(document.body.querySelector('#exposed-confirm')).not.toBeNull();
        expect(document.activeElement).toBe(
            document.body.querySelector('.ui-confirm-dialog__cancel'),
        );

        const overlay = document.body.querySelector<HTMLElement>('.ui-dialog-overlay');
        overlay?.dispatchEvent(new PointerEvent('pointerdown', {
            button: 0,
            bubbles: true,
            cancelable: true,
        }));
        overlay?.click();
        await flush();

        expect(document.body.querySelector('#exposed-confirm')).toBeNull();
        expect(onCancel).toHaveBeenCalledWith('backdrop');
        expect(document.activeElement).toBe(trigger);

        wrapper.vm.show(trigger);
        await flush();
        wrapper.vm.close();
        await flush();

        expect(document.body.querySelector('#exposed-confirm')).toBeNull();
        expect(onCancel).toHaveBeenCalledTimes(1);
        expect(document.activeElement).toBe(trigger);
        wrapper.unmount();
    });

    it('keeps a nested confirmation topmost and restores each dialog trigger on Escape', async () => {
        const onCancel = vi.fn();
        const wrapper = mount({
            components: {
                KviewConfirmDialog,
                KviewDialog,
            },
            data: () => ({
                confirmOpen: false,
                parentOpen: false,
            }),
            setup: () => ({ onCancel }),
            template: `
                <div>
                    <button id="parent-origin" type="button" @click="parentOpen = true">
                        Open parent
                    </button>
                    <KviewDialog
                        id="parent-dialog"
                        v-model:open="parentOpen"
                        label="Parent dialog"
                    >
                        <button id="nested-confirm-trigger" type="button" @click="confirmOpen = true">
                            Delete item
                        </button>
                        <KviewConfirmDialog
                            id="nested-confirm-dialog"
                            v-model:open="confirmOpen"
                            title="Delete item?"
                            message="The item will be permanently removed."
                            confirm-label="Delete item"
                            @cancel="onCancel"
                        />
                    </KviewDialog>
                </div>
            `,
        }, { attachTo: document.body });

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

        const nestedTrigger = document.body.querySelector<HTMLButtonElement>('#nested-confirm-trigger');
        nestedTrigger?.focus();
        nestedTrigger?.click();
        await flush();

        expect(document.body.querySelectorAll('.ui-dialog-layer')).toHaveLength(2);
        expect(document.body.querySelector('#nested-confirm-dialog')?.getAttribute('role')).toBe('alertdialog');
        expect(document.activeElement).toBe(
            document.body.querySelector('#nested-confirm-dialog .ui-confirm-dialog__cancel'),
        );

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

        expect(document.body.querySelector('#nested-confirm-dialog')).toBeNull();
        expect(document.body.querySelector('#parent-dialog')).not.toBeNull();
        expect(onCancel).toHaveBeenCalledWith('escape');
        expect(document.activeElement).toBe(nestedTrigger);

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

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