import { useMemo, useRef } from "react";
import type { ImageDocument } from "@core/document";
import type { AppState, MaskEditState } from "@editor/state";
import type { AppStore } from "@editor/store";
import type { InteractionMode } from "@editor/tools";
import type { GlobalKeybindConsumer, GlobalPointerConsumer, GlobalWheelConsumer } from "@input/index";
import { brushUnavailableHint, canPreviewBrush, type BrushTargetEditorState } from "@operations/paint/brush";
import { canvasCursorClass } from "./canvas/cursor";
import { useCanvasInput } from "./canvas/useCanvasInput";
import { useCanvasRenderer } from "./canvas/useCanvasRenderer";
import { useCanvasResize } from "./canvas/useCanvasResize";
import { useAppState } from "./useAppState";

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

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

export function CanvasViewport({
  store,
  globalKeybindConsumer = ignoreGlobalKeybind,
  globalPointerConsumer = ignoreGlobalPointer,
  globalWheelConsumer = ignoreGlobalWheel,
}: CanvasViewportProps) {
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const cursorState = useAppState(store, selectCanvasCursorState, canvasCursorStatesEqual);
  const inputOptions = useMemo(
    () => ({ globalKeybindConsumer, globalPointerConsumer, globalWheelConsumer }),
    [globalKeybindConsumer, globalPointerConsumer, globalWheelConsumer],
  );

  useCanvasRenderer(canvasRef, store);
  useCanvasResize(canvasRef, store.dispatch);
  useCanvasInput(canvasRef, store, inputOptions);
  const brushHint = brushUnavailableHint(cursorState.document, cursorState.editor);
  const hasBrushPreview = Boolean(cursorState.hasBrushPreview && !brushHint && canPreviewBrush(cursorState.document, cursorState.editor));
  const cursorClass = canvasCursorClass(cursorState.editor.tools.interactionMode, cursorState.isPanning, hasBrushPreview, !brushHint);

  return (
    <canvas
      ref={canvasRef}
      className={`h-full w-full touch-none ${cursorClass}`}
      role="img"
      aria-label="Image editing canvas. Use the tools and layers controls to edit the document."
    />
  );
}

type CanvasCursorState = {
  document: ImageDocument;
  editor: BrushTargetEditorState;
  hasBrushPreview: boolean;
  isPanning: boolean;
};

function selectCanvasCursorState(state: AppState): CanvasCursorState {
  return {
    document: state.document,
    editor: {
      selection: state.editor.selection,
      tools: {
        activeTool: state.editor.tools.activeTool,
        interactionMode: state.editor.tools.interactionMode,
      },
      maskEdit: state.editor.maskEdit,
    },
    hasBrushPreview: Boolean(state.editor.brushPreview),
    isPanning: state.editor.pointerSession?.type === "pan",
  };
}

function canvasCursorStatesEqual(a: CanvasCursorState, b: CanvasCursorState): boolean {
  return (
    a.document === b.document &&
    a.hasBrushPreview === b.hasBrushPreview &&
    a.isPanning === b.isPanning &&
    a.editor.selection === b.editor.selection &&
    a.editor.tools.activeTool === b.editor.tools.activeTool &&
    interactionModesEqual(a.editor.tools.interactionMode, b.editor.tools.interactionMode) &&
    maskEditStatesEqual(a.editor.maskEdit, b.editor.maskEdit)
  );
}

function interactionModesEqual(a: InteractionMode, b: InteractionMode): boolean {
  if (a.type !== b.type) return false;
  if (a.type === "temporary-pan") return b.type === "temporary-pan" && a.previousTool === b.previousTool;
  return b.type === "tool" && a.tool === b.tool;
}

function maskEditStatesEqual(a: MaskEditState | undefined, b: MaskEditState | undefined): boolean {
  if (a === b) return true;
  if (!a || !b) return false;
  return a.targetLayerId === b.targetLayerId && a.maskLayerId === b.maskLayerId && a.viewMode === b.viewMode;
}
