import { FederatedMouseEvent, FederatedWheelEvent } from "pixi.js";
import { Component, ReactNode, useEffect, useState, useRef } from "react";
import { useTransform } from "./TransformContext";
import { useMapData } from "./MapDataContext";
import { SCALE_FACTOR } from "./Constants";
import { useApplication } from "@pixi/react";

class ErrorBoundary extends Component<
  { children: ReactNode },
  { hasError: boolean }
> {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: Error, info: React.ErrorInfo) {
    console.error("Error caught:", error, info);
  }

  render() {
    return this.state.hasError ? <p>Whoopsie Daisy!</p> : this.props.children;
  }
}

export function InfiniteCanvas({
  children,
}: Readonly<{ children?: ReactNode }>) {
  const {
    position,
    setPosition,
    scale,
    setScale,
    rotation,
    setRotation,
    setScreenCenter,
    screenCenter,
  } = useTransform();
  const { routeData, originalRouteData, setSelectedSight } = useMapData();

  const applicationRef = useApplication();

  const [isDragging, setIsDragging] = useState(false);
  const [startMousePosition, setStartMousePosition] = useState({ x: 0, y: 0 });
  const [startRotation, setStartRotation] = useState(0);
  const [startPosition, setStartPosition] = useState({ x: 0, y: 0 });
  const [isPointerDown, setIsPointerDown] = useState(false);

  const [isUserInteracting, setIsUserInteracting] = useState(false);

  const lastOriginalRotation = useRef<number | undefined>(undefined);

  useEffect(() => {
    if (!applicationRef?.app?.canvas) return;

    const canvas = applicationRef.app.canvas;
    const canvasRect = canvas.getBoundingClientRect();
    const canvasLeft = canvasRect.left;
    const canvasTop = canvasRect.top;
    const centerX = window.innerWidth / 2 - canvasLeft;
    const centerY = window.innerHeight / 2 - canvasTop;
    setScreenCenter({ x: centerX, y: centerY });
  }, [applicationRef?.app, setScreenCenter]);

  const handlePointerDown = (e: FederatedMouseEvent) => {
    setIsPointerDown(true);
    setIsDragging(false);
    setIsUserInteracting(true);
    setStartPosition({
      x: position.x,
      y: position.y,
    });
    setStartMousePosition({
      x: e.globalX,
      y: e.globalY,
    });
    setStartRotation(rotation);
    e.stopPropagation();
  };

  useEffect(() => {
    const newRotation = originalRouteData?.rotate ?? 0;

    if (!isUserInteracting && lastOriginalRotation.current !== newRotation) {
      setRotation((newRotation * Math.PI) / 180);
      lastOriginalRotation.current = newRotation;
    }
  }, [originalRouteData?.rotate, isUserInteracting, setRotation]);

  const handlePointerMove = (e: FederatedMouseEvent) => {
    if (!isPointerDown) return;

    if (!isDragging) {
      const dx = e.globalX - startMousePosition.x;
      const dy = e.globalY - startMousePosition.y;
      if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
        setIsDragging(true);
      } else {
        return;
      }
    }

    if (e.shiftKey) {
      const center = screenCenter ?? { x: 0, y: 0 };
      const startAngle = Math.atan2(
        startMousePosition.y - center.y,
        startMousePosition.x - center.x
      );
      const currentAngle = Math.atan2(
        e.globalY - center.y,
        e.globalX - center.x
      );

      const rotationDiff = currentAngle - startAngle;

      setRotation(startRotation + rotationDiff);

      const cosDelta = Math.cos(rotationDiff);
      const sinDelta = Math.sin(rotationDiff);

      setPosition({
        x:
          center.x * (1 - cosDelta) +
          startPosition.x * cosDelta +
          (center.y - startPosition.y) * sinDelta,
        y:
          center.y * (1 - cosDelta) +
          startPosition.y * cosDelta +
          (startPosition.x - center.x) * sinDelta,
      });
    } else {
      setRotation(startRotation);
      setPosition({
        x: startPosition.x - startMousePosition.x + e.globalX,
        y: startPosition.y - startMousePosition.y + e.globalY,
      });
    }
    e.stopPropagation();
  };

  const handlePointerUp = (e: FederatedMouseEvent) => {
    if (!isDragging) {
      setSelectedSight(undefined);
    }

    setIsPointerDown(false);
    setIsDragging(false);

    setTimeout(() => {
      setIsUserInteracting(false);
    }, 100);
    e.stopPropagation();
  };

  const handleWheel = (e: FederatedWheelEvent) => {
    e.stopPropagation();
    setIsUserInteracting(true);

    const mouseX = e.globalX - position.x;
    const mouseY = e.globalY - position.y;

    const scaleMin = (routeData?.scale_min ?? 10) / SCALE_FACTOR;
    const scaleMax = (routeData?.scale_max ?? 20) / SCALE_FACTOR;

    const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
    const newScale = Math.max(scaleMin, Math.min(scaleMax, scale * zoomFactor));
    const actualZoomFactor = newScale / scale;

    if (scale === newScale) {
      setTimeout(() => {
        setIsUserInteracting(false);
      }, 100);
      return;
    }

    setPosition({
      x: position.x + mouseX * (1 - actualZoomFactor),
      y: position.y + mouseY * (1 - actualZoomFactor),
    });

    setScale(newScale);

    setTimeout(() => {
      setIsUserInteracting(false);
    }, 100);
  };

  useEffect(() => {
    applicationRef?.app.render();
  }, [position, scale, rotation]);

  return (
    <ErrorBoundary>
      {applicationRef?.app && (
        <pixiGraphics
          draw={(g) => {
            const canvas = applicationRef.app.canvas;
            g.clear();
            g.rect(0, 0, canvas?.width ?? 0, canvas?.height ?? 0);
            g.fill("#111");
          }}
          eventMode={"static"}
          interactive
          onPointerDown={handlePointerDown}
          onGlobalPointerMove={handlePointerMove}
          onPointerUp={handlePointerUp}
          onPointerUpOutside={handlePointerUp}
          onWheel={handleWheel}
        />
      )}
      <pixiContainer
        x={position.x}
        y={position.y}
        scale={scale}
        rotation={rotation}
      >
        {children}
      </pixiContainer>
      {/* Show center of the screen.
      <pixiGraphics
        eventMode="none"
        draw={(g) => {
          g.clear();
          const center = screenCenter ?? {x: 0, y: 0};
          g.circle(center.x, center.y, 1);
          g.fill("#fff");
        }}
      /> */}
    </ErrorBoundary>
  );
}
