import { render, waitFor } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { describe, expect, test, vi } from 'vitest';
import { ref } from 'vue';
import {
  UiDropdownMenu,
  UiDropdownMenuTrigger,
  UiDropdownMenuContent,
  UiDropdownMenuItem,
  UiDropdownMenuSeparator,
  UiDropdownMenuCheckboxItem,
  UiDropdownMenuRadioGroup,
  UiDropdownMenuRadioItem,
  UiDropdownMenuLabel,
  UiDropdownMenuSub,
  UiDropdownMenuSubTrigger,
  UiDropdownMenuSubContent,
} from '../index';

// Helper to render a basic dropdown menu
/**
 *
 * @param options
 * @param options.triggerText
 * @param options.items
 * @param options.open
 */
function renderDropdownMenu(
  options: {
    triggerText?: string;
    items?: string[];
    open?: boolean;
  } = {},
) {
  const {
    triggerText = 'Open Menu',
    items = ['Item 1', 'Item 2'],
    open,
  } = options;

  return render({
    components: {
      UiDropdownMenu,
      UiDropdownMenuTrigger,
      UiDropdownMenuContent,
      UiDropdownMenuItem,
    },
    template: `
      <UiDropdownMenu ${open !== undefined ? ':open="open"' : ''}>
        <UiDropdownMenuTrigger>
          {{ triggerText }}
        </UiDropdownMenuTrigger>
        <UiDropdownMenuContent>
          <UiDropdownMenuItem v-for="item in items" :key="item">
            {{ item }}
          </UiDropdownMenuItem>
        </UiDropdownMenuContent>
      </UiDropdownMenu>
    `,
    setup() {
      return { triggerText, items, open };
    },
  });
}

describe('UiDropdownMenu', () => {
  test('renders trigger button', () => {
    const { getByRole } = renderDropdownMenu();
    expect(getByRole('button', { name: 'Open Menu' })).toBeInTheDocument();
  });

  test('menu is closed by default', () => {
    const { queryByRole } = renderDropdownMenu();
    expect(queryByRole('menu')).not.toBeInTheDocument();
  });

  test('opens menu when trigger is clicked', async () => {
    const user = userEvent.setup();
    const { getByRole } = renderDropdownMenu();

    await user.click(getByRole('button', { name: 'Open Menu' }));

    await waitFor(() => {
      expect(getByRole('menu')).toBeInTheDocument();
    });
  });

  test('renders menu items when open', async () => {
    const user = userEvent.setup();
    const { getByRole } = renderDropdownMenu({
      items: ['Profile', 'Settings'],
    });

    await user.click(getByRole('button', { name: 'Open Menu' }));

    await waitFor(() => {
      expect(getByRole('menuitem', { name: 'Profile' })).toBeInTheDocument();
      expect(getByRole('menuitem', { name: 'Settings' })).toBeInTheDocument();
    });
  });

  test('closes menu when item is clicked', async () => {
    const user = userEvent.setup();
    const { getByRole, queryByRole } = renderDropdownMenu();

    await user.click(getByRole('button', { name: 'Open Menu' }));
    await waitFor(() => expect(getByRole('menu')).toBeInTheDocument());

    await user.click(getByRole('menuitem', { name: 'Item 1' }));

    await waitFor(() => {
      expect(queryByRole('menu')).not.toBeInTheDocument();
    });
  });

  test('closes menu when Escape is pressed', async () => {
    const user = userEvent.setup();
    const { getByRole, queryByRole } = renderDropdownMenu();

    await user.click(getByRole('button', { name: 'Open Menu' }));
    await waitFor(() => expect(getByRole('menu')).toBeInTheDocument());

    await user.keyboard('{Escape}');

    await waitFor(() => {
      expect(queryByRole('menu')).not.toBeInTheDocument();
    });
  });
});

