import { suite, test, beforeEach, expect } from 'vitest';
import { matchSorter } from 'match-sorter';

import { Combobox, Option, State } from './combobox';

suite('Combobox', () => {
  const options: Option[] =
    'Fraises,Myrtilles,Framboises,Mûres,Canneberges,Groseilles,Baies de sureau,Mûres blanches,Baies de genièvre,Baies d’açaï'
      .split(',')
      .map((label) => ({ label, value: label }));

  let combobox: Combobox;
  let currentState: State;

  suite('single select without custom value', () => {
    suite('with default selection', () => {
      beforeEach(() => {
        combobox = new Combobox({
          options,
          selected: options.at(0) ?? null,
          render: (state) => {
            currentState = state;
          }
        });
        combobox.init();
      });

      test('open select box and select option with click', () => {
        expect(currentState.open).toBeFalsy();
        expect(currentState.loading).toBe(null);
        expect(currentState.selection?.label).toBe('Fraises');

        combobox.open();
        expect(currentState.open).toBeTruthy();

        combobox.select('Mûres');
        expect(currentState.selection?.label).toBe('Mûres');
        expect(currentState.open).toBeFalsy();
      });

      test('open select box and select option with enter', () => {
        expect(currentState.open).toBeFalsy();
        expect(currentState.selection?.label).toBe('Fraises');

        combobox.keyboard('ArrowDown');
        expect(currentState.open).toBeTruthy();
        expect(currentState.selection?.label).toBe('Fraises');
        expect(currentState.focused?.label).toBe('Fraises');

        combobox.keyboard('ArrowDown');
        expect(currentState.selection?.label).toBe('Fraises');
        expect(currentState.focused?.label).toBe('Myrtilles');

        combobox.keyboard('Enter');
        expect(currentState.selection?.label).toBe('Myrtilles');
        expect(currentState.open).toBeFalsy();

        combobox.keyboard('Enter');
        expect(currentState.selection?.label).toBe('Myrtilles');
        expect(currentState.open).toBeFalsy();
      });

      test('open select box and select option with tab', () => {
        combobox.keyboard('ArrowDown');
        combobox.keyboard('ArrowDown');

        combobox.keyboard('Tab');
        expect(currentState.selection?.label).toBe('Myrtilles');
        expect(currentState.open).toBeFalsy();
        expect(currentState.hint).toEqual({
          type: 'selected',
          label: 'Myrtilles'
        });
      });

      test('do not open select box on focus', () => {
        combobox.focus();
        expect(currentState.open).toBeFalsy();
      });
    });

    suite('empty', () => {
      beforeEach(() => {
        combobox = new Combobox({
          options,
          selected: null,
          render: (state) => {
            currentState = state;
          }
        });
        combobox.init();
      });

      test('open select box on focus', () => {
        combobox.focus();
        expect(currentState.open).toBeTruthy();
      });

      suite('open', () => {
        beforeEach(() => {
          combobox.open();
        });

        test('if tab on empty input nothing is selected', () => {
          expect(currentState.open).toBeTruthy();
          expect(currentState.selection).toBeNull();
          combobox.keyboard('Tab');

          expect(currentState.open).toBeFalsy();
          expect(currentState.selection).toBeNull();
        });

        test('if enter on empty input nothing is selected', () => {
          expect(currentState.open).toBeTruthy();
          expect(currentState.selection).toBeNull();

          combobox.keyboard('Enter');
          expect(currentState.open).toBeFalsy();
          expect(currentState.selection).toBeNull();
        });
      });

      suite('closed', () => {
        test('if tab on empty input nothing is selected', () => {
          expect(currentState.open).toBeFalsy();
          expect(currentState.selection).toBeNull();

          combobox.keyboard('Tab');
          expect(currentState.open).toBeFalsy();
          expect(currentState.selection).toBeNull();
        });

        test('if enter on empty input nothing is selected', () => {
          expect(currentState.open).toBeFalsy();
          expect(currentState.selection).toBeNull();

          combobox.keyboard('Enter');
          expect(currentState.open).toBeFalsy();
          expect(currentState.selection).toBeNull();
        });
      });

      test('type exact match and press enter', () => {
        combobox.input('Baies');
        expect(currentState.open).toBeTruthy();
        expect(currentState.selection).toBeNull();
        expect(currentState.options.length).toEqual(3);

        combobox.keyboard('Enter');
        expect(currentState.open).toBeFalsy();
        expect(currentState.selection?.label).toBe('Baies d’açaï');
      });

      test('type exact match and press tab', () => {
        combobox.input('Baies');
        expect(currentState.open).toBeTruthy();
        expect(currentState.selection).toBeNull();

        combobox.keyboard('Tab');
        expect(currentState.open).toBeFalsy();
        expect(currentState.selection?.label).toBe('Baies d’açaï');
        expect(currentState.inputValue).toEqual('Baies d’açaï');
      });

      test('type non matching input and press enter', () => {
        combobox.input('toto');
        expect(currentState.open).toBeFalsy();
        expect(currentState.selection).toBeNull();

        combobox.keyboard('Enter');
        expect(currentState.open).toBeFalsy();
        expect(currentState.selection).toBeNull();
        expect(currentState.inputValue).toEqual('');
      });

      test('type non matching input and press tab', () => {
        combobox.input('toto');
        expect(currentState.open).toBeFalsy();
        expect(currentState.selection).toBeNull();

        combobox.keyboard('Tab');
        expect(currentState.open).toBeFalsy();
        expect(currentState.selection).toBeNull();
        expect(currentState.inputValue).toEqual('');
      });

      test('type non matching input and close', () => {
        combobox.input('toto');
        expect(currentState.open).toBeFalsy();
        expect(currentState.selection).toBeNull();

        combobox.close();
        expect(currentState.open).toBeFalsy();
        expect(currentState.selection).toBeNull();
        expect(currentState.inputValue).toEqual('');
      });

      test('focus should circle', () => {
        combobox.input('Baie');
        expect(currentState.open).toBeTruthy();
        expect(currentState.options.map(({ label }) => label)).toEqual([
          'Baies d’açaï',
          'Baies de genièvre',
          'Baies de sureau'
        ]);
        expect(currentState.focused).toBeNull();
        combobox.keyboard('ArrowDown');
        expect(currentState.focused?.label).toBe('Baies d’açaï');
        combobox.keyboard('ArrowDown');
        expect(currentState.focused?.label).toBe('Baies de genièvre');
        combobox.keyboard('ArrowDown');
        expect(currentState.focused?.label).toBe('Baies de sureau');
        combobox.keyboard('ArrowDown');
        expect(currentState.focused?.label).toBe('Baies d’açaï');
        combobox.keyboard('ArrowUp');
        expect(currentState.focused?.label).toBe('Baies de sureau');
      });
    });
  });

  suite('single select with custom value', () => {
    beforeEach(() => {
      combobox = new Combobox({
        options,
        selected: null,
        allowsCustomValue: true,
        render: (state) => {
          currentState = state;
        }
      });
      combobox.init();
    });

    test('type non matching input and press enter', () => {
      combobox.input('toto');
      expect(currentState.open).toBeFalsy();
      expect(currentState.selection).toBeNull();

      combobox.keyboard('Enter');
      expect(currentState.open).toBeFalsy();
      expect(currentState.selection).toBeNull();
      expect(currentState.inputValue).toEqual('toto');
    });

    test('type non matching input and press tab', () => {
      combobox.input('toto');
      expect(currentState.open).toBeFalsy();
      expect(currentState.selection).toBeNull();

      combobox.keyboard('Tab');
      expect(currentState.open).toBeFalsy();
      expect(currentState.selection).toBeNull();
      expect(currentState.inputValue).toEqual('toto');
    });

    test('type non matching input and close', () => {
      combobox.input('toto');
      expect(currentState.open).toBeFalsy();
      expect(currentState.selection).toBeNull();

      combobox.close();
      expect(currentState.open).toBeFalsy();
      expect(currentState.selection).toBeNull();
      expect(currentState.inputValue).toEqual('toto');
    });
  });

  suite('single select with fetcher', () => {
    beforeEach(() => {
      combobox = new Combobox({
        options: (term: string) =>
          Promise.resolve(matchSorter(options, term, { keys: ['value'] })),
        selected: null,
        render: (state) => {
          currentState = state;
        }
      });
      combobox.init();
    });

    test('type and get options from fetcher', async () => {
      expect(currentState.open).toBeFalsy();
      expect(currentState.loading).toBe(false);

      const result = combobox.input('Baies');

      expect(currentState.loading).toBe(true);
      await result;
      expect(currentState.loading).toBe(false);
      expect(currentState.open).toBeTruthy();
      expect(currentState.selection).toBeNull();
      expect(currentState.options.length).toEqual(3);
    });
  });
});