import { createContext, useContext, useEffect, useState } from "react";

import type { ReactNode } from "react";

type PossibleValues = "mouse" | "touch";

interface Props {
  children: ReactNode;
}

const InputDeviceContext = createContext<PossibleValues | "" | "missing-input-device-provider">(
  "missing-input-device-provider"
);

const DEFAULT_DEVICE: PossibleValues = "mouse";

export const InputDeviceProvider: React.FC<Props> = ({ children }) => {
  const [device, setDevice] = useState<PossibleValues | "">("");

  useEffect(() => {
    let lastEvent = 0;

    const mouse = () => {
      if (Date.now() - lastEvent < 350) {
        // browsers send fake mouse events on touch after a possible delay. That's why if mouse event happens
        // quickly after (any) last event we will ignore that
        // we don't expect devices to be changing superfast, so we don't need to care that we can miss something
        // there is a better way to do that, but it's in experimental state at moment of writing
        return;
      }
      lastEvent = Date.now();
      setDevice("mouse");
    };

    const touch = () => {
      lastEvent = Date.now();
      setDevice("touch");
    };

    window.addEventListener("mousemove", mouse);
    window.addEventListener("wheel", mouse);
    window.addEventListener("touchmove", touch);
    window.addEventListener("touchend", touch);

    return () => {
      window.removeEventListener("wheel", mouse);
      window.removeEventListener("mousemove", mouse);
      window.removeEventListener("touchmove", touch);
      window.removeEventListener("touchend", touch);
    };
  }, []);

  return <InputDeviceContext.Provider value={device}>{children}</InputDeviceContext.Provider>;
};

export const useInputDevice = <T extends string>(overrideDefault?: T): PossibleValues | T => {
  const contextValue = useContext(InputDeviceContext);
  if (contextValue === "missing-input-device-provider") {
    throw new Error("InputDeviceProvider is missing in the tree");
  }
  return contextValue || overrideDefault || DEFAULT_DEVICE;
};
