import { describe, it, expect, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import { defineComponent, ref, nextTick } from 'vue';
import MCombobox from './MCombobox.vue';

const MOptionListboxStub = defineComponent({
  name: 'MOptionListbox',
  props: [
    'modelValue',
    'open',
    'multiple',
    'search',
    'actions',
    'checkableSections',
    'searchPlaceholder',
    'selectLabel',
    'clearLabel',
    'options',
    'id',
  ],
  emits: ['update:modelValue', 'open', 'close'],
  setup() {
    const activeIndex = ref(-1);
    const listboxEl = ref(document.createElement('div'));

    // On crée une fonction mock que l’on expose
    const toggleValue = vi.fn();

    return {
      activeIndex,
      listboxEl,
      handleKeydown: () => {},
      toggleValue, // <- expose ici
    };
  },
  template: `<div />`,
});

const MTagStub = defineComponent({
  name: 'MTag',
  props: ['id', 'label', 'type', 'size'],
  emits: ['remove-tag'],
  template: `<div class="m-tag-stub">{{ label }}</div>`,
});

const MButtonStub = defineComponent({
  name: 'MButton',
  props: ['outlined', 'size'],
  emits: ['click'],
  template: `<button @click="$emit('click')"><slot/></button>`,
});

const CrossCircleFilled24 = defineComponent({
  name: 'CrossCircleFilled24',
  template: `<svg/>`,
});
const ChevronDown24 = defineComponent({
  name: 'ChevronDown24',
  template: `<svg/>`,
});

describe('MCombobox', () => {
  const options = [
    { label: 'One', value: 1 },
    { label: 'Two', value: 2 },
    { label: 'Three', value: 3 },
  ];

  it('renders placeholder when no selection', () => {
    const wrapper = mount(MCombobox, {
      props: { modelValue: null, options },
      global: {
        components: {
          MOptionListbox: MOptionListboxStub,
          MTag: MTagStub,
          MButton: MButtonStub,
          CrossCircleFilled24,
          ChevronDown24,
        },
      },
    });

    const control = wrapper.find('.mc-combobox__control');
    expect(control.exists()).toBe(true);
    expect(control.text()).toBe('Select an option');
  });

  it('renders selected label for single value', () => {
    const wrapper = mount(MCombobox, {
      props: { modelValue: 1, options },
      global: {
        components: {
          MOptionListbox: MOptionListboxStub,
          MTag: MTagStub,
          MButton: MButtonStub,
          CrossCircleFilled24,
          ChevronDown24,
        },
      },
    });

    const control = wrapper.find('.mc-combobox__control');
    expect(control.text()).toBe('One');
  });

  it('multiple selection shows joins values', async () => {
    const wrapper = mount(MCombobox, {
      props: { modelValue: [1, 2], multiple: true, options },
      global: {
        components: {
          MOptionListbox: MOptionListboxStub,
          MTag: MTagStub,
          MButton: MButtonStub,
          CrossCircleFilled24,
          ChevronDown24,
        },
      },
    });

    expect(wrapper.find('.mc-combobox__control').text()).toBe('One, Two');
  });

  it('toggles listbox open/close on control click', async () => {
    const wrapper = mount(MCombobox, {
      props: { modelValue: null, options },
      global: {
        components: {
          MOptionListbox: MOptionListboxStub,
          MTag: MTagStub,
          MButton: MButtonStub,
          CrossCircleFilled24,
          ChevronDown24,
        },
      },
    });

    const root = wrapper.find('.mc-combobox');
    const control = wrapper.find('.mc-combobox__control');

    await control.trigger('click');
    expect(root.classes()).toContain('mc-combobox--open');

    await control.trigger('click');
    expect(root.classes()).not.toContain('mc-combobox--open');
  });

  it('clear button clears selection and emits update:modelValue', async () => {
    const wrapperSingle = mount(MCombobox, {
      props: { modelValue: 1, clearable: true, options },
      global: {
        components: {
          MOptionListbox: MOptionListboxStub,
          MTag: MTagStub,
          MButton: MButtonStub,
          CrossCircleFilled24,
          ChevronDown24,
        },
      },
    });

    const clearBtnSingle = wrapperSingle.find('.mc-combobox__clear');
    expect(clearBtnSingle.exists()).toBe(true);
    await clearBtnSingle.trigger('click');
    const emittedSingle = wrapperSingle.emitted('update:modelValue') || [];
    expect(emittedSingle.length).toBeGreaterThan(0);
    expect(emittedSingle[emittedSingle.length - 1][0]).toBeNull();

    const wrapperMulti = mount(MCombobox, {
      props: { modelValue: [1], multiple: true, clearable: true, options },
      global: {
        components: {
          MOptionListbox: MOptionListboxStub,
          MTag: MTagStub,
          MButton: MButtonStub,
          CrossCircleFilled24,
          ChevronDown24,
        },
      },
    });

    const clearBtnMulti = wrapperMulti.find('.mc-combobox__clear');
    expect(clearBtnMulti.exists()).toBe(true);
    await clearBtnMulti.trigger('click');
    const emittedMulti = wrapperMulti.emitted('update:modelValue') || [];
    expect(emittedMulti.length).toBeGreaterThan(0);

    const last = emittedMulti[emittedMulti.length - 1][0];
    expect(Array.isArray(last)).toBe(true);
    expect(last).toEqual([]);
  });

  it('activeDescendant reflects child listbox activeIndex', async () => {
    const wrapper = mount(MCombobox, {
      props: { modelValue: null, options },
      global: {
        components: {
          MOptionListbox: MOptionListboxStub,
          MTag: MTagStub,
          MButton: MButtonStub,
          CrossCircleFilled24,
          ChevronDown24,
        },
      },
    });

    const listboxRef = (wrapper.vm as InstanceType<typeof MCombobox>).$refs
      .listbox as { activeIndex: number };
    expect(listboxRef).toBeTruthy();

    listboxRef.activeIndex = 2;
    await nextTick();

    const control = wrapper.find('.mc-combobox__control');
    const attr = control.attributes()['aria-activedescendant'];
    expect(attr).toBeTruthy();

    expect(attr.includes('-2')).toBe(true);
  });

  describe('accessibility attributes', () => {
    function mountCombobox(props = {}) {
      return mount(MCombobox, {
        props: { modelValue: null, options, ...props },
        global: {
          components: {
            MOptionListbox: MOptionListboxStub,
            CrossCircleFilled24,
            ChevronDown24,
          },
        },
      });
    }

    it('control button has aria-expanded="false" when closed', () => {
      const wrapper = mountCombobox();
      const control = wrapper.find('.mc-combobox__control');
      expect(control.attributes('aria-expanded')).toBe('false');
      expect(control.attributes('aria-haspopup')).toBe('listbox');
    });

    it('control button has default aria-label "Combobox input"', () => {
      const wrapper = mountCombobox();
      expect(
        wrapper.find('.mc-combobox__control').attributes('aria-label'),
      ).toBe('Combobox input');
    });

    it('control button uses controlAriaLabel prop when provided', () => {
      const wrapper = mountCombobox({ controlAriaLabel: 'Select a country' });
      expect(
        wrapper.find('.mc-combobox__control').attributes('aria-label'),
      ).toBe('Select a country');
    });

    it('expand icon button has tabindex="-1" and aria-hidden="true"', () => {
      const wrapper = mountCombobox();
      const icon = wrapper.find('.mc-combobox__icon');
      expect(icon.attributes('tabindex')).toBe('-1');
      expect(icon.attributes('aria-hidden')).toBe('true');
    });
  });

  describe('open prop (controlled mode)', () => {
    function mountControlled(open: boolean) {
      return mount(MCombobox, {
        props: { modelValue: null, options, open },
        global: {
          components: {
            MOptionListbox: MOptionListboxStub,
            MTag: MTagStub,
            MButton: MButtonStub,
            CrossCircleFilled24,
            ChevronDown24,
          },
        },
      });
    }

    it('renders open when open prop is true', () => {
      const wrapper = mountControlled(true);
      expect(wrapper.find('.mc-combobox').classes()).toContain(
        'mc-combobox--open',
      );
    });

    it('renders closed when open prop is false', () => {
      const wrapper = mountControlled(false);
      expect(wrapper.find('.mc-combobox').classes()).not.toContain(
        'mc-combobox--open',
      );
    });

    it('emits update:open with toggled value on click without changing visual state', async () => {
      const wrapper = mountControlled(false);
      await wrapper.find('.mc-combobox__control').trigger('click');
      expect(wrapper.emitted('update:open')?.[0]).toEqual([true]);
      expect(wrapper.find('.mc-combobox').classes()).not.toContain(
        'mc-combobox--open',
      );

      const wrapper2 = mountControlled(true);
      await wrapper2.find('.mc-combobox__control').trigger('click');
      expect(wrapper2.emitted('update:open')?.[0]).toEqual([false]);
      expect(wrapper2.find('.mc-combobox').classes()).toContain(
        'mc-combobox--open',
      );
    });

    it('reflects open prop changes from parent', async () => {
      const wrapper = mountControlled(false);
      expect(wrapper.find('.mc-combobox').classes()).not.toContain(
        'mc-combobox--open',
      );

      await wrapper.setProps({ open: true });
      expect(wrapper.find('.mc-combobox').classes()).toContain(
        'mc-combobox--open',
      );

      await wrapper.setProps({ open: false });
      expect(wrapper.find('.mc-combobox').classes()).not.toContain(
        'mc-combobox--open',
      );
    });

    it('opens and closes correctly with v-model:open', async () => {
      const Parent = defineComponent({
        components: { MCombobox },
        setup() {
          const open = ref(false);
          return { open, options };
        },
        template: `<MCombobox v-model:open="open" :model-value="null" :options="options" />`,
      });

      const wrapper = mount(Parent, {
        global: {
          components: {
            MOptionListbox: MOptionListboxStub,
            MTag: MTagStub,
            MButton: MButtonStub,
            CrossCircleFilled24,
            ChevronDown24,
          },
        },
      });

      expect(wrapper.find('.mc-combobox').classes()).not.toContain(
        'mc-combobox--open',
      );

      await wrapper.find('.mc-combobox__control').trigger('click');
      expect(wrapper.find('.mc-combobox').classes()).toContain(
        'mc-combobox--open',
      );

      await wrapper.find('.mc-combobox__control').trigger('click');
      expect(wrapper.find('.mc-combobox').classes()).not.toContain(
        'mc-combobox--open',
      );
    });
  });

  it('clicking outside closes the listbox', async () => {
    const wrapper = mount(MCombobox, {
      props: { modelValue: null, options },
      global: {
        components: {
          MOptionListbox: MOptionListboxStub,
          MTag: MTagStub,
          MButton: MButtonStub,
          CrossCircleFilled24,
          ChevronDown24,
        },
      },
      attachTo: document.body,
    });

    const root = wrapper.find('.mc-combobox');
    const control = wrapper.find('.mc-combobox__control');

    await control.trigger('click');
    expect(root.classes()).toContain('mc-combobox--open');

    document.dispatchEvent(new MouseEvent('click', { bubbles: true }));

    await nextTick();
    expect(root.classes()).not.toContain('mc-combobox--open');
  });
});
