import { fireEvent } from '@testing-library/react';
import { act } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { isDesktopViewport, useSidePanelMachine } from '../../src/panels/useSidePanelMachine';
import { renderHook } from '../helpers/renderHook';

function setViewportWidth(width: number) {
    Object.defineProperty(window, 'innerWidth', {
        configurable: true,
        writable: true,
        value: width,
    });
}

function resetMatchMedia() {
    Object.defineProperty(window, 'matchMedia', {
        configurable: true,
        writable: true,
        value: undefined,
    });
}

describe('isDesktopViewport', () => {
    it('uses matchMedia when available', () => {
        const matchMedia = vi.fn(() => ({ matches: false }));
        Object.defineProperty(window, 'matchMedia', {
            configurable: true,
            writable: true,
            value: matchMedia,
        });

        expect(isDesktopViewport(1200)).toBe(false);
        expect(matchMedia).toHaveBeenCalledWith('(min-width: 1200px)');
    });

    it('falls back to window.innerWidth when matchMedia is unavailable', () => {
        resetMatchMedia();
        setViewportWidth(1024);
        expect(isDesktopViewport(1024)).toBe(true);

        setViewportWidth(800);
        expect(isDesktopViewport(1024)).toBe(false);
    });
});

describe('useSidePanelMachine', () => {
    it('reads stored width, clamps resize values, and persists final width', async () => {
        resetMatchMedia();
        setViewportWidth(1200);
        localStorage.setItem('panel-width', '999');

        const preventDefault = vi.fn();
        const { result } = renderHook(() =>
            useSidePanelMachine({
                storageKey: 'panel-width',
                defaultWidth: 300,
                minWidth: 200,
                maxWidth: 400,
                resizeAxis: 'from-left',
                resizingBodyClass: 'resizing',
                isOpen: false,
                canResize: true,
                shouldPersistWidth: true,
                closeOnPathname: undefined,
            }),
        );

        expect(result.current.width).toBe(400);
        expect(result.current.isDesktop).toBe(true);
        expect(localStorage.getItem('panel-width')).toBe('400');

        act(() => {
            result.current.startResize({
                clientX: 400,
                preventDefault,
            } as never);
        });
        expect(preventDefault).toHaveBeenCalledTimes(1);
        expect(document.body.classList.contains('resizing')).toBe(true);

        act(() => {
            fireEvent.pointerMove(window, { clientX: 100 });
            fireEvent.pointerUp(window);
        });

        await act(async () => {
            await Promise.resolve();
        });

        expect(result.current.width).toBe(200);
        expect(localStorage.getItem('panel-width')).toBe('200');
        expect(document.body.classList.contains('resizing')).toBe(false);
    });

    it('falls back to default width for empty or invalid stored values', () => {
        resetMatchMedia();
        setViewportWidth(1200);

        localStorage.setItem('panel-width', '');
        const invalid = renderHook(() =>
            useSidePanelMachine({
                storageKey: 'panel-width',
                defaultWidth: 310,
                minWidth: 200,
                maxWidth: 400,
                resizeAxis: 'from-left',
                resizingBodyClass: 'resizing',
                isOpen: false,
                canResize: true,
                shouldPersistWidth: false,
                closeOnPathname: undefined,
            }),
        );
        expect(invalid.result.current.width).toBe(310);
        invalid.unmount();

        localStorage.setItem('panel-width', 'NaN-value');
        const nonFinite = renderHook(() =>
            useSidePanelMachine({
                storageKey: 'panel-width',
                defaultWidth: 315,
                minWidth: 200,
                maxWidth: 400,
                resizeAxis: 'from-left',
                resizingBodyClass: 'resizing',
                isOpen: false,
                canResize: true,
                shouldPersistWidth: false,
                closeOnPathname: undefined,
            }),
        );
        expect(nonFinite.result.current.width).toBe(315);
    });

    it('does not start resizing when viewport is mobile or resizing is disabled', () => {
        resetMatchMedia();
        setViewportWidth(700);

        const preventDefault = vi.fn();
        const { result } = renderHook(() =>
            useSidePanelMachine({
                storageKey: 'panel-width',
                defaultWidth: 300,
                minWidth: 200,
                maxWidth: 400,
                resizeAxis: 'from-left',
                resizingBodyClass: 'resizing',
                isOpen: false,
                canResize: false,
                shouldPersistWidth: false,
                closeOnPathname: undefined,
            }),
        );

        act(() => {
            result.current.startResize({
                clientX: 300,
                preventDefault,
            } as never);
        });

        expect(preventDefault).not.toHaveBeenCalled();
        expect(document.body.classList.contains('resizing')).toBe(false);
        expect(localStorage.getItem('panel-width')).toBeNull();
    });

    it('ignores pointer events when not currently resizing', () => {
        resetMatchMedia();
        setViewportWidth(1200);

        renderHook(() =>
            useSidePanelMachine({
                storageKey: 'panel-width',
                defaultWidth: 300,
                minWidth: 200,
                maxWidth: 400,
                resizeAxis: 'from-left',
                resizingBodyClass: 'resizing',
                isOpen: false,
                canResize: true,
                shouldPersistWidth: true,
                closeOnPathname: undefined,
            }),
        );

        act(() => {
            fireEvent.pointerMove(window, { clientX: 1000 });
            fireEvent.pointerUp(window);
        });

        expect(document.body.classList.contains('resizing')).toBe(false);
    });

    it('handles closeOnPathname, escape key callbacks and body overflow lock', () => {
        resetMatchMedia();
        setViewportWidth(700);

        const onCloseOnPathname = vi.fn();
        const onEscape = vi.fn();
        let options = {
            storageKey: 'panel-width',
            defaultWidth: 300,
            minWidth: 200,
            maxWidth: 400,
            resizeAxis: 'from-right' as const,
            resizingBodyClass: 'resizing',
            isOpen: true,
            canResize: true,
            shouldPersistWidth: false,
            closeOnPathname: '/users',
            onCloseOnPathname,
            onEscape,
        };

        const { rerender } = renderHook(() => useSidePanelMachine(options));

        expect(onCloseOnPathname).toHaveBeenCalledTimes(1);
        expect(document.body.style.overflow).toBe('hidden');

        act(() => {
            fireEvent.keyDown(window, { key: 'Escape' });
        });
        expect(onEscape).toHaveBeenCalledTimes(1);

        options = {
            ...options,
            closeOnPathname: '/posts',
        };
        rerender();
        expect(onCloseOnPathname).toHaveBeenCalledTimes(2);

        options = {
            ...options,
            isOpen: false,
        };
        rerender();
        expect(document.body.style.overflow).toBe('');
    });
});
