import {
  createContext,
  useContext,
  useState,
  ReactNode,
  useEffect,
  useMemo,
} from "react";
import { throttle } from "lodash";

const breakpoints = {
  sm: "640px",
  md: "768px",
  lg: "1024px",
  xl: "1280px",
}

type ViewportContextType = {
  width: number;
  height: number;
  isMobile: boolean;
  isTablet: boolean;
  isDesktop: boolean;
  isLessThanMedium: boolean;
};

const ViewportContext = createContext<ViewportContextType>({
  width: 0,
  height: 0,
  isMobile: true,
  isTablet: false,
  isDesktop: false,
  isLessThanMedium: false,
});

// The result of this function will only be useful if it's in pixels. It would
// not be a useful value if the unit is e.g. `%`, `em`, etc.
function stripUnit(value: string | number): number {
  if (typeof value === "number") {
    return value;
  }

  return parseFloat(value);
}

function getIsMobile(width: number) {
  return width <= stripUnit(breakpoints.sm);
}

function getIsTablet(width: number) {
  return (
    width > stripUnit(breakpoints.sm) && width <= stripUnit(breakpoints.lg)
  );
}

function getIsDesktop(width: number) {
  return width > stripUnit(breakpoints.lg);
}

function getIsLessThanMedium(width: number) {
  return width < stripUnit(breakpoints.md);
}

export const ViewportProvider = ({ children }: { children: ReactNode }) => {
  const [width, setWidth] = useState(0);
  const [height, setHeight] = useState(0);
  const [isMobile, setIsMobile] = useState(getIsMobile(width));
  const [isDesktop, setIsDesktop] = useState(getIsDesktop(width));
  const [isTablet, setIsTablet] = useState(getIsTablet(width));
  const [isLessThanMedium, setIsLessThanMedium] = useState(
    getIsLessThanMedium(width)
  );

  useEffect(() => {
    // Set the size on the initial load. We need to do this here because we only
    // want it to run on the client.
    setWidth(window.innerWidth);
    setHeight(window.innerHeight);

    const handleWindowResize = throttle(
      () => {
        setWidth(window.innerWidth);
        setHeight(window.innerHeight);
      },
      250,
      { trailing: true }
    );

    window.addEventListener("resize", handleWindowResize);
    return () => window.removeEventListener("resize", handleWindowResize);
  }, [setWidth, setHeight]);

  useEffect(() => {
    setIsMobile(getIsMobile(width));
    setIsTablet(getIsTablet(width));
    setIsDesktop(getIsDesktop(width));
    setIsLessThanMedium(getIsLessThanMedium(width));
  }, [width]);

  return (
    <ViewportContext.Provider
      value={{
        width,
        height,
        isMobile,
        isTablet,
        isDesktop,
        isLessThanMedium,
      }}
    >
      {children}
    </ViewportContext.Provider>
  );
};

export const useViewport = () => {
  const { width, height, isMobile, isTablet, isDesktop, isLessThanMedium } =
    useContext(ViewportContext);

  return useMemo(
    () => ({ width, height, isMobile, isTablet, isDesktop, isLessThanMedium }),
    [width, height, isMobile, isTablet, isDesktop, isLessThanMedium]
  );
};
