import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import { defineComponent, h, markRaw } from 'vue';
import MActionListbox from './MActionListbox.vue';
import MDivider from '../divider/MDivider.vue';
import MIconButton from '../iconbutton/MIconButton.vue';
import { Cross24 } from '@mozaic-ds/icons-vue';

// Dummy icon wrapped with markRaw to avoid reactive warnings
const DummyIcon = markRaw(
  defineComponent({
    name: 'DummyIcon',
    render() {
      return h('svg', { 'data-testid': 'icon' }, [
        h('circle', { cx: 10, cy: 10, r: 10 }),
      ]);
    },
  }),
);

const items = [
  {
    label: 'Duplicate',
    icon: DummyIcon,
  },
  {
    id: 'move',
    label: 'Move to...',
    icon: DummyIcon,
  },
  {
    label: 'Download',
    icon: DummyIcon,
  },
  {
    label: 'Delete',
    icon: DummyIcon,
    appearance: 'danger' as const,
    divider: true,
  },
];

describe('MActionListbox', () => {
  function mountComponent(props = {}, options = {}) {
    return mount(MActionListbox, {
      props: { items, ...props },
      global: {
        components: {
          MDivider,
          MIconButton,
          Cross24,
        },
      },
      ...options,
    });
  }

  it('renders the title if provided', () => {
    const wrapper = mountComponent({ title: 'Action List' });
    expect(wrapper.find('.mc-listbox__title').text()).toBe('Action List');
  });

  it('renders all item labels', () => {
    const wrapper = mountComponent();
    items.forEach((item) => {
      expect(wrapper.text()).toContain(item.label);
    });
  });

  it('renders icons for each item', () => {
    const wrapper = mountComponent();
    const icons = wrapper.findAll('[data-testid="icon"]');
    expect(icons.length).toBe(items.length);
  });

  it('adds danger class to items with appearance set to danger', () => {
    const wrapper = mountComponent();
    const dangerousItem = wrapper.findAll('.mc-action-list__element--danger');

    expect(dangerousItem.length).toBe(1);
    expect(dangerousItem[0].text()).toContain('Delete');
  });

  it('renders MDivider when divider is true', () => {
    const wrapper = mountComponent();
    const dividers = wrapper.findAll('.mc-action-list__divider');
    expect(dividers.length).toBe(1);
  });

  it('applies disabled class when item.disabled is true', () => {
    const disabledItems = [...items, { label: 'Disabled', disabled: true }];

    const wrapper = mountComponent({ items: disabledItems });

    const disabled = wrapper.findAll('.mc-action-list__element--disabled');
    expect(disabled.length).toBe(1);
    expect(disabled[0].text()).toContain('Disabled');
  });

  it('applies mc-listbox--bottom by default', () => {
    const wrapper = mountComponent();
    expect(wrapper.find('.mc-listbox').classes()).toContain(
      'mc-listbox--bottom',
    );
  });

  it('applies mc-listbox--top when position is "top"', () => {
    const wrapper = mountComponent({ position: 'top' });
    expect(wrapper.find('.mc-listbox').classes()).toContain('mc-listbox--top');
  });

  it('applies mc-listbox--left when position is "left"', () => {
    const wrapper = mountComponent({ position: 'left' });
    expect(wrapper.find('.mc-listbox').classes()).toContain('mc-listbox--left');
  });

  it('emits "close" when close button is clicked', async () => {
    const wrapper = mountComponent({ title: 'Action List' });

    await wrapper.find('.mc-listbox__close').trigger('click');

    expect(wrapper.emitted('close')).toBeTruthy();
    expect(wrapper.emitted('close')?.length).toBe(1);
  });

  it('emits "action" when an item is clicked', async () => {
    const wrapper = mountComponent({ title: 'Action List' });

    const actions = wrapper.findAll('.mc-action-list__button');
    await actions[0].trigger('click');

    expect(wrapper.emitted('action')).toBeTruthy();
    expect(wrapper.emitted('action')?.[0][0]).toBe(0);

    await actions[1].trigger('click');
    expect(wrapper.emitted('action')?.[1][0]).toBe('move');
  });

  it('has role="menu" on the list and role="menuitem" on each button', () => {
    const wrapper = mountComponent();
    expect(wrapper.find('ul.mc-action-list').attributes('role')).toBe('menu');
    const menuItems = wrapper.findAll('button[role="menuitem"]');
    expect(menuItems.length).toBe(items.length);
  });

  describe('keyboard navigation', () => {
    function mountAttached(props = {}) {
      const div = document.createElement('div');
      document.body.appendChild(div);
      const wrapper = mount(MActionListbox, {
        props: { items, ...props },
        attachTo: div,
        global: { components: { MDivider, MIconButton, Cross24 } },
      });
      return wrapper;
    }

    it('ArrowDown moves focus to the next item', async () => {
      const wrapper = mountAttached();
      const buttons = wrapper.findAll('button[role="menuitem"]');
      await buttons[0].element.focus();
      await wrapper
        .find('ul[role="menu"]')
        .trigger('keydown', { key: 'ArrowDown' });
      expect(document.activeElement).toBe(buttons[1].element);
      wrapper.unmount();
    });

    it('ArrowDown wraps from last to first item', async () => {
      const wrapper = mountAttached();
      const buttons = wrapper.findAll('button[role="menuitem"]');
      await buttons[buttons.length - 1].element.focus();
      await wrapper
        .find('ul[role="menu"]')
        .trigger('keydown', { key: 'ArrowDown' });
      expect(document.activeElement).toBe(buttons[0].element);
      wrapper.unmount();
    });

    it('ArrowUp moves focus to the previous item', async () => {
      const wrapper = mountAttached();
      const buttons = wrapper.findAll('button[role="menuitem"]');
      await buttons[1].element.focus();
      await wrapper
        .find('ul[role="menu"]')
        .trigger('keydown', { key: 'ArrowUp' });
      expect(document.activeElement).toBe(buttons[0].element);
      wrapper.unmount();
    });

    it('ArrowUp wraps from first to last item', async () => {
      const wrapper = mountAttached();
      const buttons = wrapper.findAll('button[role="menuitem"]');
      await buttons[0].element.focus();
      await wrapper
        .find('ul[role="menu"]')
        .trigger('keydown', { key: 'ArrowUp' });
      expect(document.activeElement).toBe(buttons[buttons.length - 1].element);
      wrapper.unmount();
    });

    it('Home moves focus to the first item', async () => {
      const wrapper = mountAttached();
      const buttons = wrapper.findAll('button[role="menuitem"]');
      await buttons[2].element.focus();
      await wrapper.find('ul[role="menu"]').trigger('keydown', { key: 'Home' });
      expect(document.activeElement).toBe(buttons[0].element);
      wrapper.unmount();
    });

    it('End moves focus to the last item', async () => {
      const wrapper = mountAttached();
      const buttons = wrapper.findAll('button[role="menuitem"]');
      await buttons[0].element.focus();
      await wrapper.find('ul[role="menu"]').trigger('keydown', { key: 'End' });
      expect(document.activeElement).toBe(buttons[buttons.length - 1].element);
      wrapper.unmount();
    });

    it('Escape emits "close"', async () => {
      const wrapper = mountAttached();
      await wrapper
        .find('ul[role="menu"]')
        .trigger('keydown', { key: 'Escape' });
      expect(wrapper.emitted('close')).toBeTruthy();
      wrapper.unmount();
    });

    it('disabled items are skipped during ArrowDown navigation', async () => {
      const itemsWithDisabled = [
        { label: 'First' },
        { label: 'Disabled', disabled: true },
        { label: 'Third' },
      ];
      const wrapper = mount(MActionListbox, {
        props: { items: itemsWithDisabled },
        attachTo: document.body,
        global: { components: { MDivider, MIconButton, Cross24 } },
      });
      const enabledButtons = wrapper.findAll(
        'button[role="menuitem"]:not([disabled])',
      );
      await enabledButtons[0].element.focus();
      await wrapper
        .find('ul[role="menu"]')
        .trigger('keydown', { key: 'ArrowDown' });
      expect(document.activeElement).toBe(enabledButtons[1].element);
      wrapper.unmount();
    });
  });
});
