import { useEffect, useRef, useState } from "react";
import type { AppStore } from "@editor/store";
import type { GlobalPointerConsumer, GlobalWheelConsumer } from "@input/index";
import {
  createViewportPointerPanHandler,
  handleViewportWheel,
  pointerInputEventFromPointerEvent,
  wheelInputEventFromWheelEvent,
} from "@input/index";
import { createRenderer } from "@renderer/index";

const ignoreGlobalPointer: GlobalPointerConsumer = () => false;
const ignoreGlobalWheel: GlobalWheelConsumer = () => false;

export type CanvasViewportProps = {
  store: AppStore;
  globalPointerConsumer?: GlobalPointerConsumer;
  globalWheelConsumer?: GlobalWheelConsumer;
};

export function CanvasViewport({
  store,
  globalPointerConsumer = ignoreGlobalPointer,
  globalWheelConsumer = ignoreGlobalWheel,
}: CanvasViewportProps) {
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const [isPanning, setIsPanning] = useState(false);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const renderer = createRenderer(canvas);
    renderer.render(store.getState());

    const unsubscribe = store.subscribe((state) => {
      renderer.render(state);
    });

    return () => {
      unsubscribe();
      renderer.dispose();
    };
  }, [store]);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const resizeObserver = new ResizeObserver(([entry]) => {
      if (!entry) return;

      const width = Math.floor(entry.contentRect.width);
      const height = Math.floor(entry.contentRect.height);
      store.dispatch("viewport.setSize", { w: width, h: height });
    });

    resizeObserver.observe(canvas);
    return () => resizeObserver.disconnect();
  }, [store]);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const panHandler = createViewportPointerPanHandler({
      globalConsumer: globalPointerConsumer,
      dispatch: store.dispatch,
      getCurrentZoom: () => store.getState().editor.viewport.zoom,
    });

    const handlePointerDown = (event: PointerEvent) => {
      const consumed = panHandler.pointerDown(pointerInputEventFromPointerEvent(event));
      if (!consumed) return;

      canvas.setPointerCapture(event.pointerId);
      setIsPanning(true);
      event.preventDefault();
    };

    const handlePointerMove = (event: PointerEvent) => {
      const consumed = panHandler.pointerMove(pointerInputEventFromPointerEvent(event));
      if (consumed) event.preventDefault();
    };

    const handlePointerUp = (event: PointerEvent) => {
      const consumed = panHandler.pointerUp(pointerInputEventFromPointerEvent(event));
      if (!consumed) return;

      setIsPanning(false);
      event.preventDefault();
    };

    canvas.addEventListener("pointerdown", handlePointerDown);
    canvas.addEventListener("pointermove", handlePointerMove);
    canvas.addEventListener("pointerup", handlePointerUp);
    canvas.addEventListener("pointercancel", handlePointerUp);

    return () => {
      canvas.removeEventListener("pointerdown", handlePointerDown);
      canvas.removeEventListener("pointermove", handlePointerMove);
      canvas.removeEventListener("pointerup", handlePointerUp);
      canvas.removeEventListener("pointercancel", handlePointerUp);
    };
  }, [globalPointerConsumer, store]);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const handleWheel = (event: WheelEvent) => {
      const consumed = handleViewportWheel({
        event: wheelInputEventFromWheelEvent(event),
        globalConsumer: globalWheelConsumer,
        dispatch: store.dispatch,
        currentZoom: store.getState().editor.viewport.zoom,
      });

      if (consumed) event.preventDefault();
    };

    canvas.addEventListener("wheel", handleWheel, { passive: false });
    return () => canvas.removeEventListener("wheel", handleWheel);
  }, [globalWheelConsumer, store]);

  return <canvas ref={canvasRef} className={isPanning ? "h-full w-full cursor-grabbing" : "h-full w-full cursor-grab"} />;
}
