import { useEffect, useRef, useReducer, MouseEvent } from 'react';
import {
  stateReducer,
  State,
  SelectionMode,
  HoverState,
  Selection,
  EmptySelectionAction,
} from './HudCanvasState';
import constrainNumeric from './helpers/constrainNumeric';
export { EmptySelectionAction } from './HudCanvasState';

interface Styles {
  borderLineWidth: number;
  borderStrokeStyle: string;
  positionLineWidth: number;
  positionStrokeStyle: string;
  hoverPositionStrokeStyle: string;
}

interface Props {
  width: number;
  height: number;
  emptySelectionAction: EmptySelectionAction;
  styles: Styles;
  position: number | null;
  selection: Selection;
  onSelectionChange: (selectionState: SelectionChangeEvent) => void;
}

export interface SelectionChangeEvent {
  selection: Selection;
  mode: SelectionMode;
  prevMode: SelectionMode;
}

const emptySelection: Selection = { start: 0, end: 0 };

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

const getCanvasX = (evt: MouseEvent<HTMLCanvasElement>): number => {
  const rect = evt.currentTarget.getBoundingClientRect();
  const x = Math.round(
    ((evt.clientX - rect.left) / rect.width) * evt.currentTarget.width
  );
  return constrainNumeric(x, evt.currentTarget.width);
};

export const HudCanvas: React.FC<Props> = ({
  width,
  height,
  emptySelectionAction,
  styles: {
    borderLineWidth,
    borderStrokeStyle,
    positionLineWidth,
    positionStrokeStyle,
    hoverPositionStrokeStyle,
  },
  position,
  selection: initialSelection,
  onSelectionChange,
}: Props) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  const [state, dispatch] = useReducer(stateReducer, {
    ...initialState,
    width,
    emptySelectionAction,
    selection: initialSelection,
  });

  // side effects

  useEffect(() => {
    dispatch({ selection: initialSelection, x: 0, type: 'setselection' });
  }, [initialSelection]);

  // handle global mouse up
  useEffect(() => {
    window.addEventListener('mouseup', handleMouseUp);
    return () => {
      window.removeEventListener('mouseup', handleMouseUp);
    };
  }, [state]);

  // trigger onSelectionChange callback.
  useEffect(() => {
    // really we only care if hoverX hasn't changed, not checking this will
    // result in a flood of events being published.
    if (!state.shouldPublish) {
      return;
    }
    onSelectionChange({
      selection: state.selection,
      mode: state.mode,
      prevMode: state.prevMode,
    });
  }, [state]);

  // draw the overview HUD
  useEffect(() => {
    requestAnimationFrame(() => {
      const canvas = canvasRef.current;
      if (canvas == null) {
        console.error('no hud canvas ref available');
        return;
      }
      const ctx = canvas.getContext('2d');
      if (ctx == null) {
        console.error('no hud 2d context available');
        return;
      }
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // draw selection

      const currentSelection = state.selection;

      ctx.beginPath();
      ctx.strokeStyle = borderStrokeStyle;
      ctx.lineWidth = borderLineWidth;
      const alpha =
        state.hoverState == HoverState.OverSelection ? '0.15' : '0.13';
      ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;
      ctx.rect(
        currentSelection.start,
        borderLineWidth,
        currentSelection.end - currentSelection.start,
        canvas.height - borderLineWidth * 2
      );
      ctx.fill();
      ctx.stroke();

      // draw hover position

      const hoverX = state.hoverX;
      if (
        hoverX != null &&
        (hoverX < currentSelection.start || hoverX > currentSelection.end)
      ) {
        ctx.beginPath();
        ctx.strokeStyle = hoverPositionStrokeStyle;
        ctx.lineWidth = 2;
        ctx.moveTo(hoverX, 0);
        ctx.lineTo(hoverX, canvas.height);
        ctx.stroke();
      }

      // draw position marker

      if (position == null) {
        return;
      }

      ctx.beginPath();
      ctx.strokeStyle = positionStrokeStyle;
      ctx.lineWidth = positionLineWidth;
      ctx.moveTo(position, 0);
      ctx.lineWidth = position == 0 ? 8 : 4;
      ctx.lineTo(position, canvas.height);
      ctx.stroke();
    });
  }, [state, position]);

  const handleMouseDown = (evt: MouseEvent<HTMLCanvasElement>) => {
    if (state.mode != SelectionMode.Normal) {
      return;
    }
    dispatch({ x: getCanvasX(evt), type: 'mousedown' });
  };

  const handleMouseMove = (evt: MouseEvent<HTMLCanvasElement>) => {
    dispatch({ x: getCanvasX(evt), type: 'mousemove' });
  };

  const handleMouseUp = () => {
    if (state.mode == SelectionMode.Normal) {
      return;
    }
    dispatch({ x: state.hoverX, type: 'mouseup' });
  };

  const handleMouseLeave = () => {
    dispatch({ x: state.hoverX, type: 'mouseleave' });
  };

  return (
    <>
      <canvas
        ref={canvasRef}
        className={`block absolute w-full h-full ${state.cursorClass} z-20`}
        width={width}
        height={height}
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onMouseLeave={handleMouseLeave}
      ></canvas>
    </>
  );
};
