import { expect, test, type Page } from '@playwright/test';

/**
 * Phase 7 production cutover: the exhaustive constrained-desktop pixel-parity
 * pass — selection gutter, row-actions overlay, idle/hover edge styling,
 * header/footer alignment, and toolbar geometry — kept isolated from
 * phase7-production-indexes.spec.ts because this single test's assertions
 * all depend on one continuous page session (re-navigating per assertion
 * group would change timing and defeat the parity check).
 */
const email = process.env.E2E_EMAIL;
const password = process.env.E2E_PASSWORD;

if (process.env.CI && (!email || !password)) {
    throw new Error('E2E_EMAIL and E2E_PASSWORD are required for the Phase 7 production-index parity characterization.');
}

const login = async (page: Page): Promise<void> => {
    await page.goto('/login');
    await page.getByRole('textbox', { name: 'Username', exact: true }).fill(email!);
    await page.getByLabel('Password').fill(password!);
    await page.getByRole('button', { name: 'Login' }).click();
    await expect(page).toHaveURL(/\/$/);
};

test.describe('Phase 7 production index cutover parity', () => {
    test.skip(!email || !password, 'Set E2E_EMAIL and E2E_PASSWORD to validate the Phase 7 production indexes.');

    test('constrained desktop preserves the historical selection, actions, header, and footer parity', async ({ page }, testInfo) => {
        test.skip(testInfo.project.name !== 'desktop-chromium', 'Hover and constrained-desktop parity run once on desktop.');

        await page.setViewportSize({ width: 735, height: 790 });
        await login(page);
        await page.goto('/investors?items_per_page=50');

        const topbar = page.locator('.application-shell__topbar');
        const navigationToggle = page.locator('.application-shell__navigation-toggle');
        const globalSearchTrigger = page.locator('.application-shell__search');
        const topbarActions = page.locator('.application-shell__topbar-actions');
        const topbarGeometry = async () => page.evaluate(() => {
            const selectors = [
                '.application-shell__navigation-toggle',
                '.application-shell__search',
                '.application-shell__topbar-actions',
            ];

            return selectors.map((selector) => {
                const element = document.querySelector<HTMLElement>(selector);

                if (!element) throw new Error(`Missing topbar element: ${selector}`);

                const rect = element.getBoundingClientRect();

                return {
                    x: rect.x,
                    y: rect.y,
                    width: rect.width,
                    height: rect.height,
                };
            });
        });
        const beforeSearchOpen = await topbarGeometry();

        await globalSearchTrigger.click();
        const globalSearchPanel = page.getByRole('dialog', { name: 'Recherche globale' });
        const globalSearchInput = globalSearchPanel.getByRole('combobox', { name: 'Rechercher' });

        await expect(globalSearchTrigger).toHaveAttribute('aria-expanded', 'true');
        await expect(globalSearchPanel).toBeVisible();
        await expect(globalSearchInput).toBeFocused();

        const afterSearchOpen = await topbarGeometry();
        const topbarOverlap = await page.evaluate(() => {
            const search = document.querySelector<HTMLElement>('.application-shell__search')?.getBoundingClientRect();
            const actions = document.querySelector<HTMLElement>('.application-shell__topbar-actions')?.getBoundingClientRect();
            const panel = document.querySelector<HTMLElement>('.application-global-search__panel')?.getBoundingClientRect();
            const viewport = window.visualViewport;
            const viewportLeft = viewport?.offsetLeft ?? 0;
            const viewportTop = viewport?.offsetTop ?? 0;
            const viewportRight = viewportLeft + (viewport?.width ?? window.innerWidth);
            const viewportBottom = viewportTop + (viewport?.height ?? window.innerHeight);

            if (!search || !actions || !panel) throw new Error('Missing global-search geometry.');

            return {
                searchOverlapsActions: search.right > actions.left,
                panelInsideViewport: panel.left >= viewportLeft
                    && panel.top >= viewportTop
                    && panel.right <= viewportRight + 1
                    && panel.bottom <= viewportBottom + 1,
            };
        });

        expect(afterSearchOpen).toEqual(beforeSearchOpen);
        expect(topbarOverlap.searchOverlapsActions).toBe(false);
        expect(topbarOverlap.panelInsideViewport).toBe(true);
        await expect(navigationToggle).toHaveCSS('width', '36px');
        await expect(globalSearchTrigger).toHaveCSS('width', '36px');
        await expect(topbarActions).toBeVisible();

        await page.keyboard.press('Escape');
        await expect(globalSearchPanel).toBeHidden();
        await expect(globalSearchTrigger).toBeFocused();
        await page.keyboard.press(process.platform === 'darwin' ? 'Meta+k' : 'Control+k');
        await expect(globalSearchInput).toBeFocused();
        await page.keyboard.press('Escape');
        await expect(topbar).toBeVisible();

        const table = page.locator('#investors-crud-table');
        const row = table.locator('tbody tr').first();
        const scroller = table.locator('.erp-table-main-scroll');
        const scrollShell = table.locator('.erp-table-scroll');
        const selectionHead = table.locator('.erp-table-select-head');
        const selectionCell = row.locator('.erp-table-select-cell');
        const checkbox = row.locator('.erp-table-select-cell input[type="checkbox"]');
        const actionsCell = row.locator('.erp-table-actions-cell');
        const actions = row.locator('.erp-table-actions-cell .erp-row-actions');

        await expect(row).toBeVisible();
        await page.locator('.application-shell__topbar').hover();
        await expect.poll(() => row.locator('.erp-table-actions-cell').evaluate((element) => (
            element.getBoundingClientRect().width
        ))).toBeLessThanOrEqual(1);
        await expect.poll(() => checkbox.evaluate((element) => getComputedStyle(element).opacity)).toBe('0');
        await expect.poll(() => checkbox.evaluate((element) => getComputedStyle(element).pointerEvents)).toBe('none');
        await expect.poll(() => actions.evaluate((element) => getComputedStyle(element).opacity)).toBe('0');

        const leadingGeometry = await table.evaluate((tableElement) => {
            const main = tableElement.querySelector<HTMLElement>('.erp-table-main-scroll');
            const head = tableElement.querySelector<HTMLElement>('.erp-table-select-head');
            const cell = tableElement.querySelector<HTMLElement>('tbody .erp-table-select-cell');
            const headInput = head?.querySelector<HTMLInputElement>('input[type="checkbox"]');
            const cellInput = cell?.querySelector<HTMLInputElement>('input[type="checkbox"]');
            const firstDataHead = tableElement.querySelector<HTMLElement>(
                'thead th:not(.erp-table-select-head):not(.erp-table-actions-head)',
            );
            const firstDataCell = tableElement.querySelector<HTMLElement>(
                'tbody td:not(.erp-table-select-cell):not(.erp-table-actions-cell)',
            );

            if (!main || !head || !cell || !headInput || !cellInput || !firstDataHead || !firstDataCell) {
                throw new Error('Expected the leading selection gutter geometry.');
            }

            const mainRect = main.getBoundingClientRect();
            const headRect = head.getBoundingClientRect();
            const cellRect = cell.getBoundingClientRect();
            const headInputRect = headInput.getBoundingClientRect();
            const cellInputRect = cellInput.getBoundingClientRect();

            return {
                headWidth: headRect.width,
                cellWidth: cellRect.width,
                firstHeadInset: Math.abs(firstDataHead.getBoundingClientRect().left - mainRect.left),
                firstCellInset: Math.abs(firstDataCell.getBoundingClientRect().left - mainRect.left),
                headInputX: Math.abs(
                    (headInputRect.left + headInputRect.width / 2) - (headRect.left + headRect.width / 2),
                ),
                cellInputX: Math.abs(
                    (cellInputRect.left + cellInputRect.width / 2) - (cellRect.left + cellRect.width / 2),
                ),
                headInputY: Math.abs(
                    (headInputRect.top + headInputRect.height / 2) - (headRect.top + headRect.height / 2),
                ),
                cellInputY: Math.abs(
                    (cellInputRect.top + cellInputRect.height / 2) - (cellRect.top + cellRect.height / 2),
                ),
            };
        });

        expect(leadingGeometry.headWidth).toBeGreaterThanOrEqual(43);
        expect(leadingGeometry.headWidth).toBeLessThanOrEqual(45);
        expect(leadingGeometry.cellWidth).toBeGreaterThanOrEqual(43);
        expect(leadingGeometry.cellWidth).toBeLessThanOrEqual(45);
        expect(Math.abs(leadingGeometry.firstHeadInset - leadingGeometry.headWidth)).toBeLessThanOrEqual(1);
        expect(Math.abs(leadingGeometry.firstCellInset - leadingGeometry.cellWidth)).toBeLessThanOrEqual(1);
        expect(leadingGeometry.headInputX).toBeLessThanOrEqual(1);
        expect(leadingGeometry.cellInputX).toBeLessThanOrEqual(1);
        expect(leadingGeometry.headInputY).toBeLessThanOrEqual(1);
        expect(leadingGeometry.cellInputY).toBeLessThanOrEqual(1);

        const idleEdgeStyles = await table.evaluate((tableElement) => {
            const stylesFor = (selector: string) => {
                const element = tableElement.querySelector<HTMLElement>(selector);

                if (!element) throw new Error(`Expected ${selector}.`);

                const style = getComputedStyle(element);

                return {
                    backgroundColor: style.backgroundColor,
                    backgroundImage: style.backgroundImage,
                    borderRightWidth: style.borderRightWidth,
                    boxShadow: style.boxShadow,
                    pointerEvents: style.pointerEvents,
                    beforeBackgroundColor: getComputedStyle(element, '::before').backgroundColor,
                    beforeBackgroundImage: getComputedStyle(element, '::before').backgroundImage,
                    beforeBoxShadow: getComputedStyle(element, '::before').boxShadow,
                    beforeOpacity: getComputedStyle(element, '::before').opacity,
                };
            };

            return {
                selectionHead: stylesFor('.erp-table-select-head'),
                selectionCell: stylesFor('.erp-table-select-cell'),
                dataHead: stylesFor('thead th:not(.erp-table-select-head):not(.erp-table-actions-head)'),
                dataCell: stylesFor('tbody td:not(.erp-table-select-cell):not(.erp-table-actions-cell)'),
                actionsHead: stylesFor('.erp-table-actions-head'),
                actionsCell: stylesFor('.erp-table-actions-cell'),
            };
        });

        expect(idleEdgeStyles.selectionHead.borderRightWidth).toBe('0px');
        expect(idleEdgeStyles.selectionCell.borderRightWidth).toBe('0px');
        expect(idleEdgeStyles.selectionHead.backgroundColor).toBe(idleEdgeStyles.dataHead.backgroundColor);
        expect(idleEdgeStyles.selectionCell.backgroundColor).toBe(idleEdgeStyles.dataCell.backgroundColor);
        expect(idleEdgeStyles.selectionHead.boxShadow).toBe('none');
        expect(idleEdgeStyles.selectionCell.boxShadow).toBe('none');
        expect(idleEdgeStyles.actionsHead.backgroundColor).toBe('rgba(0, 0, 0, 0)');
        expect(idleEdgeStyles.actionsHead.backgroundImage).toBe('none');
        expect(idleEdgeStyles.actionsHead.boxShadow).toBe('none');
        expect(idleEdgeStyles.actionsHead.pointerEvents).toBe('none');
        expect(idleEdgeStyles.actionsCell.backgroundColor).toBe('rgba(0, 0, 0, 0)');
        expect(idleEdgeStyles.actionsCell.backgroundImage).toBe('none');
        expect(idleEdgeStyles.actionsCell.boxShadow).toBe('none');
        expect(idleEdgeStyles.actionsCell.pointerEvents).toBe('none');
        expect(idleEdgeStyles.actionsCell.beforeOpacity).toBe('0');
        expect(idleEdgeStyles.actionsCell.beforeBackgroundImage.startsWith('linear-gradient')).toBe(true);

        await scroller.evaluate((element) => {
            element.scrollLeft = Math.min(160, element.scrollWidth - element.clientWidth);
        });
        await expect(scrollShell).toHaveClass(/is-scrolled-x/);
        await expect.poll(() => selectionCell.evaluate((element) => (
            getComputedStyle(element).boxShadow
        ))).toBe('none');

        await table.locator('thead tr').hover();
        await expect.poll(() => selectionHead.locator('input').evaluate((element) => (
            getComputedStyle(element).opacity
        ))).toBe('1');
        await expect.poll(() => selectionHead.evaluate((element) => (
            getComputedStyle(element).boxShadow
        ))).not.toBe('none');

        await row.hover({ position: { x: 22, y: 22 } });
        // Locator hover may bring the full-width row back to its inline start.
        // Restore the reported horizontal-scroll state before checking the
        // conditional depth cue on the now-visible selection gutter.
        await scroller.evaluate((element) => {
            element.scrollLeft = Math.min(160, element.scrollWidth - element.clientWidth);
        });
        await expect(scrollShell).toHaveClass(/is-scrolled-x/);
        await expect.poll(() => checkbox.evaluate((element) => getComputedStyle(element).opacity)).toBe('1');
        await expect.poll(() => checkbox.evaluate((element) => getComputedStyle(element).pointerEvents)).toBe('auto');
        await expect.poll(() => selectionCell.evaluate((element) => (
            getComputedStyle(element).boxShadow
        ))).not.toBe('none');
        await expect.poll(() => actions.evaluate((element) => getComputedStyle(element).opacity)).toBe('1');
        await expect.poll(() => actions.evaluate((element) => getComputedStyle(element).pointerEvents)).toBe('auto');
        await expect.poll(() => actionsCell.evaluate((element) => (
            getComputedStyle(element, '::before').opacity
        ))).toBe('1');
        await expect.poll(() => actions.evaluate((element) => (
            getComputedStyle(element).backgroundColor
        ))).not.toBe('rgba(0, 0, 0, 0)');
        await expect(row.getByRole('link', { name: 'Preview investor' })).toBeVisible();
        await expect(row.getByRole('link', { name: 'Edit investor' })).toBeVisible();
        await expect(row.getByRole('button', { name: 'Delete investor' })).toBeVisible();

        await page.locator('.application-shell__topbar').hover();
        await expect.poll(() => actions.evaluate((element) => getComputedStyle(element).opacity)).toBe('0');
        await expect.poll(() => actions.evaluate((element) => getComputedStyle(element).pointerEvents)).toBe('none');
        await expect.poll(() => checkbox.evaluate((element) => getComputedStyle(element).pointerEvents)).toBe('none');
        await expect.poll(() => selectionCell.evaluate((element) => (
            getComputedStyle(element).boxShadow
        ))).toBe('none');
        await expect.poll(() => actionsCell.evaluate((element) => (
            getComputedStyle(element, '::before').opacity
        ))).toBe('0');
        await row.hover({ position: { x: 22, y: 22 } });

        await row.getByRole('link', { name: 'Preview investor' }).click();
        await expect(page.locator('[id$="_preview_drawer"]')).toBeVisible();
        await page.keyboard.press('Escape');
        await expect(page.locator('[id$="_preview_drawer"]')).toBeHidden();

        await page.locator('.application-shell__topbar').hover();
        await row.focus();
        await scroller.evaluate((element) => {
            element.scrollLeft = Math.min(160, element.scrollWidth - element.clientWidth);
        });
        await expect(scrollShell).toHaveClass(/is-scrolled-x/);
        await expect.poll(() => checkbox.evaluate((element) => getComputedStyle(element).pointerEvents)).toBe('auto');
        await expect.poll(() => selectionCell.evaluate((element) => (
            getComputedStyle(element).boxShadow
        ))).not.toBe('none');
        await expect.poll(() => actions.evaluate((element) => getComputedStyle(element).opacity)).toBe('1');
        await expect.poll(() => actions.evaluate((element) => getComputedStyle(element).pointerEvents)).toBe('auto');
        await expect.poll(() => actionsCell.evaluate((element) => (
            getComputedStyle(element, '::before').opacity
        ))).toBe('1');

        await checkbox.check();
        await scroller.evaluate((element) => {
            element.scrollLeft = Math.min(160, element.scrollWidth - element.clientWidth);
        });
        await expect(scrollShell).toHaveClass(/is-scrolled-x/);
        await page.locator('.application-shell__topbar').hover();
        await expect.poll(() => checkbox.evaluate((element) => getComputedStyle(element).opacity)).toBe('1');
        await expect.poll(() => checkbox.evaluate((element) => getComputedStyle(element).pointerEvents)).toBe('auto');
        await expect.poll(() => selectionCell.evaluate((element) => (
            getComputedStyle(element).boxShadow
        ))).not.toBe('none');

        await scroller.evaluate((element) => {
            element.scrollLeft = 0;
        });
        await expect(scrollShell).not.toHaveClass(/is-scrolled-x/);
        await expect.poll(() => selectionCell.evaluate((element) => (
            getComputedStyle(element).boxShadow
        ))).toBe('none');

        const headerParity = await table.locator('thead').evaluate((head) => {
            const selection = head.querySelector<HTMLElement>('.erp-table-select-head');
            const data = head.querySelector<HTMLElement>('th:not(.erp-table-select-head):not(.erp-table-actions-head)');
            const main = head.closest('.erp-table-main-scroll');

            if (!selection || !data || !main) throw new Error('Expected selection and data headers.');

            const selectionStyle = getComputedStyle(selection);
            const dataStyle = getComputedStyle(data);

            return {
                selectionWidth: selection.getBoundingClientRect().width,
                firstDataInset: Math.abs(
                    data.getBoundingClientRect().left - main.getBoundingClientRect().left,
                ),
                selectionBackground: selectionStyle.backgroundColor,
                dataBackground: dataStyle.backgroundColor,
                selectionDivider: `${selectionStyle.borderBottomWidth} ${selectionStyle.borderBottomStyle} ${selectionStyle.borderBottomColor}`,
                dataDivider: `${dataStyle.borderBottomWidth} ${dataStyle.borderBottomStyle} ${dataStyle.borderBottomColor}`,
            };
        });

        expect(headerParity.selectionWidth).toBeGreaterThanOrEqual(43);
        expect(headerParity.selectionWidth).toBeLessThanOrEqual(45);
        expect(Math.abs(headerParity.firstDataInset - headerParity.selectionWidth)).toBeLessThanOrEqual(1);
        expect(headerParity.selectionBackground).toBe(headerParity.dataBackground);
        expect(headerParity.selectionDivider).toBe(headerParity.dataDivider);

        const footerLayout = await table.locator('.erp-table-footer').evaluate((footer) => {
            const footnote = footer.querySelector<HTMLElement>('.erp-table-footnote');
            const pageSize = footer.querySelector<HTMLElement>('.erp-page-size-form');

            if (!footnote || !pageSize) throw new Error('Expected footer count and page-size control.');

            const footnoteRect = footnote.getBoundingClientRect();
            const pageSizeRect = pageSize.getBoundingClientRect();
            const pageSizeLabelRect = pageSize.querySelector('label')?.getBoundingClientRect();
            const footerRect = footer.getBoundingClientRect();
            const footerStyle = getComputedStyle(footer);
            const contentRight = footerRect.right - Number.parseFloat(footerStyle.paddingRight);

            return {
                centerDelta: Math.abs(
                    (footnoteRect.top + footnoteRect.height / 2)
                    - (pageSizeRect.top + pageSizeRect.height / 2),
                ),
                footnoteRight: footnoteRect.right,
                pageSizeLeft: pageSizeRect.left,
                pageSizeLabelHeight: pageSizeLabelRect?.height ?? 0,
                rightDelta: Math.abs(contentRight - pageSizeRect.right),
            };
        });

        expect(footerLayout.centerDelta).toBeLessThanOrEqual(1);
        expect(footerLayout.pageSizeLeft).toBeGreaterThan(footerLayout.footnoteRight);
        expect(footerLayout.pageSizeLabelHeight).toBeLessThanOrEqual(20);
        expect(footerLayout.rightDelta).toBeLessThanOrEqual(1);

        await page.goto('/investors?items_per_page=25');
        const pagination = table.locator('.erp-table-pagination:not(:empty)');
        await expect(pagination).toBeVisible();

        await table.locator('thead tr').hover();
        await table.locator('input[aria-label="Select all visible rows"]').check();
        await expect(table.locator('.erp-selection-count')).toHaveText('25 selected');

        const toolbarGeometry = await table.locator('.erp-table-toolbar-actions').evaluate((toolbar) => {
            const selectors = {
                exportButton: '.erp-table-import-export',
                selectionCount: '.erp-selection-count',
                deleteButton: '.erp-bulk-delete-trigger',
                createButton: '.erp-table-create',
            };

            return Object.fromEntries(Object.entries(selectors).map(([key, selector]) => {
                const element = toolbar.querySelector<HTMLElement>(selector);

                if (!element) throw new Error(`Missing toolbar element: ${selector}`);

                const rect = element.getBoundingClientRect();

                return [key, {
                    left: rect.left,
                    right: rect.right,
                    height: rect.height,
                    centerY: rect.top + (rect.height / 2),
                    borderRadius: getComputedStyle(element).borderRadius,
                }];
            }));
        }) as Record<string, {
            left: number;
            right: number;
            height: number;
            centerY: number;
            borderRadius: string;
        }>;

        const controlRadius = await table.evaluate((element) => (
            getComputedStyle(element).getPropertyValue('--radius-control').trim()
        ));

        expect(toolbarGeometry.exportButton.right).toBeLessThan(toolbarGeometry.selectionCount.left);
        expect(toolbarGeometry.selectionCount.right).toBeLessThan(toolbarGeometry.deleteButton.left);
        expect(toolbarGeometry.deleteButton.right).toBeLessThan(toolbarGeometry.createButton.left);
        expect(toolbarGeometry.exportButton.height).toBe(38);
        expect(toolbarGeometry.selectionCount.height).toBe(38);
        expect(toolbarGeometry.deleteButton.height).toBe(38);
        expect(toolbarGeometry.createButton.height).toBe(38);
        expect(toolbarGeometry.exportButton.borderRadius).toBe(controlRadius);
        expect(toolbarGeometry.deleteButton.borderRadius).toBe(controlRadius);
        expect(toolbarGeometry.createButton.borderRadius).toBe(controlRadius);
        expect(Math.abs(
            toolbarGeometry.exportButton.centerY - toolbarGeometry.deleteButton.centerY,
        )).toBeLessThanOrEqual(1);
        expect(Math.abs(
            toolbarGeometry.deleteButton.centerY - toolbarGeometry.createButton.centerY,
        )).toBeLessThanOrEqual(1);
        await expect.poll(() => table.locator('.erp-selection-count').evaluate((element) => ({
            backgroundColor: getComputedStyle(element).backgroundColor,
            tabIndex: (element as HTMLElement).tabIndex,
        }))).toEqual({
            backgroundColor: 'rgba(0, 0, 0, 0)',
            tabIndex: -1,
        });

        const paginatedFooterLayout = await table.locator('.erp-table-footer').evaluate((footer) => {
            const footnote = footer.querySelector<HTMLElement>('.erp-table-footnote');
            const pageSize = footer.querySelector<HTMLElement>('.erp-page-size-form');
            const pagination = footer.querySelector<HTMLElement>('.erp-table-pagination:not(:empty)');

            if (!footnote || !pageSize || !pagination) throw new Error('Expected all three footer groups.');

            const footnoteRect = footnote.getBoundingClientRect();
            const pageSizeRect = pageSize.getBoundingClientRect();
            const paginationRect = pagination.getBoundingClientRect();
            const footerRect = footer.getBoundingClientRect();
            const footerStyle = getComputedStyle(footer);
            const contentRight = footerRect.right - Number.parseFloat(footerStyle.paddingRight);
            const rects = [footnoteRect, pageSizeRect, paginationRect];
            const centers = rects.map((rect) => rect.top + rect.height / 2);
            const overlaps = rects.some((rect, index) => rects.slice(index + 1).some((other) => (
                rect.left < other.right
                && other.left < rect.right
                && rect.top < other.bottom
                && other.top < rect.bottom
            )));

            return {
                centerSpread: Math.max(...centers) - Math.min(...centers),
                footerHeight: footerRect.height,
                order: footnoteRect.right < pageSizeRect.left && pageSizeRect.right < paginationRect.left,
                overlaps,
                overflows: footer.scrollWidth > footer.clientWidth + 1,
                rightDelta: Math.abs(contentRight - paginationRect.right),
            };
        });

        expect(paginatedFooterLayout.centerSpread).toBeLessThanOrEqual(1);
        expect(paginatedFooterLayout.footerHeight).toBeLessThanOrEqual(64);
        expect(paginatedFooterLayout.order).toBe(true);
        expect(paginatedFooterLayout.overlaps).toBe(false);
        expect(paginatedFooterLayout.overflows).toBe(false);
        expect(paginatedFooterLayout.rightDelta).toBeLessThanOrEqual(1);
    });
});