describe('UiDropdownMenuTrigger', () => {
  test('applies aria-label when provided', async () => {
    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger ariaLabel="Menu options">
            ⋮
          </UiDropdownMenuTrigger>
          <UiDropdownMenuContent />
        </UiDropdownMenu>
      `,
    });

    expect(getByRole('button')).toHaveAttribute('aria-label', 'Menu options');
  });

  test('is disabled when disabled prop is true', () => {
    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger :disabled="true">
            Open
          </UiDropdownMenuTrigger>
          <UiDropdownMenuContent />
        </UiDropdownMenu>
      `,
    });

    expect(getByRole('button')).toBeDisabled();
  });
});

describe('UiDropdownMenuItem', () => {
  test('uses muted-subtle hover and highlighted fill', async () => {
    const user = userEvent.setup();
    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuItem,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuItem>Profile</UiDropdownMenuItem>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));

    await waitFor(() => {
      const item = getByRole('menuitem', { name: 'Profile' });
      expect(item).toHaveClass('hover:bg-background-surface-muted-subtle');
      expect(item).toHaveClass(
        'data-[highlighted]:bg-background-surface-muted-subtle',
      );
    });
  });

  test('is disabled when disabled prop is true', async () => {
    const user = userEvent.setup();
    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuItem,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuItem :disabled="true">Disabled Item</UiDropdownMenuItem>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));

    await waitFor(() => {
      const item = getByRole('menuitem', { name: 'Disabled Item' });
      expect(item).toHaveAttribute('aria-disabled', 'true');
    });
  });
});

describe('UiDropdownMenuSeparator', () => {
  test('renders separator', async () => {
    const user = userEvent.setup();
    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuItem,
        UiDropdownMenuSeparator,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuItem>Item 1</UiDropdownMenuItem>
            <UiDropdownMenuSeparator />
            <UiDropdownMenuItem>Item 2</UiDropdownMenuItem>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));

    await waitFor(() => {
      expect(getByRole('separator')).toBeInTheDocument();
    });
  });
});

describe('UiDropdownMenuCheckboxItem', () => {
  test('renders as menuitemcheckbox with correct initial state', async () => {
    const user = userEvent.setup();
    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuCheckboxItem,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuCheckboxItem :checked="true">Show Toolbar</UiDropdownMenuCheckboxItem>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));

    await waitFor(() => {
      const checkbox = getByRole('menuitemcheckbox', { name: 'Show Toolbar' });
      const indicator = checkbox.querySelector(
        '[data-slot="dropdown-menu-checkbox-indicator"]',
      );
      const nestedCheckbox = checkbox.querySelector('[data-slot="checkbox"]');

      expect(checkbox).toBeInTheDocument();
      expect(checkbox).toHaveAttribute('aria-checked', 'true');
      expect(checkbox).toHaveClass('hover:bg-background-surface-muted-subtle');
      expect(checkbox).toHaveClass(
        'data-[highlighted]:bg-background-surface-muted-subtle',
      );
      expect(indicator).toContainElement(nestedCheckbox);
      expect(nestedCheckbox).toHaveClass('pointer-events-none');
      expect(nestedCheckbox).toHaveAttribute('data-state', 'checked');
    });
  });
});

