import RawAutocomplete, {
  type Props,
} from '@/components/form/RawAutocomplete.vue';
import { mount } from '@vue/test-utils';
import { field } from '@/plugins/field';
import { nextTick } from 'vue';

interface PropsAndModel extends Props {
  modelValue: any;
}

const factory = (props?: PropsAndModel) => {
  const wrapper = mount(RawAutocomplete, {
    global: {
      plugins: [field],
    },
    props: {
      options: [
        { label: 'titi', value: 'i' },
        { label: 'toto', value: 'o' },
        { label: 'tutu', value: 'u' },
      ],
      modelValue: '',
      ...props,
    },
    attachTo: document.body,
  });

  const inputElement = wrapper.get('input');
  const menu = wrapper.get('[data-testid="autocomplete-menu"]');
  return { wrapper, inputElement, menu };
};

describe('RawAutocomplete', () => {
  it('should mount component correctly', async () => {
    const { wrapper } = factory();

    expect(wrapper.text()).toContain('titi');
    expect(wrapper.text()).toContain('toto');
    expect(wrapper.text()).toContain('tutu');
  });

  it('should load with default value', async () => {
    const { inputElement } = factory({ modelValue: 'i' } as PropsAndModel);
    expect(inputElement.element.value).toStrictEqual('titi');
  });

  it('should change input value if parent modelvalue change', async () => {
    const { wrapper, inputElement } = factory({
      modelValue: 'i',
    } as PropsAndModel);
    expect(inputElement.element.value).toStrictEqual('titi');
    await wrapper.setProps({ modelValue: 'u' });
    expect(inputElement.element.value).toStrictEqual('tutu');
  });

  it('should reset input value if parent modelvalue change for unknown value', async () => {
    const { wrapper, inputElement } = factory({
      modelValue: 'i',
    } as PropsAndModel);
    expect(inputElement.element.value).toStrictEqual('titi');
    await wrapper.setProps({ modelValue: 'not_found' });
    expect(inputElement.element.value).toStrictEqual('');
  });

  it('should not reset input value if parent modelvalue change for nullable value', async () => {
    const { wrapper, inputElement } = factory({
      modelValue: 'i',
    } as PropsAndModel);
    expect(inputElement.element.value).toStrictEqual('titi');
    await wrapper.setProps({ modelValue: undefined });
    expect(inputElement.element.value).toStrictEqual('titi');
  });

  it('should emit modelValue if clicks on option', async () => {
    const { wrapper, inputElement, menu } = factory({
      modelValue: null,
    } as PropsAndModel);
    await inputElement.trigger('click');
    await menu.find('[data-testid="i"]').trigger('click');
    expect(wrapper.emitted()['select']).toStrictEqual([
      [{ label: 'titi', value: 'i' }],
    ]);
    expect(wrapper.emitted()['update:modelValue']).toStrictEqual([['i']]);
  });

  it('should reset modelValue if user removes text input', async () => {
    const { wrapper, inputElement } = factory({
      modelValue: 'i',
    } as PropsAndModel);
    expect(inputElement.element.value).toStrictEqual('titi');
    inputElement.element.value = '';
    await inputElement.trigger('input');
    expect(inputElement.element.value).toStrictEqual('');
    expect(wrapper.emitted()['update:modelValue']).toStrictEqual([[null]]);
  });

  it('should emit event on option selection', async () => {
    const { wrapper, inputElement } = factory();
    const titi = wrapper.get('[data-testid="i"]');
    expect(inputElement.element.value).toStrictEqual('');
    await titi.trigger('click');
    expect(wrapper.emitted()['select']).toHaveLength(1);
    expect(wrapper.emitted()['select'][0]).toStrictEqual([
      { value: 'i', label: 'titi' },
    ]);
  });

  it('should filter options', async () => {
    const { wrapper, inputElement } = factory();

    await inputElement.setValue('ti');

    expect(wrapper.text()).toContain('titi');
    expect(wrapper.text()).not.toContain('toto');
    expect(wrapper.text()).not.toContain('tutu');
  });
});
