import UiBmsTable from '@/components/table/UiBmsTable.vue';
import BmsTooltip from '@/components/feedback/BmsTooltip.vue';
import { field } from '@/plugins/field';
import { mount, MountingOptions } from '@vue/test-utils';
import UiBmsInputCheckbox from '../form/UiBmsInputCheckbox.vue';
import { SelectMode, SortValue } from '@/models';
import BmsInputRadio from '../form/BmsInputRadio.vue';

const intersectionObserverMock = () => ({
  observe: () => null,
});
window.IntersectionObserver = vi
  .fn()
  .mockImplementation(intersectionObserverMock);

const headers = [
  { label: 'Nom', key: 'name', class: 'customClass0' },
  { label: 'Titre', key: 'title', align: 'start' },
  { label: 'Age', key: 'age', align: 'center' },
  { label: 'Surnom', key: 'nickname', align: 'end' },
];

const items = [
  { name: 'Hello', title: 'Hello world', age: 4, nickname: 'Nick1' },
  { name: 'Foo', title: 'Bar', age: 10, nickname: 'Nick2' },
];

const factory = (options: MountingOptions<any, {}> = {}) => {
  const wrapper = mount(UiBmsTable, {
    global: {
      plugins: [field],
    },
    props: {
      headers,
      items,
      size: 0,
      totalSize: 2,
    },
    ...options,
  } as any);
  return {
    wrapper,
    selectedMessage: () => wrapper.findAll('.bms-table__selected')[0],

    getAllCheckboxes: () => wrapper.findAllComponents(UiBmsInputCheckbox),
    getAllCheckedCheckboxes: () =>
      wrapper
        .findAllComponents(UiBmsInputCheckbox)
        .filter((checkbox) => checkbox.props('modelValue') === true),
    getTableCheckbox: () => wrapper.findAllComponents(UiBmsInputCheckbox)[0],
    getFirstRowCheckbox: () => wrapper.findAllComponents(UiBmsInputCheckbox)[1],

    getAllCheckedRadio: () =>
      wrapper
        .findAllComponents(BmsInputRadio)
        .filter((checkbox) => checkbox.props('modelValue') === true),
    getAllRadio: () => wrapper.findAllComponents(BmsInputRadio),
  };
};
describe('UiBmsTable', () => {
  it('should reflect header correctly', async () => {
    const { wrapper } = factory({
      slots: {
        'custom-actions': 'Hello world!',
      },
    });

    expect(wrapper.text()).toContain('Hello world!');
  });

  it('should have correct number of cells', () => {
    const { wrapper } = factory();

    expect(wrapper.findAll('thead th').length).toEqual(headers.length);
  });

  it('should render BmsTooltip for header when helperText is set', () => {
    const headersWithHelper = [
      { ...headers[0], helperText: 'Aide sur la colonne Nom' },
      ...headers.slice(1),
    ];
    const { wrapper } = factory({
      props: {
        headers: headersWithHelper,
        items,
        totalSize: 2,
      },
    });
    const headerTooltips = wrapper
      .findAllComponents(BmsTooltip)
      .filter((c) => c.props('tooltipText') === 'Aide sur la colonne Nom');
    expect(headerTooltips).toHaveLength(1);
  });

  it('should not emit clickHeader when clicking header helper anchor', async () => {
    const headersWithHelper = [
      { ...headers[0], helperText: 'Aide', sortable: true },
      ...headers.slice(1),
    ];
    const { wrapper } = factory({
      props: {
        headers: headersWithHelper,
        items,
        totalSize: 2,
        sort: { key: null, value: SortValue.default },
      },
    });
    const helper = wrapper.find('.header-content-helper');
    expect(helper.exists()).toBe(true);
    await helper.trigger('click');
    expect(wrapper.emitted('clickHeader')).toBeFalsy();
  });

  it('should set classes for header and content', () => {
    const { wrapper } = factory({
      props: {
        headers,
        items,
        disableSearch: true,
        totalSize: 2,
      },
    });
    const ths: any = wrapper.findAll('thead th');
    expect(ths[0].element.className).toContain('u-text-align-start');
    expect(ths[1].element.className).toContain('u-text-align-start');
    expect(ths[2].element.className).toContain('u-text-align-center');
    expect(ths[3].element.className).toContain('u-text-align-end');

    const tds: any = wrapper.findAll('tbody td');
    expect(tds[0].element.className).toContain('u-text-align-start');
    expect(tds[1].element.className).toContain('u-text-align-start');
    expect(tds[2].element.className).toContain('u-text-align-center');
    expect(tds[3].element.className).toContain('u-text-align-end');
  });

  it('should set classes for header', () => {
    const { wrapper } = factory({
      props: {
        headers,
        items,
        disableSearch: true,
        totalSize: 2,
      },
    });
    const ths: any = wrapper.findAll('thead th');
    expect(ths[0].element.className).toContain('customClass0');
  });

  it.each([{ caseItems: items }, { caseItems: items.concat([...items]) }])(
    'should have correct number of rows',
    ({ caseItems }) => {
      const { wrapper } = factory({
        props: {
          headers,
          items: caseItems,
          totalSize: 2,
        },
      });

      expect(wrapper.findAll('tbody tr').length).toEqual(caseItems.length);
    },
  );
  describe('select items', () => {
    it('should have checkbox available', () => {
      const { wrapper } = factory({
        props: {
          headers,
          items,
          totalSize: 2,
          selectable: true,
        },
      });
      expect(wrapper.findAllComponents(UiBmsInputCheckbox).length).toEqual(3);
    });
    it('should have a selected checkbox', () => {
      const { getAllCheckedCheckboxes, getTableCheckbox, getFirstRowCheckbox } =
        factory({
          props: {
            headers,
            items,
            totalSize: 2,
            selectable: true,
            selectedItems: [
              {
                name: 'Hello',
                title: 'Hello world',
                age: 4,
                nickname: 'Nick1',
              },
              {
                name: 'NotInArray',
                title: 'NotInArray',
                age: 0,
                nickname: 'NotInArray',
              },
            ],
          },
        });
      expect(getAllCheckedCheckboxes().length).toEqual(1);
      expect(getTableCheckbox().props('modelValue')).toEqual(false);
      expect(getFirstRowCheckbox().props('modelValue')).toEqual(true);
    });
    it('should have a selected checkbox and emit event when check', async () => {
      const {
        wrapper,
        getAllCheckedCheckboxes,
        getFirstRowCheckbox,
        getTableCheckbox,
      } = factory({
        props: {
          headers,
          items,
          totalSize: 2,
          selectable: true,
          selectedItems: [
            {
              name: 'NotInArray',
              title: 'NotInArray',
              age: 0,
              nickname: 'NotInArray',
            },
          ],
          'onUpdate:selectedItems': (e: any) =>
            wrapper.setProps({ selectedItems: e }),
        },
      });
      expect(getAllCheckedCheckboxes().length).toEqual(0);
      expect(getTableCheckbox().props('modelValue')).toEqual(false);
      expect(getFirstRowCheckbox().props('modelValue')).toEqual(false);
      await getFirstRowCheckbox().get('input').setValue(true);
      expect(getAllCheckedCheckboxes().length).toEqual(1);
      expect(getTableCheckbox().props('modelValue')).toEqual(false);
      expect(getFirstRowCheckbox().props('modelValue')).toEqual(true);
      expect(wrapper.props('selectedItems')).toEqual([
        {
          name: 'NotInArray',
          title: 'NotInArray',
          age: 0,
          nickname: 'NotInArray',
        },
        { name: 'Hello', title: 'Hello world', age: 4, nickname: 'Nick1' },
      ]);
    });
    it('should have no selected checkbox if uncheck', async () => {
      const {
        wrapper,
        getAllCheckedCheckboxes,
        getFirstRowCheckbox,
        getTableCheckbox,
      } = factory({
        props: {
          headers,
          items,
          totalSize: 2,
          selectable: true,
          selectedItems: [
            { name: 'Hello', title: 'Hello world', age: 4, nickname: 'Nick1' },
            {
              name: 'NotInArray',
              title: 'NotInArray',
              age: 0,
              nickname: 'NotInArray',
            },
          ],
          'onUpdate:selectedItems': (e: any) =>
            wrapper.setProps({ selectedItems: e }),
        },
      });
      expect(getAllCheckedCheckboxes().length).toEqual(1);
      expect(getTableCheckbox().props('modelValue')).toEqual(false);
      expect(getFirstRowCheckbox().props('modelValue')).toEqual(true);
      await getFirstRowCheckbox().get('input').setValue(false);
      expect(getAllCheckedCheckboxes().length).toEqual(0);
      expect(getTableCheckbox().props('modelValue')).toEqual(false);
      expect(getFirstRowCheckbox().props('modelValue')).toEqual(false);
      expect(wrapper.props('selectedItems')).toEqual([
        {
          name: 'NotInArray',
          title: 'NotInArray',
          age: 0,
          nickname: 'NotInArray',
        },
      ]);
    });
    it('should select all if major checkbox is checked', async () => {
      const {
        wrapper,
        getAllCheckedCheckboxes,
        getFirstRowCheckbox,
        getTableCheckbox,
      } = factory({
        props: {
          headers,
          items,
          totalSize: 2,
          selectable: true,
          selectedItems: [
            {
              name: 'NotInArray',
              title: 'NotInArray',
              age: 0,
              nickname: 'NotInArray',
            },
          ],
          'onUpdate:selectedItems': (e: any) =>
            wrapper.setProps({ selectedItems: e }),
        },
      });
      expect(getAllCheckedCheckboxes().length).toEqual(0);
      expect(getTableCheckbox().props('modelValue')).toEqual(false);
      expect(getFirstRowCheckbox().props('modelValue')).toEqual(false);
      await getTableCheckbox().get('input').setValue(true);
      expect(getAllCheckedCheckboxes().length).toEqual(3);
      expect(getTableCheckbox().props('modelValue')).toEqual(true);
      expect(getFirstRowCheckbox().props('modelValue')).toEqual(true);
      expect(wrapper.props('selectedItems')).toEqual([
        {
          name: 'NotInArray',
          title: 'NotInArray',
          age: 0,
          nickname: 'NotInArray',
        },
        ...items,
      ]);
    });
    it('should unselect all if major checkbox is unchecked', async () => {
      const {
        wrapper,
        getAllCheckedCheckboxes,
        getFirstRowCheckbox,
        getTableCheckbox,
      } = factory({
        props: {
          headers,
          items,
          totalSize: 2,
          selectable: true,
          selectedItems: [],
          'onUpdate:selectedItems': (e: any) =>
            wrapper.setProps({ selectedItems: e }),
        },
      });
      await getTableCheckbox().get('input').setValue(true);
      expect(getAllCheckedCheckboxes().length).toEqual(3);
      expect(getTableCheckbox().props('modelValue')).toEqual(true);
      expect(getFirstRowCheckbox().props('modelValue')).toEqual(true);
      await getTableCheckbox().get('input').setValue(false);
      expect(getAllCheckedCheckboxes().length).toEqual(0);
      expect(getTableCheckbox().props('modelValue')).toEqual(false);
      expect(getFirstRowCheckbox().props('modelValue')).toEqual(false);
      expect(wrapper.props('selectedItems')).toHaveLength(0);
    });
    it('should allow to select all elements if more elements are available', async () => {
      const {
        wrapper,
        getAllCheckedCheckboxes,
        getFirstRowCheckbox,
        getTableCheckbox,
        selectedMessage,
      } = factory({
        props: {
          headers,
          items,
          totalSize: 2,
          selectable: true,
          selectedItems: [
            {
              name: 'Hello',
              title: 'Hello world',
              age: 4,
              nickname: 'Nick1',
            },
          ],
        },
      });
      expect(getAllCheckedCheckboxes().length).toEqual(1);
      expect(getTableCheckbox().props('modelValue')).toEqual(false);
      expect(getFirstRowCheckbox().props('modelValue')).toEqual(true);
      expect(selectedMessage().text()).toEqual(
        '1 élément du tableau est sélectionné.  Sélectionner la totalité des 2 éléments',
      );
      await selectedMessage().find('.select-mode-all').trigger('click');
      expect(wrapper.emitted()['selectAll']).toStrictEqual([[]]);
    });
    it('should disabled individual checkboxes if in selectMode ALL', async () => {
      const {
        wrapper,
        getAllCheckedCheckboxes,
        getFirstRowCheckbox,
        getTableCheckbox,
        selectedMessage,
      } = factory({
        props: {
          headers,
          items,
          totalSize: 2,
          selectable: true,
          selectMode: SelectMode.ALL,
          selectedItems: [
            {
              name: 'Hello',
              title: 'Hello world',
              age: 4,
              nickname: 'Nick1',
            },
          ],
        },
      });
      expect(getAllCheckedCheckboxes().length).toEqual(3);
      expect(getTableCheckbox().props('modelValue')).toEqual(true);
      expect(getFirstRowCheckbox().props('modelValue')).toEqual(true);
      expect(getFirstRowCheckbox().classes()).toContain('disabled');
      expect(selectedMessage().text()).toEqual(
        'Vous avez sélectionné la totalité des 2 éléments.',
      );
    });
    it('should display readio if in selectMode SINGLE', async () => {
      const { getAllRadio, getAllCheckboxes } = factory({
        props: {
          headers,
          items,
          totalSize: 2,
          selectable: true,
          selectMode: SelectMode.SINGLE,
          selectedItems: [items[0]],
        },
      });

      expect(getAllRadio().length).toEqual(2);
      expect(getAllCheckboxes().length).toEqual(0);
    });
    it('should unselect previous selection if in selectMode SINGLE', async () => {
      const { getAllRadio } = factory({
        props: {
          headers,
          items,
          totalSize: 2,
          selectable: true,
          selectMode: SelectMode.SINGLE,
          selectedItems: [items[0]],
        },
      });
      const firstRadio = getAllRadio()[0];
      const secondRadio = getAllRadio()[1];

      expect(firstRadio.props('modelValue')).toBeTruthy();
      expect(secondRadio.props('modelValue')).toBeFalsy();

      await secondRadio.get('input').setValue(items[1]);

      expect(secondRadio.props('modelValue')).toBeTruthy();
      expect(firstRadio.props('modelValue')).toBeFalsy();
    });
  });
});