describe('UiDropdownMenuRadioGroup', () => {
  test('reflects selected value via modelValue', async () => {
    const user = userEvent.setup();
    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuRadioGroup,
        UiDropdownMenuRadioItem,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuRadioGroup modelValue="medium">
              <UiDropdownMenuRadioItem value="small">Small</UiDropdownMenuRadioItem>
              <UiDropdownMenuRadioItem value="medium">Medium</UiDropdownMenuRadioItem>
              <UiDropdownMenuRadioItem value="large">Large</UiDropdownMenuRadioItem>
            </UiDropdownMenuRadioGroup>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));

    await waitFor(() => {
      const selectedRadio = getByRole('menuitemradio', { name: 'Medium' });
      const indicator = selectedRadio.querySelector(
        '[data-slot="dropdown-menu-radio-indicator"]',
      );
      const nestedRadio = selectedRadio.querySelector(
        '[data-slot="radio-group-item"]',
      );

      expect(selectedRadio).toHaveAttribute('aria-checked', 'true');
      expect(getByRole('menuitemradio', { name: 'Small' })).toHaveAttribute(
        'aria-checked',
        'false',
      );
      expect(selectedRadio).toHaveClass(
        'hover:bg-background-surface-muted-subtle',
      );
      expect(selectedRadio).toHaveClass(
        'data-[highlighted]:bg-background-surface-muted-subtle',
      );
      expect(indicator).toContainElement(nestedRadio);
      expect(nestedRadio).toHaveClass('pointer-events-none');
      expect(nestedRadio).toHaveAttribute('data-state', 'checked');
    });
  });
});

describe('Keyboard Navigation', () => {
  test('navigates items with arrow keys', async () => {
    const user = userEvent.setup();
    const { getByRole } = renderDropdownMenu({
      items: ['First', 'Second', 'Third'],
    });

    await user.click(getByRole('button', { name: 'Open Menu' }));
    await waitFor(() => expect(getByRole('menu')).toBeInTheDocument());

    await user.keyboard('{ArrowDown}');
    await user.keyboard('{ArrowDown}');

    // Focus should move through items
    expect(getByRole('menuitem', { name: 'Second' })).toHaveFocus();
  });
});

