/**
 * @name Oneko
 * @author VWilk
 * @authorId 363358047784927234
 * @version 1
 * @description Cat follows your cursor (Go check out vencord's code ( I just ported it to BD :) https://github.com/Vendicated/Vencord/tree/main/src/plugins/oneko which was based off of Adryd's code.)
 * @source https://github.com/VWilk/Oneko_BetterDiscord/
 */

module.exports = () => {
  let cleanup = null;

  function createOneko() {
    const existing = document.getElementById("oneko");
    if (existing) {
      existing.remove();
    }

    const nekoEl = document.createElement("div");

    let nekoPosX = 32;
    let nekoPosY = 32;
    let mousePosX = 0;
    let mousePosY = 0;

    const isReducedMotion = false;
    if (isReducedMotion) {
      return () => {};
    }

    let frameCount = 0;
    let idleTime = 0;
    let idleAnimation = null;
    let idleAnimationFrame = 0;
    let lastFrameTimestamp;
    let animationFrameId = null;

    const nekoSpeed = 10;
    const spriteSets = {
      idle: [[-3, -3]],
      alert: [[-7, -3]],
      scratchSelf: [
        [-5, 0],
        [-6, 0],
        [-7, 0]
      ],
      scratchWallN: [
        [0, 0],
        [0, -1]
      ],
      scratchWallS: [
        [-7, -1],
        [-6, -2]
      ],
      scratchWallE: [
        [-2, -2],
        [-2, -3]
      ],
      scratchWallW: [
        [-4, 0],
        [-4, -1]
      ],
      tired: [[-3, -2]],
      sleeping: [
        [-2, 0],
        [-2, -1]
      ],
      N: [
        [-1, -2],
        [-1, -3]
      ],
      NE: [
        [0, -2],
        [0, -3]
      ],
      E: [
        [-3, 0],
        [-3, -1]
      ],
      SE: [
        [-5, -1],
        [-5, -2]
      ],
      S: [
        [-6, -3],
        [-7, -2]
      ],
      SW: [
        [-5, -3],
        [-6, -1]
      ],
      W: [
        [-4, -2],
        [-4, -3]
      ],
      NW: [
        [-1, 0],
        [-1, -1]
      ]
    };

    const handleMouseMove = event => {
      mousePosX = event.clientX;
      mousePosY = event.clientY;
    };

    function setSprite(name, frame) {
      const sprite = spriteSets[name][frame % spriteSets[name].length];
      nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
    }

    function resetIdleAnimation() {
      idleAnimation = null;
      idleAnimationFrame = 0;
    }

    function idle() {
      idleTime += 1;

      if (
        idleTime > 10 &&
        Math.floor(Math.random() * 200) === 0 &&
        idleAnimation == null
      ) {
        const availableIdleAnimations = ["sleeping", "scratchSelf"];

        if (nekoPosX < 32) {
          availableIdleAnimations.push("scratchWallW");
        }
        if (nekoPosY < 32) {
          availableIdleAnimations.push("scratchWallN");
        }
        if (nekoPosX > window.innerWidth - 32) {
          availableIdleAnimations.push("scratchWallE");
        }
        if (nekoPosY > window.innerHeight - 32) {
          availableIdleAnimations.push("scratchWallS");
        }

        idleAnimation =
          availableIdleAnimations[
            Math.floor(Math.random() * availableIdleAnimations.length)
          ];
      }

      switch (idleAnimation) {
        case "sleeping":
          if (idleAnimationFrame < 8) {
            setSprite("tired", 0);
            break;
          }
          setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
          if (idleAnimationFrame > 192) {
            resetIdleAnimation();
          }
          break;
        case "scratchWallN":
        case "scratchWallS":
        case "scratchWallE":
        case "scratchWallW":
        case "scratchSelf":
          setSprite(idleAnimation, idleAnimationFrame);
          if (idleAnimationFrame > 9) {
            resetIdleAnimation();
          }
          break;
        default:
          setSprite("idle", 0);
          return;
      }

      idleAnimationFrame += 1;
    }

    function frame() {
      frameCount += 1;

      const diffX = nekoPosX - mousePosX;
      const diffY = nekoPosY - mousePosY;
      const distance = Math.sqrt(diffX ** 2 + diffY ** 2);

      if (distance < nekoSpeed || distance < 48) {
        idle();
        return;
      }

      idleAnimation = null;
      idleAnimationFrame = 0;

      if (idleTime > 1) {
        setSprite("alert", 0);
        idleTime = Math.min(idleTime, 7);
        idleTime -= 1;
        return;
      }

      let direction = diffY / distance > 0.5 ? "N" : "";
      direction += diffY / distance < -0.5 ? "S" : "";
      direction += diffX / distance > 0.5 ? "W" : "";
      direction += diffX / distance < -0.5 ? "E" : "";

      setSprite(direction, frameCount);

      nekoPosX -= (diffX / distance) * nekoSpeed;
      nekoPosY -= (diffY / distance) * nekoSpeed;

      nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
      nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);

      nekoEl.style.left = `${nekoPosX - 16}px`;
      nekoEl.style.top = `${nekoPosY - 16}px`;
    }

    function onAnimationFrame(timestamp) {
      if (!nekoEl.isConnected) {
        return;
      }

      if (!lastFrameTimestamp) {
        lastFrameTimestamp = timestamp;
      }

      if (timestamp - lastFrameTimestamp > 100) {
        lastFrameTimestamp = timestamp;
        frame();
      }

      animationFrameId = window.requestAnimationFrame(onAnimationFrame);
    }

    nekoEl.id = "oneko";
    nekoEl.ariaHidden = "true";
    nekoEl.style.width = "32px";
    nekoEl.style.height = "32px";
    nekoEl.style.position = "fixed";
    nekoEl.style.pointerEvents = "none";
    nekoEl.style.backgroundImage =
      "url('https://raw.githubusercontent.com/adryd325/oneko.js/14bab15a755d0e35cd4ae19c931d96d306f99f42/oneko.gif')";
    nekoEl.style.imageRendering = "pixelated";
    nekoEl.style.left = `${nekoPosX - 16}px`;
    nekoEl.style.top = `${nekoPosY - 16}px`;
    nekoEl.style.zIndex = String(Number.MAX_SAFE_INTEGER);

    document.body.appendChild(nekoEl);
    document.addEventListener("mousemove", handleMouseMove);
    animationFrameId = window.requestAnimationFrame(onAnimationFrame);

    return () => {
      document.removeEventListener("mousemove", handleMouseMove);

      if (animationFrameId !== null) {
        window.cancelAnimationFrame(animationFrameId);
      }

      if (nekoEl.isConnected) {
        nekoEl.remove();
      }
    };
  }

  return {
    start: () => {
      cleanup?.();
      cleanup = createOneko();
    },
    stop: () => {
      cleanup?.();
      cleanup = null;
    }
  };
};
