import { useWindowDimension } from "hooks/useWindowDimension";
import { useRouter } from "next/router";
import React, { CSSProperties, useEffect, useRef, useState } from "react";

import { Image } from "./Image";

import styles from "./ShapesBackground.module.css";

const MOBILE_WIDTH = 768;

interface Props {
  getConfig?: GetShapesConfig;
}

export function ShapesBackground({ getConfig }: Props) {
  const [config, setConfig] = useState<ShapesConfig>({});
  const [prevWidth, setPrevWidth] = useState<number>(-1);
  const [prevRoute, setPrevRoute] = useState<string>("");
  const { width, height } = useWindowDimension();
  const shapesContainerRef = useRef<HTMLDivElement>(null);
  const router = useRouter();

  useEffect(() => {
    const containerWidth = shapesContainerRef.current?.offsetWidth;
    const containerHeight = shapesContainerRef.current?.offsetHeight;

    // In general, rerun getShapesConfig() if the screen size changes from desktop to mobile (or vice versa)
    if (
      containerWidth == null ||
      containerHeight == null ||
      !(
        router.asPath === "/" ||
        router.asPath !== prevRoute ||
        prevWidth < 0 ||
        (prevWidth <= MOBILE_WIDTH && MOBILE_WIDTH < width) ||
        (prevWidth > MOBILE_WIDTH && MOBILE_WIDTH >= width)
      )
    ) {
      return;
    }

    setPrevWidth(width);
    setPrevRoute(router.asPath);
    setConfig(getConfig?.(containerWidth, containerHeight) ?? {});
  }, [getConfig, width, height, prevWidth, prevRoute, router.asPath]);

  return (
    <div
      className={styles.shapesContainer}
      ref={shapesContainerRef}
      aria-hidden
    >
      {Object.entries(config).map(([type, instances]) =>
        instances.map((attributes, idx) => (
          <Shape
            key={idx.toString() + type}
            type={type as ShapeType}
            style={attributes}
          />
        ))
      )}
    </div>
  );
}

function Shape(props: { type: ShapeType; style: CSSProperties }) {
  return (
    <Image
      src={`/images/shapes/${props.type}.svg`}
      className={styles.shape}
      style={props.style}
    />
  );
}

export type ShapeType =
  | "asterisk"
  | "circle"
  | "cross"
  | "dots"
  | "hash"
  | "plus"
  | "ring"
  | "triangle"
  | "triangleBig"
  | "waves"
  | "wavesBig";

export type ShapesConfig = {
  [key in ShapeType]?: CSSProperties[];
};

export type GetShapesConfig = (width: number, height: number) => ShapesConfig;

type ShapeSize = {
  [key in ShapeType]: {
    size: "big" | "small";
    width: number;
    height: number;
    // 0 <= minAngle, maxAngle <= 180
    minAngle?: number;
    maxAngle?: number;
  };
};

const shapeTypes: ShapeType[] = [
  "asterisk",
  "circle",
  "cross",
  "dots",
  "hash",
  "plus",
  "ring",
  "triangle",
  "triangleBig",
  "waves",
  "wavesBig",
];

const shapesSizes: ShapeSize = {
  asterisk: {
    size: "big",
    width: 168,
    height: 168,
  },
  circle: {
    size: "big",
    width: 132,
    height: 132,
  },
  cross: {
    size: "big",
    width: 150,
    height: 150,
  },
  dots: {
    size: "big",
    width: 232,
    height: 250,
  },
  hash: {
    size: "small",
    width: 60,
    height: 60,
  },
  plus: {
    size: "small",
    width: 48,
    height: 48,
  },
  ring: {
    size: "small",
    width: 70,
    height: 70,
  },
  triangle: {
    size: "small",
    width: 68,
    height: 68,
    minAngle: 15,
    maxAngle: 26,
  },
  triangleBig: {
    size: "big",
    width: 138,
    height: 138,
    minAngle: 15,
    maxAngle: 26,
  },
  waves: {
    size: "small",
    width: 102,
    height: 50,
  },
  wavesBig: {
    size: "big",
    width: 252,
    height: 132,
  },
};

const shapesBySize = {
  big: shapeTypes.filter((shape) => shapesSizes[shape]["size"] == "big"),
  small: shapeTypes.filter((shape) => shapesSizes[shape]["size"] == "small"),
};

