import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { KviewCrudTable } from '@kview/ui-data/vue';
import {
    columns, mountFull, pagination, rows,
} from './support/kviewCrudTableFixtures';

/**
 * Row-level interactions: keyboard activation (shared with `KviewDetailTable`
 * through `useTableRowActivation`), selection + bulk delete, and row-delete
 * confirmation.
 */
afterEach(() => {
    document.body.innerHTML = '';
    vi.restoreAllMocks();
});

describe('KviewCrudTable row interactions', () => {
    it('keeps rows keyboard-activatable through the hidden first-cell link', async () => {
        const wrapper = mount(KviewCrudTable, {
            attachTo: document.body,
            props: {
                id: 'specimen-table',
                title: 'General Partners',
                columns,
                rows,
                sort: { key: null, direction: null },
                search: '',
                pagination: pagination(),
            },
        });
        const row = wrapper.get('tbody tr');

        expect(row.attributes('tabindex')).toBe('0');
        const link = wrapper.get('#specimen-table_row_1_link');
        const click = vi.fn();
        (link.element as HTMLAnchorElement).addEventListener('click', (event) => {
            event.preventDefault();
            click();
        });

        await row.trigger('keydown', { key: 'Enter' });
        expect(click).toHaveBeenCalledTimes(1);
    });

    it('does not swallow keystrokes aimed at a real control inside the row', async () => {
        const rowsWithActions = rows.map((row) => ({
            ...row,
            actions: [
                { type: 'view' as const, label: 'Preview GP', url: `/gps/${row.id}` },
            ],
        }));
        const wrapper = mountFull({ rows: rowsWithActions });

        // Space on the selection checkbox must still toggle it (the row
        // keydown handler must bail out before preventDefault).
        const checkbox = wrapper.get('.erp-table-select-cell input[type="checkbox"]');
        const spaceOnCheckbox = new KeyboardEvent('keydown', { key: ' ', bubbles: true, cancelable: true });
        checkbox.element.dispatchEvent(spaceOnCheckbox);
        expect(spaceOnCheckbox.defaultPrevented).toBe(false);

        // Enter on the GP link must not be cancelled by the row handler.
        const link = wrapper.get('.erp-table-actions-cell a, tbody tr a.erp-row-action-button');
        const enterOnLink = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true });
        link.element.dispatchEvent(enterOnLink);
        expect(enterOnLink.defaultPrevented).toBe(false);

        // Enter on the row background still activates the row link.
        const row = wrapper.get('tbody tr');
        const clickSpy = vi.fn();
        const rowLink = document.getElementById('lot2-table_row_1_link');
        rowLink?.addEventListener('click', (e) => { e.preventDefault(); clickSpy(); });
        row.element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }));
        expect(clickSpy).toHaveBeenCalledTimes(1);
    });

    it('selects rows, wires them to the host bulk form, and submits after confirmation', async () => {
        const form = document.createElement('form');
        form.id = 'lot2-table_bulk_delete';
        document.body.appendChild(form);
        const submit = vi.fn();
        form.requestSubmit = submit;

        const wrapper = mountFull({ createUrl: '/gps/create' });

        const selectAll = document.querySelector<HTMLInputElement>('input[aria-label="Select all visible rows"]');
        selectAll!.dispatchEvent(new Event('change', { bubbles: true }));
        await nextTick();

        const rowBoxes = Array.from(document.querySelectorAll<HTMLInputElement>('.erp-table-select-cell input[type="checkbox"]'));
        expect(rowBoxes.every((box) => box.checked)).toBe(true);
        expect(rowBoxes[0].getAttribute('form')).toBe('lot2-table_bulk_delete');
        // Checkboxes are named after their row, not a generic repeated label.
        expect(rowBoxes[0].getAttribute('aria-label')).toBe('Select Alpha Capital');
        expect(rowBoxes[1].getAttribute('aria-label')).toBe('Select Beta Partners');
        expect(wrapper.text()).toContain('2 selected');

        const toolbar = wrapper.get('.erp-table-toolbar-actions').element;
        const commandOrder = Array.from(toolbar.querySelectorAll<HTMLElement>(
            '.erp-table-import-export, .erp-selection-count, .erp-bulk-delete-trigger, .erp-table-create',
        )).map((element) => {
            if (element.classList.contains('erp-table-import-export')) return 'export';
            if (element.classList.contains('erp-selection-count')) return 'count';
            if (element.classList.contains('erp-bulk-delete-trigger')) return 'delete';
            return 'create';
        });
        expect(commandOrder).toEqual(['export', 'count', 'delete', 'create']);

        const selectionGroup = toolbar.querySelector<HTMLElement>('.erp-table-selection-actions');
        const selectionCount = selectionGroup?.querySelector<HTMLElement>('.erp-selection-count');
        expect(selectionGroup).not.toBeNull();
        expect(selectionCount?.getAttribute('role')).toBe('status');
        expect(selectionCount?.getAttribute('aria-live')).toBe('polite');
        expect(selectionCount?.getAttribute('aria-atomic')).toBe('true');
        expect(selectionGroup?.querySelector('.erp-bulk-delete-trigger')).not.toBeNull();

        const bulkTrigger = document.querySelector<HTMLButtonElement>('.erp-bulk-delete-trigger');
        expect(bulkTrigger!.disabled).toBe(false);
        expect(bulkTrigger!.classList.contains('ui-icon-button--danger')).toBe(true);
        expect(bulkTrigger!.classList.contains('btn-danger')).toBe(false);
        bulkTrigger!.click();
        await nextTick();

        const confirm = document.body.querySelector<HTMLButtonElement>('.ui-confirm-dialog__accept');
        expect(confirm).not.toBeNull();
        confirm!.click();
        await nextTick();

        expect(submit).toHaveBeenCalledTimes(1);
        expect(wrapper.emitted('bulk-delete')?.[0]).toEqual([[1, 2]]);
    });

    it('confirms a row delete then retargets and submits the host row form', async () => {
        const form = document.createElement('form');
        form.id = 'lot2-table_row_delete';
        form.setAttribute('action', '#');
        document.body.appendChild(form);
        const submit = vi.fn();
        form.requestSubmit = submit;

        const rowsWithActions = rows.map((row) => ({
            ...row,
            actions: [
                { type: 'delete' as const, label: 'Delete GP', url: `/gps/${row.id}`, confirm: 'Delete this GP?' },
            ],
        }));

        const wrapper = mountFull({ rows: rowsWithActions });

        const deleteButton = document.querySelector<HTMLButtonElement>('.erp-table-actions-cell button[aria-label="Delete GP"]');
        expect(deleteButton).not.toBeNull();
        deleteButton!.click();
        await nextTick();

        const confirm = document.body.querySelector<HTMLButtonElement>('.ui-confirm-dialog__accept');
        expect(confirm).not.toBeNull();
        confirm!.click();
        await nextTick();

        expect(form.getAttribute('action')).toBe('/gps/1');
        expect(submit).toHaveBeenCalledTimes(1);
        expect(wrapper.emitted('delete')?.[0]?.[1]).toMatchObject({ id: 1 });
    });
});
