import { useCallback, useEffect, useRef } from 'react';
import { useInterval } from 'usehooks-ts';

import { useContextSelector } from '@/hooks/useContextSelector';

import StudioContext from './StudioContext';

const ZOOM_PAN_INTERVAL_MS = 1000 / 120;
const ZOOM_FACTOR = 1.02;
const SCROLL_PX = 8;

export default function useZoomButtons() {
  const zoomAboutSelectionOrCenter = useContextSelector(
    StudioContext,
    (context) => context.timelineController.zoomAboutSelectionOrCenter
  );

  const scrollXRef = useContextSelector(
    StudioContext,
    (context) => context.timelineController.scrollXRef
  );

  const frameCountRef = useContextSelector(
    StudioContext,
    (context) => context.timelineController.frameCountRef
  );

  const resetZoom = useContextSelector(
    StudioContext,
    (context) => context.timelineController.resetZoom
  );

  const zoomPanInstructionsRef = useRef<{
    sprint?: boolean;
    left?: boolean;
    right?: boolean;
    in?: boolean;
    out?: boolean;
  }>({});

  useEffect(() => {
    const handleWASDDown = (e: KeyboardEvent) => {
      if (
        e.target instanceof HTMLElement &&
        e.target.matches('input, textarea, select, [contenteditable]')
      ) {
        return;
      }
      if (e.metaKey || e.ctrlKey) return;
      if (e.key.toLowerCase() === '=') {
        zoomPanInstructionsRef.current.in = true;
      } else if (e.key.toLowerCase() === '-') {
        zoomPanInstructionsRef.current.out = true;
      }
    };
    const handleWASDUp = (e: KeyboardEvent) => {
      if (
        e.target instanceof HTMLElement &&
        e.target.matches('input, textarea, select, [contenteditable]')
      ) {
        return;
      }
      if (e.metaKey || e.ctrlKey) return;
      if (e.key.toLowerCase() === '=') {
        zoomPanInstructionsRef.current.in = false;
      } else if (e.key.toLowerCase() === '-') {
        zoomPanInstructionsRef.current.out = false;
      }
    };
    window.addEventListener('keydown', handleWASDDown);
    window.addEventListener('keyup', handleWASDUp);
    return () => {
      window.removeEventListener('keydown', handleWASDDown);
      window.removeEventListener('keyup', handleWASDUp);
    };
  }, []);

  const lastIntervalTimeRef = useRef(performance.now());

  useInterval(
    useCallback(() => {
      const now = performance.now();
      const delta = now - lastIntervalTimeRef.current;
      lastIntervalTimeRef.current = now;
      const instructions = zoomPanInstructionsRef.current;

      const zoomFactor = Math.pow(
        ZOOM_FACTOR * (instructions.sprint ? 1.03 : 1),
        delta / ZOOM_PAN_INTERVAL_MS
      );
      const scrollPx =
        SCROLL_PX *
        (instructions.sprint ? 2.5 : 1) *
        (delta / ZOOM_PAN_INTERVAL_MS);
      let didSomething = false;
      if (instructions.in && !instructions.out) {
        zoomAboutSelectionOrCenter(zoomFactor);
        didSomething = true;
      }
      if (instructions.out && !instructions.in) {
        zoomAboutSelectionOrCenter(1 / zoomFactor);
        didSomething = true;
      }
      if (instructions.left && !instructions.right) {
        scrollXRef.current -= scrollPx;
        didSomething = true;
      }
      if (instructions.right && !instructions.left) {
        scrollXRef.current += scrollPx;
        didSomething = true;
      }
      if (didSomething) frameCountRef.current++;
    }, [zoomAboutSelectionOrCenter]),
    ZOOM_PAN_INTERVAL_MS
  );

  const handleZoomInMouseDown = useCallback(() => {
    zoomPanInstructionsRef.current.in = true;
    const onMouseUp = () => {
      zoomPanInstructionsRef.current.in = false;
      window.removeEventListener('mouseup', onMouseUp);
    };
    window.addEventListener('mouseup', onMouseUp);
  }, []);

  const handleZoomOutMouseDown = useCallback(() => {
    zoomPanInstructionsRef.current.out = true;
    const onMouseUp = () => {
      zoomPanInstructionsRef.current.out = false;
      window.removeEventListener('mouseup', onMouseUp);
    };
    window.addEventListener('mouseup', onMouseUp);
  }, []);

  return {
    handleZoomInMouseDown,
    handleZoomOutMouseDown,
    resetZoom,
  };
}
