import {
  stateReducer,
  SelectionMode,
  EmptySelectionAction,
  HoverState,
} from './HudCanvasState';

const initialState = {
  width: 5000,
  emptySelectionAction: EmptySelectionAction.SelectNothing,
  hoverX: 0,
  selection: { start: 0, end: 0 },
  origSelection: { start: 0, end: 0 },
  mousedownX: 0,
  mode: SelectionMode.Normal,
  prevMode: SelectionMode.Normal,
  cursorClass: 'cursor-auto',
  hoverState: HoverState.Normal,
  shouldPublish: false,
};

describe('stateReducer', () => {
  describe('setselection', () => {
    it('sets the selection', () => {
      const state = stateReducer(
        { ...initialState },
        { type: 'setselection', x: 0, selection: { start: 100, end: 200 } }
      );
      expect(state.selection).toEqual({ start: 100, end: 200 });
      expect(state.shouldPublish).toBeFalsy();
    });
  });

  describe.each([
    {
      name: 'entering resizing start',
      x: 995,
      selection: { start: 1000, end: 2000 },
      wantMode: SelectionMode.ResizingStart,
      wantSelection: { start: 1000, end: 2000 },
    },
    {
      name: 'entering resizing end',
      x: 2003,
      selection: { start: 1000, end: 2000 },
      wantMode: SelectionMode.ResizingEnd,
      wantSelection: { start: 1000, end: 2000 },
    },
    {
      name: 'entering dragging',
      x: 1500,
      selection: { start: 1000, end: 2000 },
      wantMode: SelectionMode.Dragging,
      wantSelection: { start: 1000, end: 2000 },
    },
    {
      name: 'entering selecting',
      x: 10,
      selection: { start: 1000, end: 2000 },
      wantMode: SelectionMode.Selecting,
      wantSelection: { start: 10, end: 10 },
    },
  ])('mousedown', ({ name, x, selection, wantMode, wantSelection }) => {
    test(`${name} generates the expected state`, () => {
      const state = stateReducer(
        { ...initialState, selection: selection },
        { type: 'mousedown', x: x }
      );
      expect(state.mode).toEqual(wantMode);
      expect(state.selection).toEqual(wantSelection);
      expect(state.mousedownX).toEqual(x);
      expect(state.shouldPublish).toBeTruthy();
    });
  });

  describe.each([
    {
      name: 're-entering normal mode',
      x: 1200,
      selection: { start: 1000, end: 2000 },
      emptySelectionAction: EmptySelectionAction.SelectNothing,
      wantSelection: { start: 1000, end: 2000 },
    },
    {
      name: 'when nothing is selected and emptySelectionAction is SelectNothing',
      x: 1200,
      selection: { start: 1000, end: 1000 },
      emptySelectionAction: EmptySelectionAction.SelectNothing,
      wantSelection: { start: 1200, end: 1200 },
    },
    {
      // TODO: broken
      name: 'when nothing is selected and emptySelectionAction is SelectPrevious',
      x: 1200,
      selection: { start: 1000, end: 2000 },
      emptySelectionAction: EmptySelectionAction.SelectPrevious,
      wantSelection: { start: 1000, end: 2000 },
    },
  ])(
    'mouseup',
    ({ name, x, selection, emptySelectionAction, wantSelection }) => {
      test(`${name} generates the expected state`, () => {
        const state = stateReducer(
          {
            ...initialState,
            selection: selection,
            mode: SelectionMode.Selecting,
            emptySelectionAction: emptySelectionAction,
          },
          { type: 'mouseup', x: x }
        );
        expect(state.mode).toEqual(SelectionMode.Normal);
        expect(state.prevMode).toEqual(SelectionMode.Selecting);
        expect(state.selection).toEqual(wantSelection);
        expect(state.shouldPublish).toBeTruthy();
      });
    }
  );

  describe('mouseleave', () => {
    it('sets the state', () => {
      const state = stateReducer(
        {
          ...initialState,
          selection: { start: 2000, end: 3000 },
          mode: SelectionMode.Dragging,
          mousedownX: 475,
        },
        { type: 'mouseleave', x: 500 }
      );
      expect(state.mode).toEqual(SelectionMode.Dragging);
      expect(state.selection).toEqual({ start: 2000, end: 3000 });
      expect(state.mousedownX).toEqual(475);
      expect(state.shouldPublish).toBeFalsy();
    });
  });

  describe('mousemove', () => {
    describe.each([
      // Normal mode
      {
        name: 'hovering over selection start',
        mode: SelectionMode.Normal,
        x: 997,
        selection: { start: 1000, end: 3000 },
        wantHoverState: HoverState.OverSelectionStart,
        shouldPublish: false,
      },
      {
        name: 'hovering over selection end',
        mode: SelectionMode.Normal,
        x: 3009,
        selection: { start: 1000, end: 3000 },
        wantHoverState: HoverState.OverSelectionEnd,
        shouldPublish: false,
      },
      {
        name: 'hovering over selection',
        mode: SelectionMode.Normal,
        x: 1200,
        selection: { start: 1000, end: 3000 },
        wantHoverState: HoverState.OverSelection,
        shouldPublish: false,
      },
      {
        name: 'hovering elsewhere',
        mode: SelectionMode.Normal,
        x: 300,
        selection: { start: 1000, end: 3000 },
        wantHoverState: HoverState.Normal,
        shouldPublish: false,
      },
      // Selecting mode
      {
        name: 'when not crossing over',
        mode: SelectionMode.Selecting,
        x: 3005,
        mousedownX: 2000,
        selection: { start: 2000, end: 3000 },
        wantSelection: { start: 2000, end: 3005 },
        shouldPublish: true,
      },
      {
        name: 'when crossing over',
        mode: SelectionMode.Selecting,
        x: 1995,
        mousedownX: 2000,
        selection: { start: 2000, end: 2002 },
        wantSelection: { start: 1995, end: 2000 },
        shouldPublish: true,
      },
      // Dragging mode
      {
        name: 'in the middle of the canvas',
        mode: SelectionMode.Dragging,
        x: 1220,
        mousedownX: 1200,
        selection: { start: 1000, end: 1500 },
        origSelection: { start: 1000, end: 1500 },
        wantSelection: { start: 1020, end: 1520 },
        shouldPublish: true,
      },
      {
        name: 'at the start of the canvas',
        mode: SelectionMode.Dragging,
        x: 30,
        mousedownX: 50,
        selection: { start: 10, end: 210 },
        origSelection: { start: 10, end: 210 },
        wantSelection: { start: 0, end: 200 },
        shouldPublish: true,
      },
      {
        name: 'at the end of the canvas',
        mode: SelectionMode.Dragging,
        x: 1400,
        mousedownX: 1250,
        selection: { start: 4800, end: 4900 },
        origSelection: { start: 4800, end: 4900 },
        wantSelection: { start: 4900, end: 5000 },
        shouldPublish: true,
      },
      // ResizingStart mode
      {
        name: 'when not crossing over',
        mode: SelectionMode.ResizingStart,
        x: 2020,
        selection: { start: 2000, end: 3000 },
        origSelection: { start: 2000, end: 3000 },
        wantSelection: { start: 2020, end: 3000 },
        shouldPublish: true,
      },
      {
        name: 'when crossing over',
        mode: SelectionMode.ResizingStart,
        x: 2010,
        selection: { start: 2000, end: 2002 },
        origSelection: { start: 2000, end: 2002 },
        wantSelection: { start: 2002, end: 2010 },
        shouldPublish: true,
      },
      // ResizingEnd mode
      {
        name: 'when not crossing over',
        mode: SelectionMode.ResizingEnd,
        x: 2007,
        selection: { start: 1000, end: 2000 },
        origSelection: { start: 1000, end: 2000 },
        wantSelection: { start: 1000, end: 2007 },
        shouldPublish: true,
      },
      {
        name: 'when crossing over',
        mode: SelectionMode.ResizingEnd,
        x: 1995,
        selection: { start: 2000, end: 2002 },
        origSelection: { start: 2000, end: 2002 },
        wantSelection: { start: 1995, end: 2000 },
        shouldPublish: true,
      },
    ])(
      'mousemove',
      ({
        name,
        mode,
        x,
        mousedownX = 0,
        selection,
        origSelection = { start: 0, end: 0 },
        wantSelection = selection,
        wantHoverState = HoverState.Normal,
        shouldPublish,
      }) => {
        test(`${SelectionMode[mode]} mode: ${name} generates the expected state`, () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: selection,
              origSelection: origSelection,
              mode: mode,
              mousedownX: mousedownX,
            },
            { type: 'mousemove', x: x }
          );
          expect(state.mode).toEqual(mode);
          expect(state.selection).toEqual(wantSelection);
          expect(state.hoverState).toEqual(wantHoverState);
          expect(state.shouldPublish).toEqual(shouldPublish);
        });
      }
    );
  });
});
