import React, { useState, cloneElement, useEffect } from "react";
import PropTypes from "prop-types";

interface Props {
  ResetButton?: PropTypes.ReactElementLike;
  state?: [PanZoomConfig, (pz: PanZoomConfig) => void];
  onChange?: (pan_zoom: PanZoomConfig) => void;
}

const style = {
  container: {
    position: "absolute" as "absolute",
    top: 0,
    left: 0,
    right: 0,
    bottom: 0
  }
};

const withPanAndZoom = <P extends object>(
  WrappedComponent: React.ComponentType<P>
): React.FunctionComponent<P & Props> => ({
  ResetButton,
  state,
  onChange,
  ...props
}: Props) => {
  const [panning, setPanning] = useState(false);
  const [pan_zoom, setPosScale] = state
    ? state
    : useState({ y: 0, x: 0, scale: 1 });

  useEffect(() => {
    onChange && onChange(pan_zoom);
  }, [pan_zoom]);

  const onMouseLeave = () => {
    setPanning(false);
  };

  const onPanStart = (e: React.MouseEvent<HTMLDivElement>) => {
    setPanning(true);
  };

  const onPanStop = (e: React.MouseEvent<HTMLDivElement>) => {
    setPanning(false);
  };

  const onPan = (e: React.MouseEvent<HTMLDivElement>) => {
    panning &&
      setPosScale({
        ...pan_zoom,
        y: pan_zoom.y + e.movementY,
        x: pan_zoom.x + e.movementX
      });
  };

  const onZoom = (e: React.WheelEvent<HTMLDivElement>) => {
    const scale_by = 1.05;

    const mousePointTo = {
      x: e.pageX / pan_zoom.scale - pan_zoom.x / pan_zoom.scale,
      y: e.pageY / pan_zoom.scale - pan_zoom.y / pan_zoom.scale
    };

    const new_scale =
      e.deltaY < 0 ? pan_zoom.scale * scale_by : pan_zoom.scale / scale_by;

    setPosScale({
      scale: new_scale,
      x: -(mousePointTo.x - e.pageX / new_scale) * new_scale,
      y: -(mousePointTo.y - e.pageY / new_scale) * new_scale
    });
  };

  const onReset = (e: any) => {
    setPosScale({
      scale: 1,
      x: 0,
      y: 0
    });
  };

  return (
    <div
      style={style.container}
      onMouseLeave={onMouseLeave}
      onMouseDown={onPanStart}
      onMouseMove={onPan}
      onMouseUp={onPanStop}
      onWheel={onZoom}
    >
      <div
        style={{
          position: "absolute",
          top: pan_zoom.y,
          left: pan_zoom.x,
          transform: "scale(" + pan_zoom.scale + ")",
          minHeight: "100%",
          minWidth: "100%"
        }}
      >
        <WrappedComponent {...(props as P)} />
      </div>
      {!state &&
        ResetButton &&
        cloneElement(ResetButton, {
          ...ResetButton.props,
          onClick: onReset
        })}
    </div>
  );
};

export default withPanAndZoom;