// Used to generate random shapes in the margins
export const defaultGetShapesConfig = ((pageWidth, pageHeight) => {
  if (window.innerWidth <= MOBILE_WIDTH) {
    return mobileShapesConfig;
  }

  const defaultConfig: ShapesConfig = {};
  const gap = 20;
  const minBoxWidth = 150;
  const boxWidth = Math.max(minBoxWidth, (pageWidth - 800 - 2 * gap) / 2);
  const boxHeight = 400;
  const shapeGenerationProbability = 0.85;

  for (let y = 0; y + boxHeight <= pageHeight; y += gap + boxHeight) {
    for (let x = 0; x <= 1; ++x) {
      if (Math.random() > shapeGenerationProbability) {
        continue;
      }

      const size =
        boxWidth > minBoxWidth && (y == 0 || y + 2 * boxHeight > pageHeight)
          ? "big"
          : "small";
      const shape: ShapeType = getRandomShape(size);
      const verticalOffset = getVerticalOffset(boxHeight, shape);
      const horizontalOffset = getHorizontalOffset(boxWidth - 2 * gap, shape);
      const shapeWidth = shapesSizes[shape]["width"];
      const shapeHeight = shapesSizes[shape]["height"];
      const angle = getRandomAngle(shape);
      const colour = getRandomColour();
      const opacity = getRandomOpacity(colour);

      defaultConfig[shape] ??= [];
      defaultConfig[shape]?.push({
        top: `${((y + verticalOffset) / 16).toFixed(5)}rem`,
        left:
          x == 0
            ? `${(((horizontalOffset + gap) / window.innerWidth) * 100).toFixed(
                5
              )}vw`
            : "unset",
        right:
          x == 1
            ? `${(((horizontalOffset + gap) / window.innerWidth) * 100).toFixed(
                5
              )}vw`
            : "unset",
        width: `${(shapeWidth / 16).toFixed(5)}rem`,
        height: `${(shapeHeight / 16).toFixed(5)}rem`,
        transform: `rotate(${angle}deg)`,
        filter: `var(--${colour})`,
        opacity: `${opacity}%`,
      });
    }
  }

  return defaultConfig;
}) as GetShapesConfig;

function getRandomShape(size: "big" | "small"): ShapeType {
  const idx = Math.floor(Math.random() * shapesBySize[size].length);
  return shapesBySize[size][idx];
}

function getVerticalOffset(boxHeight: number, shape: ShapeType): number {
  const padding = shapesSizes[shape]["height"];
  return Math.floor(Math.random() * (boxHeight - padding));
}

function getHorizontalOffset(boxWidth: number, shape: ShapeType): number {
  const padding = shapesSizes[shape]["width"];
  return shapesSizes[shape]["size"] == "big"
    ? Math.floor(Math.random() * (boxWidth - padding / 2) - padding / 2)
    : Math.floor(Math.random() * (boxWidth - padding));
}

function getRandomAngle(shape: ShapeType): number {
  const minAngle = shapesSizes[shape]["minAngle"] ?? 0;
  const maxAngle = shapesSizes[shape]["maxAngle"] ?? 0;
  const direction = Math.random() < 0.5 ? 1 : -1;
  return (
    (minAngle + Math.floor(Math.random() * (maxAngle - minAngle + 1))) *
    direction
  );
}

function getRandomColour(): "blue" | "teal" {
  return Math.random() < 0.7 ? "blue" : "teal";
}

function getRandomOpacity(colour: "blue" | "teal"): number {
  if (colour === "blue") {
    return Math.random() < 0.8 ? 20 : 25;
  } else {
    return Math.random() < 0.8 ? 25 : 30;
  }
}

// Used for most mobile pages
export const mobileShapesConfig = {
  dots: [
    {
      top: "calc(-6rem / 16)",
      left: "calc(-95rem / 16)",
      width: "calc(166rem / 16)",
      height: "calc(150rem / 16)",
    },
  ],
  hash: [
    {
      top: "calc(88rem / 16)",
      right: "15vw",
      width: "calc(40rem / 16)",
      height: "calc(40rem / 16)",
    },
  ],
  triangle: [
    {
      top: "calc(20rem / 16)",
      right: "1vw",
      width: "calc(45rem / 16)",
      height: "calc(45rem / 16)",
      transform: "rotate(17deg)",
    },
  ],
};
