import React, { useRef, useEffect, useState } from "react";
import { Box } from "theme-ui";
import ReactResizeDetector from "react-resize-detector";
import { Stage, Layer, Image, Group } from "react-konva";
import Konva from "konva";

import useMapImage from "../../hooks/useMapImage";
import usePreventOverscroll from "../../hooks/usePreventOverscroll";
import useStageInteraction from "../../hooks/useStageInteraction";
import useImageCenter from "../../hooks/useImageCenter";
import usePreventContextMenu from "../../hooks/usePreventContextMenu";

import { getGridMaxZoom } from "../../helpers/grid";
import KonvaBridge from "../../helpers/KonvaBridge";

import {
  MapInteractionEmitter,
  MapInteractionProvider,
} from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext";
import { GridProvider } from "../../contexts/GridContext";
import { useKeyboard } from "../../contexts/KeyboardContext";

import shortcuts from "../../shortcuts";
import { Map, MapToolId } from "../../types/Map";
import { MapState } from "../../types/MapState";

type SelectedToolChangeEventHanlder = (tool: MapToolId) => void;

type MapInteractionProps = {
  map: Map | null;
  mapState: MapState | null;
  children?: React.ReactNode;
  controls: React.ReactNode;
  selectedToolId: MapToolId;
  onSelectedToolChange: SelectedToolChangeEventHanlder;
};

function MapInteraction({
  map,
  mapState,
  children,
  controls,
  selectedToolId,
  onSelectedToolChange,
}: MapInteractionProps) {
  const [mapImage, mapImageStatus] = useMapImage(map);

  const [mapLoaded, setMapLoaded] = useState(false);
  useEffect(() => {
    if (!map || !mapState || mapState.mapId !== map.id) {
      setMapLoaded(false);
    } else if (mapImageStatus === "loaded") {
      setMapLoaded(true);
    }
  }, [mapImageStatus, map, mapState]);

  const [stageWidth, setStageWidth] = useState(1);
  const [stageHeight, setStageHeight] = useState(1);
  const [stageScale, setStageScale] = useState(1);
  const [preventMapInteraction, setPreventMapInteraction] = useState(false);

  // Avoid state udpates when panning the map by using a ref and updating the konva element directly
  const stageTranslateRef = useRef({ x: 0, y: 0 });
  const mapStageRef = useMapStage();
  const mapLayerRef = useRef<Konva.Layer>(null);
  const mapImageRef = useRef<Konva.Image>(null);

  function handleResize(width?: number, height?: number) {
    if (width && height && width > 0 && height > 0) {
      setStageWidth(width);
      setStageHeight(height);
    }
  }

  const containerRef = useRef<HTMLDivElement>(null);
  usePreventOverscroll(containerRef);
  usePreventContextMenu(containerRef);

  const [mapWidth, mapHeight] = useImageCenter(
    map,
    mapStageRef,
    stageWidth,
    stageHeight,
    stageTranslateRef,
    setStageScale,
    mapLayerRef,
    containerRef
  );

  const previousSelectedToolRef = useRef(selectedToolId);
  const [currentMouseButtons, setCurentMouseButtons] = useState(0);

  const [interactionEmitter] = useState(new MapInteractionEmitter());

  useStageInteraction(
    mapStageRef,
    stageScale,
    setStageScale,
    stageTranslateRef,
    mapLayerRef,
    getGridMaxZoom(map?.grid),
    selectedToolId,
    preventMapInteraction,
    {
      onPinchStart: () => {
        // Change to move tool when pinching and zooming
        previousSelectedToolRef.current = selectedToolId;
        onSelectedToolChange("move");
      },
      onPinchEnd: () => {
        onSelectedToolChange(previousSelectedToolRef.current);
      },
      onDrag: (props) => {
        const { first, last, buttons } = props;
        if (buttons !== currentMouseButtons) {
          setCurentMouseButtons(buttons);
        }
        if (first) {
          interactionEmitter.emit("dragStart", props);
        } else if (last) {
          interactionEmitter.emit("dragEnd", props);
        } else {
          interactionEmitter.emit("drag", props);
        }
      },
    }
  );

  function handleKeyDown(event: KeyboardEvent) {
    // Change to move tool when pressing space
    if (shortcuts.move(event) && selectedToolId === "move") {
      // Stop active state on move icon from being selected
      event.preventDefault();
    }
    if (map && shortcuts.move(event) && selectedToolId !== "move") {
      event.preventDefault();
      previousSelectedToolRef.current = selectedToolId;
      onSelectedToolChange("move");
    }
    if (!event.repeat && shortcuts.move(event) && selectedToolId === "move") {
      previousSelectedToolRef.current = "move";
    }
  }

  function handleKeyUp(event: KeyboardEvent) {
    if (shortcuts.move(event) && selectedToolId === "move") {
      onSelectedToolChange(previousSelectedToolRef.current);
    }
  }

  useKeyboard(handleKeyDown, handleKeyUp);

  function getCursorForTool(tool: MapToolId) {
    if (currentMouseButtons === 2) {
      return "crosshair";
    } else if (currentMouseButtons > 2) {
      return "move";
    }
    switch (tool) {
      case "move":
        return "move";
      case "fog":
      case "drawing":
        return "crosshair";
      case "measure":
      case "pointer":
      case "note":
      case "select":
        return "crosshair";
      default:
        return "default";
    }
  }

  const mapInteraction = {
    stageScale,
    stageWidth,
    stageHeight,
    setPreventMapInteraction,
    mapWidth,
    mapHeight,
    interactionEmitter,
  };

  return (
    <MapInteractionProvider value={mapInteraction}>
      <GridProvider
        grid={map?.grid || null}
        width={mapWidth}
        height={mapHeight}
      >
        <Box
          sx={{
            position: "relative",
            cursor: getCursorForTool(selectedToolId),
            touchAction: "none",
            outline: "none",
            width: "100%",
            height: "100%",
          }}
          ref={containerRef}
          className="map"
        >
          <ReactResizeDetector
            handleWidth
            handleHeight
            onResize={handleResize}
            targetRef={containerRef}
          >
            <KonvaBridge
              stageRender={(children) => (
                <Stage
                  // @ts-ignore
                  width={stageWidth}
                  height={stageHeight}
                  scale={{ x: stageScale, y: stageScale }}
                  ref={mapStageRef}
                >
                  {children}
                </Stage>
              )}
            >
              <Layer ref={mapLayerRef}>
                <Image
                  image={(mapLoaded && mapImage) || undefined}
                  width={mapWidth}
                  height={mapHeight}
                  id="mapImage"
                  ref={mapImageRef}
                />
                {mapLoaded && children}
                <Group id="portal" />
              </Layer>
            </KonvaBridge>
          </ReactResizeDetector>
          {controls}
        </Box>
      </GridProvider>
    </MapInteractionProvider>
  );
}

export default MapInteraction;
