import { nextTick } from 'vue';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { column, mountFull } from './support/kviewCrudTableFixtures';

/**
 * Column view-state behavior owned by `useKviewCrudTableColumns`: visibility
 * persistence, keyboard/pointer reorder and resize, and the width bounds
 * shared with the ARIA `role="separator"` handle. Also covers the persistent
 * horizontal scrollbar (`useKviewCrudTableScroll`), whose overflow state
 * feeds the same fitted-width computation.
 */
afterEach(() => {
    document.body.innerHTML = '';
    vi.restoreAllMocks();
});

beforeEach(() => {
    window.localStorage.clear();
});

describe('KviewCrudTable columns', () => {
    it('persists column visibility under the shared Blade storage contract', async () => {
        mountFull();

        document.querySelector<HTMLButtonElement>('.erp-table-settings-trigger')!.click();
        await nextTick();

        const toggles = Array.from(document.querySelectorAll<HTMLInputElement>('#lot2-table_settings input[type="checkbox"]'));
        const fundsToggle = toggles[1];
        expect(fundsToggle).toBeDefined();
        fundsToggle!.dispatchEvent(new Event('change', { bubbles: true }));
        await nextTick();

        const saved = JSON.parse(window.localStorage.getItem('kview_investor.erp-table.lot2-table') ?? '{}');
        expect(saved.hidden).toContain('funds_count');
        expect(saved.order).toEqual(['name', 'funds_count', 'internal']);
    });

    it('renders and synchronizes the persistent horizontal scrollbar for overflowing data', async () => {
        const wrapper = mountFull();
        const scroller = wrapper.get('.erp-table-main-scroll').element as HTMLElement;
        const persistentScroller = wrapper.get('.erp-table-persistent-horizontal-scroll').element as HTMLElement;

        Object.defineProperties(scroller, {
            clientWidth: { configurable: true, value: 500 },
            scrollWidth: { configurable: true, value: 1_000 },
            scrollLeft: { configurable: true, writable: true, value: 0 },
        });
        Object.defineProperties(persistentScroller, {
            clientWidth: { configurable: true, value: 500 },
            scrollWidth: { configurable: true, value: 1_000 },
            scrollLeft: { configurable: true, writable: true, value: 0 },
        });

        scroller.dispatchEvent(new Event('scroll'));
        await nextTick();
        await nextTick();
        expect(wrapper.get('.erp-table-scroll').classes()).toContain('is-horizontally-scrollable');
        expect(wrapper.get('.erp-table-scroll').classes()).not.toContain('is-scrolled-x');
        expect(wrapper.get('.erp-table-persistent-horizontal-scroll').isVisible()).toBe(true);
        expect(wrapper.get('.erp-table-persistent-horizontal-scroll__track').attributes('style')).toContain('width: 1000px');
        expect(wrapper.get('.erp-table-persistent-horizontal-scroll').attributes('aria-controls')).toBe('lot2-table_main_scroll');
        expect(persistentScroller.parentElement).toBe(wrapper.get('.erp-table-shell').element);
        expect(persistentScroller.nextElementSibling).toBe(wrapper.get('.erp-table-footer').element);

        scroller.scrollLeft = 64;
        scroller.dispatchEvent(new Event('scroll'));
        await nextTick();
        expect(wrapper.get('.erp-table-scroll').classes()).toContain('is-scrolled-x');
        expect(persistentScroller.scrollLeft).toBe(64);

        persistentScroller.scrollLeft = 180;
        persistentScroller.dispatchEvent(new Event('scroll'));
        await nextTick();
        await nextTick();
        expect(scroller.scrollLeft).toBe(180);
        expect(wrapper.get('.erp-table-scroll').classes()).toContain('is-scrolled-x');

        persistentScroller.scrollLeft = 0;
        persistentScroller.dispatchEvent(new Event('scroll'));
        await nextTick();
        await nextTick();
        expect(scroller.scrollLeft).toBe(0);
        expect(wrapper.get('.erp-table-scroll').classes()).not.toContain('is-scrolled-x');

        Object.defineProperty(scroller, 'scrollWidth', { configurable: true, value: 500 });
        scroller.dispatchEvent(new Event('scroll'));
        await nextTick();
        await nextTick();
        expect(wrapper.get('.erp-table-scroll').classes()).not.toContain('is-horizontally-scrollable');
        expect(wrapper.get('.erp-table-persistent-horizontal-scroll').isVisible()).toBe(false);
    });

    it('reorders columns from the keyboard with Alt+Arrow and persists the order', async () => {
        mountFull();

        document.querySelector<HTMLButtonElement>('.erp-table-settings-trigger')!.click();
        await nextTick();

        const handle = document.querySelector<HTMLElement>('.erp-column-manager-item:first-child .erp-drag-handle');
        expect(handle).not.toBeNull();
        handle!.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', altKey: true, bubbles: true }));
        await nextTick();

        const labels = Array.from(document.querySelectorAll('.erp-column-manager-toggle span')).map((s) => s.textContent);
        expect(labels.slice(0, 2)).toEqual(['Funds', 'GP']);

        const saved = JSON.parse(window.localStorage.getItem('kview_investor.erp-table.lot2-table') ?? '{}');
        expect(saved.order.slice(0, 2)).toEqual(['funds_count', 'name']);
    });

    it('clamps restored and proportionally rendered widths to the real ARIA bounds', async () => {
        vi.spyOn(HTMLElement.prototype, 'clientWidth', 'get').mockReturnValue(3_000);
        window.localStorage.setItem('kview_investor.erp-table.lot2-table', JSON.stringify({
            hidden: ['internal'],
            order: ['name', 'funds_count', 'internal'],
            widths: { name: 1_200 },
        }));

        const constrainedColumns = [
            column({ key: 'name', label: 'GP', type: 'link', width: '240px', minWidth: '190px' }),
            column({ key: 'funds_count', label: 'Funds', type: 'number', width: '160px' }),
            column({ key: 'internal', label: 'Internal', hidden: true }),
        ];
        const wrapper = mountFull({ columns: constrainedColumns });
        await nextTick();

        const separator = document.querySelector<HTMLElement>('.erp-column-resize[role="separator"]');
        expect(separator).not.toBeNull();
        expect(separator!.getAttribute('aria-valuemin')).toBe('190');
        expect(separator!.getAttribute('aria-valuemax')).toBe('800');
        expect(separator!.getAttribute('aria-valuenow')).toBe('800');
        expect(separator!.getAttribute('aria-valuetext')).toBe('800 pixels');

        const firstDataHeader = wrapper.get('th:not(.erp-table-select-head):not(.erp-table-actions-head)');
        expect(firstDataHeader.attributes('style')).toContain('width: 800px');
        expect(firstDataHeader.attributes('style')).toContain('min-width: 190px');
        expect(firstDataHeader.attributes('style')).toContain('max-width: 800px');
    });

    it('clamps pointer resizing to column minWidth and the shared 800px maximum', async () => {
        const constrainedColumns = [
            column({ key: 'name', label: 'GP', type: 'link', width: '240px', minWidth: '190px' }),
        ];
        const wrapper = mountFull({ columns: constrainedColumns });

        const separator = document.querySelector<HTMLElement>('.erp-column-resize[role="separator"]');
        expect(separator).not.toBeNull();

        separator!.dispatchEvent(new MouseEvent('pointerdown', { clientX: 240, bubbles: true, cancelable: true }));
        window.dispatchEvent(new MouseEvent('pointermove', { clientX: -1_000 }));
        await nextTick();
        expect(separator!.getAttribute('aria-valuenow')).toBe('190');
        expect(wrapper.get('th:not(.erp-table-select-head):not(.erp-table-actions-head)').attributes('style'))
            .toContain('width: 190px');
        window.dispatchEvent(new MouseEvent('pointerup'));

        let saved = JSON.parse(window.localStorage.getItem('kview_investor.erp-table.lot2-table') ?? '{}');
        expect(saved.widths.name).toBe(190);

        separator!.dispatchEvent(new MouseEvent('pointerdown', { clientX: 190, bubbles: true, cancelable: true }));
        window.dispatchEvent(new MouseEvent('pointermove', { clientX: 2_000 }));
        await nextTick();
        expect(separator!.getAttribute('aria-valuenow')).toBe('800');
        window.dispatchEvent(new MouseEvent('pointerup'));

        saved = JSON.parse(window.localStorage.getItem('kview_investor.erp-table.lot2-table') ?? '{}');
        expect(saved.widths.name).toBe(800);
    });

    it('clamps keyboard resizing at the same maximum and column minWidth announced by ARIA', async () => {
        window.localStorage.setItem('kview_investor.erp-table.lot2-table', JSON.stringify({
            hidden: [],
            order: ['name'],
            widths: { name: 795 },
        }));
        const constrainedColumns = [
            column({ key: 'name', label: 'GP', type: 'link', width: '240px', minWidth: '190px' }),
        ];
        mountFull({ columns: constrainedColumns });

        const separator = document.querySelector<HTMLElement>('.erp-column-resize[role="separator"]');
        expect(separator).not.toBeNull();
        separator!.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
        await nextTick();

        let saved = JSON.parse(window.localStorage.getItem('kview_investor.erp-table.lot2-table') ?? '{}');
        expect(saved.widths.name).toBe(800);
        expect(separator!.getAttribute('aria-valuenow')).toBe('800');

        for (let index = 0; index < 20; index += 1) {
            separator!.dispatchEvent(new KeyboardEvent('keydown', {
                key: 'ArrowLeft',
                shiftKey: true,
                bubbles: true,
            }));
        }
        await nextTick();

        saved = JSON.parse(window.localStorage.getItem('kview_investor.erp-table.lot2-table') ?? '{}');
        expect(saved.widths.name).toBe(190);
        expect(separator!.getAttribute('aria-valuemin')).toBe('190');
        expect(separator!.getAttribute('aria-valuenow')).toBe('190');
        expect(separator!.getAttribute('aria-valuemax')).toBe('800');
    });
});
