import { h } from 'vue';
import { mount } from '@vue/test-utils';
import { describe, it, expect } from 'vitest';
import MPopover from './MPopover.vue';
import MIconButton from '../iconbutton/MIconButton.vue';

describe('MPopover.vue', () => {
  it('applies classes based on props', () => {
    const wrapper = mount(MPopover, {
      props: {
        appearance: 'inverse',
        position: 'right',
        pointer: false,
        size: 'l',
      },
    });

    const rootDiv = wrapper.find('.mc-popover');
    expect(rootDiv.exists()).toBe(true);
    expect(rootDiv.classes()).toContain('mc-popover');
    expect(rootDiv.classes()).toContain('mc-popover--inverse');
    expect(rootDiv.classes()).toContain('mc-popover--right');
    expect(rootDiv.classes()).toContain('mc-popover--no-pointer');
    expect(rootDiv.classes()).toContain('mc-popover--l');
  });

  it('sets aria-labelledby and aria-describedby when title/description provided', () => {
    const wrapper = mount(MPopover, {
      props: { title: 'My title', description: 'My description' },
    });

    const wrapperEl = wrapper.find('.mc-popover__wrapper');
    const id = wrapperEl.attributes('id');
    expect(id).toBeTruthy();
    expect(wrapperEl.attributes('aria-labelledby')).toBe(`${id}-title`);
    expect(wrapperEl.attributes('aria-describedby')).toBe(`${id}-description`);
  });

  it('renders title and description', () => {
    const wrapper = mount(MPopover, {
      props: { title: 'T', description: 'D' },
    });

    const wrapperEl = wrapper.find('.mc-popover__wrapper');
    const id = wrapperEl.attributes('id');

    const title = wrapper.find(`#${id}-title`);
    const description = wrapper.find(`#${id}-description`);
    expect(title.exists()).toBe(true);
    expect(title.text()).toBe('T');
    expect(description.exists()).toBe(true);
    expect(description.text()).toBe('D');
  });

  it('renders default slot content', () => {
    const wrapper = mount(MPopover, {
      slots: { default: '<div class="slot-content">Hello</div>' },
    });

    expect(wrapper.find('.slot-content').exists()).toBe(true);
    expect(wrapper.find('.slot-content').text()).toBe('Hello');
  });

  it('renders footer slot when provided', () => {
    const wrapper = mount(MPopover, {
      slots: { footer: '<button class="footer-button">OK</button>' },
    });

    const footer = wrapper.find('.mc-popover__footer');
    expect(footer.exists()).toBe(true);
    const button = footer.find('button');
    expect(button.exists()).toBe(true);
    expect(button.text()).toBe('OK');
  });

  it('renders close button when closable is true and binds id', () => {
    const wrapper = mount(MPopover, { props: { closable: true } });

    const closeButton = wrapper.findComponent(MIconButton);
    expect(closeButton.exists()).toBe(true);

    const wrapperEl = wrapper.find('.mc-popover__wrapper');
    const id = wrapperEl.attributes('id');
    expect(closeButton.attributes('popovertarget')).toBe(id);
    expect(closeButton.attributes('aria-label')).toBe('Close button');
  });

  it('activator slot receives id and can bind popovertarget', () => {
    const wrapper = mount(MPopover, {
      slots: {
        activator: (slotProps: { id: string }) =>
          h(
            'button',
            { popovertarget: slotProps.id, class: 'activator' },
            'Open',
          ),
      },
    });

    const wrapperEl = wrapper.find('.mc-popover__wrapper');
    const id = wrapperEl.attributes('id');
    const activator = wrapper.find('.activator');
    expect(activator.exists()).toBe(true);
    expect(activator.attributes('popovertarget')).toBe(id);
  });

  describe('Focus management (toggle)', () => {
    it('focuses the first focusable element inside when opened', async () => {
      const wrapper = mount(MPopover, {
        attachTo: document.body,
        props: { closable: true },
      });

      const popoverEl = wrapper.find('.mc-popover__wrapper').element;
      const toggleEvent = new Event('toggle') as ToggleEvent;
      Object.defineProperty(toggleEvent, 'newState', { value: 'open' });
      popoverEl.dispatchEvent(toggleEvent);
      await wrapper.vm.$nextTick();

      const firstFocusable = popoverEl.querySelector<HTMLElement>(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
      );
      expect(document.activeElement).toBe(firstFocusable);
      wrapper.unmount();
    });

    it('focuses the popover container when opened without focusable content', async () => {
      const wrapper = mount(MPopover, {
        attachTo: document.body,
        props: {
          closable: false,
          title: 'Information',
          description: 'Read-only content',
        },
      });

      const popoverEl = wrapper.find('.mc-popover__wrapper')
        .element as HTMLElement;
      const toggleEvent = new Event('toggle') as ToggleEvent;
      Object.defineProperty(toggleEvent, 'newState', { value: 'open' });
      popoverEl.dispatchEvent(toggleEvent);
      await wrapper.vm.$nextTick();

      expect(document.activeElement).toBe(popoverEl);
      wrapper.unmount();
    });

    it('returns focus to the trigger when closed with focus inside the popover', async () => {
      const wrapper = mount(MPopover, {
        attachTo: document.body,
        props: { closable: true },
        slots: {
          activator: (slotProps: { id: string }) =>
            h('button', {
              popovertarget: slotProps.id,
              class: 'trigger-btn',
            }),
        },
      });

      // Simulate focus being on the close button (inside the popover)
      const closeBtn = wrapper.findComponent(MIconButton).find('button')
        .element as HTMLElement;
      closeBtn.focus();

      const popoverEl = wrapper.find('.mc-popover__wrapper').element;
      const toggleEvent = new Event('toggle') as ToggleEvent;
      Object.defineProperty(toggleEvent, 'newState', { value: 'closed' });
      popoverEl.dispatchEvent(toggleEvent);
      await wrapper.vm.$nextTick();

      const trigger = wrapper.find('.trigger-btn').element as HTMLElement;
      expect(document.activeElement).toBe(trigger);
      wrapper.unmount();
    });

    it('returns focus to the trigger when closed with focus on document.body', async () => {
      const wrapper = mount(MPopover, {
        attachTo: document.body,
        slots: {
          activator: (slotProps: { id: string }) =>
            h('button', {
              popovertarget: slotProps.id,
              class: 'trigger-btn',
            }),
        },
      });

      // Simulate browser moving focus to body after popover collapses
      (document.activeElement as HTMLElement)?.blur?.();

      const popoverEl = wrapper.find('.mc-popover__wrapper').element;
      const toggleEvent = new Event('toggle') as ToggleEvent;
      Object.defineProperty(toggleEvent, 'newState', { value: 'closed' });
      popoverEl.dispatchEvent(toggleEvent);
      await wrapper.vm.$nextTick();

      const trigger = wrapper.find('.trigger-btn').element as HTMLElement;
      expect(document.activeElement).toBe(trigger);
      wrapper.unmount();
    });

    it('does not steal focus when closed by an outside click (focus already on another element)', async () => {
      const wrapper = mount(MPopover, {
        attachTo: document.body,
        slots: {
          activator: (slotProps: { id: string }) =>
            h('button', {
              popovertarget: slotProps.id,
              class: 'trigger-btn',
            }),
        },
      });

      // Simulate user clicking an outside input
      const outsideInput = document.createElement('input');
      document.body.appendChild(outsideInput);
      outsideInput.focus();

      const popoverEl = wrapper.find('.mc-popover__wrapper').element;
      const toggleEvent = new Event('toggle') as ToggleEvent;
      Object.defineProperty(toggleEvent, 'newState', { value: 'closed' });
      popoverEl.dispatchEvent(toggleEvent);
      await wrapper.vm.$nextTick();

      // Focus should remain on the outside input, not be stolen by the trigger
      expect(document.activeElement).toBe(outsideInput);
      outsideInput.remove();
      wrapper.unmount();
    });
  });
});
