import {
  checkAtBottom,
  getItemOffsetTop,
  mapOptions,
  optionPredicate,
  filterGroupedOptions,
  getAccumulatedIndex,
} from '../utils';

describe('checkAtBottom', () => {
  it('returns undefined when element is null', () => {
    expect(checkAtBottom(null)).toBe(undefined);
  });

  it('returns false when element is not at its bottom', () => {
    const element = document.createElement('div');
    jest.spyOn(element, 'scrollHeight', 'get').mockImplementation(() => 500);
    jest.spyOn(element, 'scrollTop', 'get').mockImplementation(() => 100);
    jest.spyOn(element, 'clientHeight', 'get').mockImplementation(() => 250);

    expect(checkAtBottom(element)).toBe(false);
  });

  it('returns true when element is at its bottom', () => {
    const element = document.createElement('div');
    jest.spyOn(element, 'scrollHeight', 'get').mockImplementation(() => 500);
    jest.spyOn(element, 'scrollTop', 'get').mockImplementation(() => 250);
    jest.spyOn(element, 'clientHeight', 'get').mockImplementation(() => 250);

    expect(checkAtBottom(element)).toBe(true);
  });
});

describe('getItemOffsetTop', () => {
  it('returns undefined when element is null', () => {
    expect(getItemOffsetTop(null)).toBe(undefined);
  });

  it('returns offsetTop when element is not null', () => {
    const item = document.createElement('li');
    jest.spyOn(item, 'offsetTop', 'get').mockImplementation(() => 300);

    expect(getItemOffsetTop(item)).toBe(300);
  });
});

describe('mapOptions', () => {
  it('returns options with empty category', () => {
    const options = [
      { value: 1, text: 'Item 1' },
      { value: 2, text: 'Item 2' },
    ];

    expect(mapOptions(options)).toEqual([{ category: '', options }]);
  });

  it('returns original options when they are already grouped', () => {
    const options = [
      {
        category: 'Category 1',
        options: [
          { value: 1, text: 'Item 1' },
          { value: 2, text: 'Item 2' },
        ],
      },
      {
        category: 'Category 2',
        options: [
          { value: 3, text: 'Item 3' },
          { value: 4, text: 'Item 4' },
        ],
      },
    ];

    expect(mapOptions(options)).toEqual(options);
  });
});

describe('optionPredicate', () => {
  describe('query is not defined', () => {
    it('returns true', () => {
      const option = { value: 1, text: 'Item' };

      expect(optionPredicate(undefined)(option)).toBe(true);
    });
  });

  describe('query is defined', () => {
    const predicate = optionPredicate('Matched Item');

    it('returns false when option does not contain the query', () => {
      const option = {
        value: 1,
        text: 'This is a random item',
        helpText: 'Random help text',
      };

      expect(predicate(option)).toBe(false);
    });

    it('returns true when option does contain the query', () => {
      const option1 = { value: 1, text: 'This is a matched item' };
      const option2 = {
        value: 2,
        text: 'This is a random item',
        helpText: 'Matched item help text',
      };

      expect(predicate(option1)).toBe(true);
      expect(predicate(option2)).toBe(true);
    });
  });
});

describe('filterGroupedOptions', () => {
  const predicate = optionPredicate('matched');

  it('returns matched options', () => {
    const options = [
      {
        category: 'Category 1',
        options: [
          { value: 1, text: 'Matched Item 1' },
          { value: 2, text: 'Item 2' },
        ],
      },
      {
        category: 'Category 2',
        options: [
          { value: 3, text: 'Matched Item 3' },
          { value: 4, text: 'Item 4' },
        ],
      },
      {
        category: 'Category 3',
        options: [
          { value: 5, text: 'Item 5' },
          { value: 6, text: 'Item 6' },
        ],
      },
    ];
    const expectedOptions = [
      {
        category: 'Category 1',
        options: [{ value: 1, text: 'Matched Item 1' }],
      },
      {
        category: 'Category 2',
        options: [{ value: 3, text: 'Matched Item 3' }],
      },
    ];

    expect(filterGroupedOptions(predicate, options)).toEqual(expectedOptions);
  });

  it('returns no options', () => {
    const options = [
      {
        category: 'Category 1',
        options: [
          { value: 1, text: 'Item 1' },
          { value: 2, text: 'Item 2' },
        ],
      },
      {
        category: 'Category 2',
        options: [
          { value: 3, text: 'Item 3' },
          { value: 4, text: 'Item 4' },
        ],
      },
      {
        category: 'Category 3',
        options: [
          { value: 5, text: 'Item 5' },
          { value: 6, text: 'Item 6' },
        ],
      },
    ];

    expect(filterGroupedOptions(predicate, options)).toEqual([]);
  });
});

describe('getAccumulatedIndex', () => {
  const options = [
    {
      category: 'Category 1',
      options: [
        { value: 1, text: 'Item 1' },
        { value: 2, text: 'Item 2' },
      ],
    },
    {
      category: 'Category 2',
      options: [{ value: 3, text: 'Item 3' }],
    },
    {
      category: 'Category 3',
      options: [
        { value: 4, text: 'Item 4' },
        { value: 5, text: 'Item 5' },
      ],
    },
    {
      category: 'Category 4',
      options: [{ value: 6, text: 'Item 6' }],
    },
  ];

  it.each`
    catIndex | expected
    ${0}     | ${0}
    ${1}     | ${2}
    ${2}     | ${3}
    ${3}     | ${5}
  `(
    'returns $expected when category index is $catIndex',
    ({ catIndex, expected }) => {
      expect(getAccumulatedIndex(options, catIndex)).toBe(expected);
    }
  );
});