describe('UiDropdownMenu controlled state', () => {
  test('respects controlled open prop', async () => {
    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuItem,
      },
      template: `
        <UiDropdownMenu :open="open">
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuItem>Item</UiDropdownMenuItem>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
      setup() {
        const open = ref(true);
        return { open };
      },
    });

    // Menu should be open initially
    await waitFor(() => {
      expect(getByRole('menu')).toBeInTheDocument();
    });
  });

  test('emits update:open when trigger is clicked', async () => {
    const user = userEvent.setup();
    const onUpdateOpen = vi.fn();

    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
      },
      template: `
        <UiDropdownMenu @update:open="onUpdateOpen">
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent />
        </UiDropdownMenu>
      `,
      setup() {
        return { onUpdateOpen };
      },
    });

    await user.click(getByRole('button', { name: 'Open' }));

    expect(onUpdateOpen).toHaveBeenCalledWith(true);
  });
});

describe('UiDropdownMenuLabel', () => {
  test('renders label text', async () => {
    const user = userEvent.setup();
    const { getByRole, getByText } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuLabel,
        UiDropdownMenuItem,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuLabel>My Account</UiDropdownMenuLabel>
            <UiDropdownMenuItem>Profile</UiDropdownMenuItem>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));

    await waitFor(() => {
      expect(getByText('My Account')).toBeInTheDocument();
    });
  });
});

describe('UiDropdownMenuCheckboxItem v-model', () => {
  test('emits update:checked when clicked', async () => {
    const user = userEvent.setup();
    const onUpdateChecked = vi.fn();

    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuCheckboxItem,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuCheckboxItem :checked="false" @update:checked="onUpdateChecked">
              Option
            </UiDropdownMenuCheckboxItem>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
      setup() {
        return { onUpdateChecked };
      },
    });

    await user.click(getByRole('button', { name: 'Open' }));
    await waitFor(() =>
      expect(getByRole('menuitemcheckbox')).toBeInTheDocument(),
    );

    await user.click(getByRole('menuitemcheckbox', { name: 'Option' }));

    expect(onUpdateChecked).toHaveBeenCalledWith(true);
  });

  test('is disabled when disabled prop is true', async () => {
    const user = userEvent.setup();
    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuCheckboxItem,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuCheckboxItem :disabled="true">Disabled Option</UiDropdownMenuCheckboxItem>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));

    await waitFor(() => {
      expect(
        getByRole('menuitemcheckbox', { name: 'Disabled Option' }),
      ).toHaveAttribute('aria-disabled', 'true');
    });
  });
});

describe('UiDropdownMenuRadioGroup v-model', () => {
  test('emits update:modelValue when radio item is clicked', async () => {
    const user = userEvent.setup();
    const onUpdateModelValue = vi.fn();

    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuRadioGroup,
        UiDropdownMenuRadioItem,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuRadioGroup modelValue="small" @update:modelValue="onUpdateModelValue">
              <UiDropdownMenuRadioItem value="small">Small</UiDropdownMenuRadioItem>
              <UiDropdownMenuRadioItem value="large">Large</UiDropdownMenuRadioItem>
            </UiDropdownMenuRadioGroup>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
      setup() {
        return { onUpdateModelValue };
      },
    });

    await user.click(getByRole('button', { name: 'Open' }));
    await waitFor(() =>
      expect(getByRole('menuitemradio', { name: 'Large' })).toBeInTheDocument(),
    );

    await user.click(getByRole('menuitemradio', { name: 'Large' }));

    expect(onUpdateModelValue).toHaveBeenCalledWith('large');
  });
});

describe('UiDropdownMenuRadioItem', () => {
  test('is disabled when disabled prop is true', async () => {
    const user = userEvent.setup();
    const { getByRole } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuRadioGroup,
        UiDropdownMenuRadioItem,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuRadioGroup>
              <UiDropdownMenuRadioItem value="enabled">Enabled</UiDropdownMenuRadioItem>
              <UiDropdownMenuRadioItem value="disabled" :disabled="true">Disabled</UiDropdownMenuRadioItem>
            </UiDropdownMenuRadioGroup>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));

    await waitFor(() => {
      expect(getByRole('menuitemradio', { name: 'Disabled' })).toHaveAttribute(
        'aria-disabled',
        'true',
      );
    });
  });
});

describe('UiDropdownMenuSub', () => {
  test('opens submenu when hovering over sub trigger', async () => {
    const user = userEvent.setup();
    const { getByRole, getByText } = render({
      components: {
        UiDropdownMenu,
        UiDropdownMenuTrigger,
        UiDropdownMenuContent,
        UiDropdownMenuItem,
        UiDropdownMenuSub,
        UiDropdownMenuSubTrigger,
        UiDropdownMenuSubContent,
      },
      template: `
        <UiDropdownMenu>
          <UiDropdownMenuTrigger>Open</UiDropdownMenuTrigger>
          <UiDropdownMenuContent>
            <UiDropdownMenuSub>
              <UiDropdownMenuSubTrigger>More Options</UiDropdownMenuSubTrigger>
              <UiDropdownMenuSubContent>
                <UiDropdownMenuItem>Sub Item 1</UiDropdownMenuItem>
                <UiDropdownMenuItem>Sub Item 2</UiDropdownMenuItem>
              </UiDropdownMenuSubContent>
            </UiDropdownMenuSub>
          </UiDropdownMenuContent>
        </UiDropdownMenu>
      `,
    });

    await user.click(getByRole('button', { name: 'Open' }));
    await waitFor(() => expect(getByRole('menu')).toBeInTheDocument());

    const subTrigger = getByText('More Options').closest(
      '[data-slot="dropdown-menu-sub-trigger"]',
    );
    expect(subTrigger).toHaveClass('hover:bg-background-surface-muted-subtle');
    expect(subTrigger).toHaveClass(
      'data-[highlighted]:bg-background-surface-muted-subtle',
    );
    expect(subTrigger).toHaveClass(
      'data-[state=open]:bg-background-surface-muted-subtle',
    );

    // Hover over sub trigger to open submenu
    await user.hover(getByText('More Options'));

    await waitFor(() => {
      expect(getByRole('menuitem', { name: 'Sub Item 1' })).toBeInTheDocument();
    });
  });
});
