import { render, screen, fireEvent, within } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { RecordHeaderActions, type HeaderAction } from '../src/platform/features/record/components/RecordHeaderActions';

const btn = (key: string, label: string, extra: Partial<Extract<HeaderAction, { kind: 'button' }>> = {}): HeaderAction => ({
    kind: 'button', key, label, icon: null, onInvoke: vi.fn(), ...extra,
});

const overflow = () => screen.queryByTestId('overflow-menu');

describe('RecordHeaderActions', () => {
    it('pins Edit first, fills the primary row up to the budget, and collapses the rest into overflow', () => {
        render(
            <RecordHeaderActions
                maxPrimaryButtons={2}
                actions={[
                    btn('host:edit-record', 'Edit'),
                    btn('a', 'Create Quote'),
                    btn('b', 'Add Product'),
                    btn('c', 'Apply for PoC'),
                    btn('d', 'Change Owner'),
                ]}
            />,
        );
        // Edit + first 2 others are primary buttons (role=button, not menuitems)
        expect(screen.getByRole('button', { name: 'Edit' })).toBeInTheDocument();
        expect(screen.getByRole('button', { name: 'Create Quote' })).toBeInTheDocument();
        expect(screen.getByRole('button', { name: 'Add Product' })).toBeInTheDocument();
        // the rest collapse into the overflow menu
        const menu = within(overflow()!);
        expect(menu.getByRole('menuitem', { name: 'Apply for PoC' })).toBeInTheDocument();
        expect(menu.getByRole('menuitem', { name: 'Change Owner' })).toBeInTheDocument();
        // and are not also primary buttons
        expect(screen.queryByRole('button', { name: 'Apply for PoC' })).toBeNull();
    });

    it('always demotes a destructive action (Delete) into the overflow menu with danger styling + a divider, even when the primary budget is free', () => {
        render(
            <RecordHeaderActions
                maxPrimaryButtons={2}
                actions={[
                    btn('host:edit-record', 'Edit'),
                    btn('host:delete-record', 'Delete', { destructive: true }),
                ]}
            />,
        );
        // Delete is not a primary button despite an open budget
        expect(screen.queryByRole('button', { name: 'Delete' })).toBeNull();
        const menu = within(overflow()!);
        const del = menu.getByRole('menuitem', { name: 'Delete' });
        expect(del).toHaveAttribute('data-variant', 'danger');
        expect(menu.getByRole('separator')).toBeInTheDocument();
    });

    it('invokes onInvoke from both a primary button and an overflow item', () => {
        const onPrimary = vi.fn();
        const onOverflow = vi.fn();
        render(
            <RecordHeaderActions
                maxPrimaryButtons={1}
                actions={[
                    btn('host:edit-record', 'Edit'),
                    btn('p', 'Create Quote', { onInvoke: onPrimary }),
                    btn('o', 'Clone', { onInvoke: onOverflow }),
                ]}
            />,
        );
        fireEvent.click(screen.getByRole('button', { name: 'Create Quote' }));
        expect(onPrimary).toHaveBeenCalledTimes(1);
        fireEvent.click(within(overflow()!).getByRole('menuitem', { name: 'Clone' }));
        expect(onOverflow).toHaveBeenCalledTimes(1);
    });

    it('renders no overflow trigger when everything fits in the primary row', () => {
        render(
            <RecordHeaderActions
                maxPrimaryButtons={2}
                actions={[btn('host:edit-record', 'Edit'), btn('a', 'Create Quote')]}
            />,
        );
        expect(overflow()).toBeNull();
    });

    it('collapses every non-Edit action when the primary budget is zero', () => {
        render(
            <RecordHeaderActions
                maxPrimaryButtons={0}
                actions={[
                    btn('host:edit-record', 'Edit'),
                    btn('metadata-action:ApplyForCredits', 'Apply for Credits'),
                    btn('a', 'Create Quote'),
                ]}
            />,
        );
        expect(screen.getByRole('button', { name: 'Edit' })).toBeInTheDocument();
        const menu = within(overflow()!);
        expect(menu.getByRole('menuitem', { name: 'Apply for Credits' })).toBeInTheDocument();
        expect(menu.getByRole('menuitem', { name: 'Create Quote' })).toBeInTheDocument();
    });

    it('forwards a disabled action title into the overflow item tooltip', () => {
        render(
            <RecordHeaderActions
                maxPrimaryButtons={0}
                actions={[
                    btn('host:edit-record', 'Edit'),
                    btn('host:delete-record', 'Delete', { destructive: true, disabled: true, title: 'Locked by approval' }),
                ]}
            />,
        );
        const del = within(overflow()!).getByRole('menuitem', { name: 'Delete' });
        expect(del).toBeDisabled();
        expect(del).toHaveAttribute('title', 'Locked by approval');
    });

    it('promotes pinned keys into the primary row in pin order, ahead of source order', () => {
        render(
            <RecordHeaderActions
                pinnedKeys={['c', 'a']}
                actions={[
                    btn('host:edit-record', 'Edit'),
                    btn('a', 'Create Quote'),
                    btn('b', 'Add Product'),
                    btn('c', 'Apply for PoC'),
                ]}
            />,
        );
        // pinned: Apply for PoC then Create Quote are primary; Add Product overflows
        const buttons = screen.getAllByRole('button').map(b => b.textContent);
        expect(buttons.slice(0, 3)).toEqual(['Edit', 'Apply for PoC', 'Create Quote']);
        expect(within(overflow()!).getByRole('menuitem', { name: 'Add Product' })).toBeInTheDocument();
        expect(screen.queryByRole('button', { name: 'Add Product' })).toBeNull();
    });

    it('skips absent pinned keys and backfills the freed slot in source order', () => {
        render(
            <RecordHeaderActions
                pinnedKeys={['missing-action', 'b']}
                actions={[
                    btn('host:edit-record', 'Edit'),
                    btn('a', 'Create Quote'),
                    btn('b', 'Add Product'),
                    btn('c', 'Apply for PoC'),
                ]}
            />,
        );
        // budget = 2 (pin list length); pin 'b' + backfill 'a'; 'c' overflows
        expect(screen.getByRole('button', { name: 'Add Product' })).toBeInTheDocument();
        expect(screen.getByRole('button', { name: 'Create Quote' })).toBeInTheDocument();
        expect(within(overflow()!).getByRole('menuitem', { name: 'Apply for PoC' })).toBeInTheDocument();
    });

    it('sets the primary budget from the pin list (three pins -> three slots), capped at three', () => {
        render(
            <RecordHeaderActions
                pinnedKeys={['a', 'b', 'c', 'd']}
                actions={[
                    btn('host:edit-record', 'Edit'),
                    btn('a', 'Create Quote'),
                    btn('b', 'Add Product'),
                    btn('c', 'Apply for PoC'),
                    btn('d', 'Change Owner'),
                ]}
            />,
        );
        // four pins requested, cap = 3: a/b/c primary, d overflows
        expect(screen.getByRole('button', { name: 'Apply for PoC' })).toBeInTheDocument();
        expect(screen.queryByRole('button', { name: 'Change Owner' })).toBeNull();
        expect(within(overflow()!).getByRole('menuitem', { name: 'Change Owner' })).toBeInTheDocument();
    });

    it('never promotes a destructive action, even when pinned', () => {
        render(
            <RecordHeaderActions
                pinnedKeys={['host:delete-record']}
                actions={[
                    btn('host:edit-record', 'Edit'),
                    btn('host:delete-record', 'Delete', { destructive: true }),
                    btn('a', 'Create Quote'),
                ]}
            />,
        );
        expect(screen.queryByRole('button', { name: 'Delete' })).toBeNull();
        expect(within(overflow()!).getByRole('menuitem', { name: 'Delete' })).toBeInTheDocument();
        // the pin slot backfills from source order
        expect(screen.getByRole('button', { name: 'Create Quote' })).toBeInTheDocument();
    });

    it('treats an empty pin list as unconfigured (default budget + source order)', () => {
        render(
            <RecordHeaderActions
                pinnedKeys={[]}
                actions={[
                    btn('host:edit-record', 'Edit'),
                    btn('a', 'Create Quote'),
                    btn('b', 'Add Product'),
                    btn('c', 'Apply for PoC'),
                ]}
            />,
        );
        expect(screen.getByRole('button', { name: 'Create Quote' })).toBeInTheDocument();
        expect(screen.getByRole('button', { name: 'Add Product' })).toBeInTheDocument();
        expect(within(overflow()!).getByRole('menuitem', { name: 'Apply for PoC' })).toBeInTheDocument();
    });

    it('passes disabled + title through to a primary button', () => {
        render(
            <RecordHeaderActions
                actions={[btn('host:edit-record', 'Edit', { disabled: true, title: 'Read only' })]}
            />,
        );
        const edit = screen.getByRole('button', { name: 'Edit' });
        expect(edit).toBeDisabled();
        expect(edit).toHaveAttribute('title', 'Read only');
    });
});
