import { render, screen, fireEvent, within } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { OverflowMenu, type OverflowMenuItem } from '../src/platform/components/ui/OverflowMenu';

// Note: @testing-library auto-cleanup (test/setup.ts) unmounts between tests,
// which also tears down the portaled menu — do not wipe document.body manually.
const makeItems = (): OverflowMenuItem[] => [
    { key: 'a', label: 'Change Owner', onSelect: vi.fn() },
    { key: 'b', label: 'Share', onSelect: vi.fn() },
    { key: 'del', label: 'Delete', onSelect: vi.fn(), variant: 'danger', dividerBefore: true },
];

describe('OverflowMenu', () => {
    it('renders nothing when there are no items', () => {
        const { container } = render(<OverflowMenu items={[]} />);
        expect(container.firstChild).toBeNull();
    });

    it('renders the trigger as type=button so it never submits an enclosing form', () => {
        render(<OverflowMenu items={makeItems()} />);
        expect(screen.getByRole('button', { name: 'More actions' })).toHaveAttribute('type', 'button');
    });

    it('is collapsed by default and expands on trigger click', () => {
        render(<OverflowMenu items={makeItems()} />);
        const trigger = screen.getByRole('button', { name: 'More actions' });
        expect(trigger).toHaveAttribute('aria-expanded', 'false');
        expect(screen.queryByRole('menu')).toBeNull();

        fireEvent.click(trigger);
        expect(trigger).toHaveAttribute('aria-expanded', 'true');
        expect(screen.getByRole('menu')).toBeInTheDocument();
        expect(screen.getAllByRole('menuitem')).toHaveLength(3);
    });

    it('invokes onSelect and closes when an item is chosen', () => {
        const items = makeItems();
        render(<OverflowMenu items={items} />);
        fireEvent.click(screen.getByRole('button', { name: 'More actions' }));
        fireEvent.click(screen.getByRole('menuitem', { name: 'Share' }));

        expect(items[1].onSelect).toHaveBeenCalledTimes(1);
        expect(items[0].onSelect).not.toHaveBeenCalled();
        expect(screen.queryByRole('menu')).toBeNull();
    });

    it('does not invoke onSelect for a disabled item', () => {
        const items: OverflowMenuItem[] = [
            { key: 'a', label: 'Change Owner', onSelect: vi.fn(), disabled: true },
            { key: 'b', label: 'Share', onSelect: vi.fn() },
        ];
        render(<OverflowMenu items={items} />);
        fireEvent.click(screen.getByRole('button', { name: 'More actions' }));
        fireEvent.click(screen.getByRole('menuitem', { name: 'Change Owner' }));
        expect(items[0].onSelect).not.toHaveBeenCalled();
        expect(screen.getByRole('menu')).toBeInTheDocument();
    });

    it('still opens when every item is disabled, showing them read-only with their disabled reasons', () => {
        const items: OverflowMenuItem[] = [
            { key: 'a', label: 'Change Owner', onSelect: vi.fn(), disabled: true, title: 'Locked by approval' },
            { key: 'b', label: 'Share', onSelect: vi.fn(), disabled: true },
        ];
        render(<OverflowMenu items={items} />);
        const trigger = screen.getByRole('button', { name: 'More actions' });
        // discoverability: the trigger stays enabled and the menu opens
        expect(trigger).not.toBeDisabled();
        fireEvent.click(trigger);
        const changeOwner = screen.getByRole('menuitem', { name: 'Change Owner' });
        expect(changeOwner).toBeDisabled();
        // the disabled reason surfaces as a tooltip
        expect(changeOwner).toHaveAttribute('title', 'Locked by approval');
        fireEvent.click(changeOwner);
        expect(items[0].onSelect).not.toHaveBeenCalled();
        // Escape closes from the trigger (no item can hold focus)
        fireEvent.keyDown(trigger, { key: 'Escape' });
        expect(screen.queryByRole('menu')).toBeNull();
        // Tab also closes from the trigger instead of stranding the menu open
        fireEvent.click(trigger);
        expect(screen.getByRole('menu')).toBeInTheDocument();
        fireEvent.keyDown(trigger, { key: 'Tab' });
        expect(screen.queryByRole('menu')).toBeNull();
    });

    it('passes an item title through as the menuitem tooltip', () => {
        const items: OverflowMenuItem[] = [
            { key: 'a', label: 'Delete', onSelect: vi.fn(), disabled: true, title: 'Record is locked' },
            { key: 'b', label: 'Share', onSelect: vi.fn() },
        ];
        render(<OverflowMenu items={items} />);
        fireEvent.click(screen.getByRole('button', { name: 'More actions' }));
        expect(screen.getByRole('menuitem', { name: 'Delete' })).toHaveAttribute('title', 'Record is locked');
        expect(screen.getByRole('menuitem', { name: 'Share' })).not.toHaveAttribute('title');
    });

    it('does not open from a bare Enter keydown (native button click owns activation)', () => {
        render(<OverflowMenu items={makeItems()} />);
        const trigger = screen.getByRole('button', { name: 'More actions' });
        fireEvent.keyDown(trigger, { key: 'Enter' });
        expect(screen.queryByRole('menu')).toBeNull();
        // the native click path still opens it
        fireEvent.click(trigger);
        expect(screen.getByRole('menu')).toBeInTheDocument();
    });

    it('opens with ArrowDown and focuses the first enabled item', () => {
        render(<OverflowMenu items={makeItems()} />);
        const trigger = screen.getByRole('button', { name: 'More actions' });
        fireEvent.keyDown(trigger, { key: 'ArrowDown' });
        expect(screen.getByRole('menu')).toBeInTheDocument();
        expect(screen.getByRole('menuitem', { name: 'Change Owner' })).toHaveFocus();
    });

    it('closes on Escape and returns focus to the trigger', () => {
        render(<OverflowMenu items={makeItems()} />);
        const trigger = screen.getByRole('button', { name: 'More actions' });
        fireEvent.click(trigger);
        fireEvent.keyDown(screen.getByRole('menu'), { key: 'Escape' });
        expect(screen.queryByRole('menu')).toBeNull();
        expect(trigger).toHaveFocus();
    });

    it('closes on outside pointer-down', () => {
        render(<div><OverflowMenu items={makeItems()} /><button>outside</button></div>);
        fireEvent.click(screen.getByRole('button', { name: 'More actions' }));
        expect(screen.getByRole('menu')).toBeInTheDocument();
        fireEvent.mouseDown(screen.getByText('outside'));
        expect(screen.queryByRole('menu')).toBeNull();
    });

    it('renders a destructive item with a separator and danger styling', () => {
        render(<OverflowMenu items={makeItems()} />);
        fireEvent.click(screen.getByRole('button', { name: 'More actions' }));
        const menu = screen.getByRole('menu');
        expect(within(menu).getByRole('separator')).toBeInTheDocument();
        expect(screen.getByRole('menuitem', { name: 'Delete' }).className).toContain('text-red-600');
    });
});
