"use client";

import { SpotLight } from "three";
import {
  useGLTF,
  PerspectiveCamera,
  Environment,
  Text,
  Html,
  type PerspectiveCameraProps,
  PerformanceMonitor,
} from "@react-three/drei";
import {
  animated as threeAnimated,
  useSpring as useThreeSpring,
} from "@react-spring/three";
import {
  animated as webAnimated,
  useSpring as useWebSpring,
} from "@react-spring/web";
import {
  useEffect,
  useMemo,
  useState,
  useRef,
  Suspense,
  useCallback,
  type JSX,
} from "react";
import round from "lodash/round";
import Stack from "@/util/stack";
import { Canvas } from "@react-three/fiber";
import { mdiArrowLeftBold } from "@mdi/js";
import { Icon } from "@mdi/react";
import { PDF } from "../util/PDF";
import { CellphoneUI } from "../ui/CellphoneUI";
import { PCUI } from "../ui/PCUI";
import { useConfig } from "../hooks/useConfig";
import { GLTFResult } from "@/types/scene";
import { hashtoView, views, View } from "./consts";
import { StaticMeshes } from "./StaticMeshes";
import { Buttons } from "./Buttons";
import { Credits } from "../ui/Credits";
import { isWebGL2Available } from "@react-three/drei";
import { useWindowSize } from "../hooks/useWindowSize";
import { Info } from "../util/Info";
import { Loader } from "../ui/Loader";

const AnimatedCam = threeAnimated(PerspectiveCamera);
const AnimatedButton = webAnimated.button as React.ComponentType<
  React.ButtonHTMLAttributes<HTMLButtonElement> & { children?: React.ReactNode }
>;

export function Scene(props: JSX.IntrinsicElements["group"]) {
  const { nodes, materials } = useGLTF("/scene.glb") as unknown as GLTFResult;
  const { width, height } = useWindowSize();
  const config = useConfig();

  // States
  const [fps, setFps] = useState(0);
  const [dpr, setDpr] = useState(2);
  const [pendingView, setPendingView] = useState<View | null>(null);
  const [backHovered, setBackHovered] = useState(false);
  const [backClicked, setBackClicked] = useState(false);
  const [menuTraversal, setMenuTraversal] = useState<Stack<View>>(() => {
    const s = new Stack<View>();
    s.push(View.MainView);
    const hash = window.location.hash.slice(1);
    if (hash in hashtoView && hashtoView[hash] !== View.MainView) {
      s.push(hashtoView[hash]);
    }
    return s;
  });

  // Variables
  const currentView = menuTraversal.peek() ?? View.MainView;
  const view = views[currentView];

  // Refs
  const portalRef = useRef<HTMLElement | null>(null);

  // Callbacks
  const goPreviousView = useCallback(() => {
    setMenuTraversal((prev) => {
      if (prev.size() <= 1) return prev;
      const next = prev.clone();
      next.pop();
      setPendingView(next.peek()!);
      return next;
    });
  }, []);

  // Memos
  const spotlight = useMemo(() => {
    const light = new SpotLight("#ffffff");
    light.intensity = 175;
    light.penumbra = 0.25;
    light.castShadow = true;
    light.shadow.mapSize.set(2048, 2048);
    light.shadow.bias = -0.000025;
    return light;
  }, []);

  // Effects
  useEffect(() => {
    portalRef.current = document.body!;
  }, []);

  // Springs
  const cameraSpring = useThreeSpring<PerspectiveCameraProps>({
    ...view,
    config: {
      precision: 0.0001,
      friction: 80,
      mass: 10,
    },
  });

  const { backScale, backOpacity } = useWebSpring({
    backScale: backHovered ? 1.25 : 1,
    backOpacity: menuTraversal.size() > 1 ? 1 : 0,
    onRest: () => backClicked && setBackClicked(false),
  });

  // Scene
  return (
    <main className="h-screen w-screen overscroll-none touch-none bg-[#1b1b1b]">
      {isWebGL2Available() ? (
        width >= 1.5 * height ? (
          <>
            <Loader />
            <div className="pointer-events-none fixed z-[999999999] size-full">
              <AnimatedButton
                style={{
                  transform: backScale.to(
                    (s) => `scale(${s})`
                  ) as unknown as string,
                  opacity: backOpacity as unknown as number,
                  pointerEvents: menuTraversal.size() > 1 ? "auto" : "none",
                  cursor: backHovered ? "pointer" : "auto",
                }}
                className="m-4"
                onMouseEnter={() => setBackHovered(true)}
                onMouseLeave={() => setBackHovered(false)}
                onClick={() => {
                  setBackClicked(true);
                  goPreviousView();
                }}
              >
                <Icon
                  path={mdiArrowLeftBold}
                  size={2}
                  color="white"
                  className="stroke-[0.75] stroke-black"
                />
              </AnimatedButton>
              <div className="min-w-12 text-white bottom-0 right-0 absolute m-3 p-1 text-xs text-right text-stroke-2 text-stroke-black paint-sfm">
                <Info />
                {fps > 0 && (
                  <>
                    {fps.toFixed(0)} fps | {dpr}x
                  </>
                )}
              </div>
            </div>
            <Canvas
              shadows
              dpr={dpr}
              gl={{ localClippingEnabled: true, antialias: true, alpha: true }}
            >
              <Suspense fallback={null}>
                <group {...props} dispose={null}>
                  <PerformanceMonitor
                    ms={100}
                    onChange={(api) => {
                      setFps(api.fps);
                      setDpr(round(1.5 * api.factor + 0.5, 1));
                    }}
                  >
                    <Environment preset="apartment" />
                    <AnimatedCam makeDefault {...cameraSpring} />
                    <Buttons
                      menuTraversal={menuTraversal}
                      setMenuTraversal={setMenuTraversal}
                      pendingView={pendingView}
                      setPendingView={setPendingView}
                      goPreviousView={goPreviousView}
                      currentView={currentView}
                    />
                    <group>
                      <primitive
                        object={spotlight}
                        position={[-1.915, 20, 1.0925]}
                      />
                      <primitive
                        object={spotlight.target}
                        position={[-1.915, 0, 1.0925]}
                      />
                    </group>
                    <group
                      position={[2.50415, 0.12973, 3.47808]}
                      rotation={[0, -Math.PI / 2, 0]}
                      scale={0.01}
                    >
                      <mesh
                        castShadow
                        receiveShadow
                        geometry={nodes.Monitor.geometry}
                        material={materials.PlasticMaterial}
                        position={[-175, 415.25, -160]}
                      >
                        <Html
                          transform
                          receiveShadow
                          castShadow
                          pointerEvents={
                            currentView === View.PCView ? "auto" : "none"
                          }
                          occlude="blending"
                          className="w-[1452px] h-[810px] bg-[#0e1838]"
                          scale={10}
                          position={[0, 170, 0.5]}
                          raycast={
                            currentView === View.DesktopView
                              ? () => null
                              : undefined
                          }
                          distanceFactor={10}
                        >
                          <div className="size-full">
                            <PCUI />
                          </div>
                        </Html>
                      </mesh>
                    </group>
                    <group
                      position={[4.105, 4.2825, -2.085]}
                      rotation={[0, -Math.PI / 2, 0]}
                    >
                      <mesh
                        castShadow
                        receiveShadow
                        geometry={nodes.SecondaryMonitorMesh.geometry}
                        material={materials.SecondaryMonitorMaterial}
                        scale={0.01}
                      >
                        <Credits currentView={currentView} />
                      </mesh>
                    </group>
                    <group
                      position={[3.81024, 3.85444, 6.13972]}
                      rotation={[-Math.PI, -0.44331, -Math.PI]}
                      scale={0.035}
                    >
                      <mesh
                        castShadow
                        receiveShadow
                        geometry={nodes.Paper3.geometry}
                        material={materials.PaperMaterial}
                      >
                        <Html
                          transform
                          receiveShadow
                          castShadow
                          pointerEvents={
                            currentView === View.ResumeView ? "auto" : "none"
                          }
                          occlude="blending"
                          className="w-[800px] h-[1074px] bg-white"
                          scale={15}
                          position={[28.7765, 12.431875, 3.725]}
                          rotation={[-Math.PI / 2, 0, (1 * Math.PI) / 9]}
                          raycast={
                            currentView === View.ResumeView
                              ? () => null
                              : undefined
                          }
                          distanceFactor={1}
                        >
                          <PDF url="/config/resume.pdf" />
                        </Html>
                      </mesh>
                    </group>
                    <group
                      position={[2.58559, 4.69855, 3.29056]}
                      rotation={[-Math.PI, -0.43633, -Math.PI]}
                      scale={0.02}
                    >
                      <mesh
                        castShadow
                        receiveShadow
                        geometry={nodes.CellphoneMesh.geometry}
                        material={materials.CellphoneMaterial}
                        position={[-102.64982, -21.45192, 247.01788]}
                      >
                        <Html
                          transform
                          receiveShadow
                          castShadow
                          pointerEvents={
                            currentView === View.CellphoneView ? "auto" : "none"
                          }
                          occlude="blending"
                          className="w-[1008px] h-[1614px] bg-blue-100"
                          position={[0, 2.85, 0]}
                          rotation={[-Math.PI / 2, 0, Math.PI / 2]}
                          raycast={
                            currentView === View.CellphoneView
                              ? () => null
                              : undefined
                          }
                          style={{
                            backgroundImage: "url('/assets/cellphone-bg.webp')",
                            backgroundColor: "black",
                          }}
                          distanceFactor={10}
                        >
                          <CellphoneUI />
                        </Html>
                      </mesh>
                    </group>
                    <Text
                      position={[-2.415, 12.5, -6]}
                      font="/assets/clashdisplay.ttf"
                      color="black"
                      fontSize={3}
                    >
                      {config.name[0]}
                    </Text>
                    <Text
                      position={[5.185, 12.5, 1.592]}
                      font="/assets/clashdisplay.ttf"
                      rotation={[0, -Math.PI / 2, 0]}
                      color="black"
                      fontSize={3}
                    >
                      {config.name[1]}
                    </Text>
                    {config.status && (
                      <Text
                        position={[5.185, 10, 1.592]}
                        font="/assets/inter.ttf"
                        rotation={[0, -Math.PI / 2, 0]}
                        color="black"
                        fontSize={0.75}
                      >
                        {config.status}
                      </Text>
                    )}
                    {config.location && (
                      <Text
                        position={[5.185, 8.5, 1.592]}
                        font="/assets/inter.ttf"
                        rotation={[0, -Math.PI / 2, 0]}
                        color="black"
                        fontSize={0.75}
                      >
                        📍&#8201;{config.location}
                      </Text>
                    )}
                    <StaticMeshes nodes={nodes} materials={materials} />
                  </PerformanceMonitor>
                </group>
              </Suspense>
            </Canvas>
          </>
        ) : (
          <div
            className={`${
              config.fallbackUrl ? "pt-12" : ""
            } flex flex-col space-y-6 w-screen h-screen text-center justify-center items-center text-white text-4xl`}
          >
            <p className="mx-8">Screen too small, please rotate</p>
            {config.fallbackUrl && (
              <p className="text-base mx-8">
                or visit the{" "}
                <a
                  href={config.fallbackUrl}
                  target="_blank"
                  className="text-blue-500 hover:underline"
                >
                  fallback
                </a>
              </p>
            )}
          </div>
        )
      ) : (
        <div
          className={`${
            config.fallbackUrl ? "pt-12" : ""
          } flex flex-col space-y-6 w-screen h-screen text-center justify-center items-center text-white text-4xl`}
        >
          <p>
            Please enable{" "}
            <a
              href="https://get.webgl.org"
              target="_blank"
              className="text-blue-500 hover:underline"
            >
              WebGL
            </a>
          </p>
          {config.fallbackUrl && (
            <p className="text-base">
              or visit the{" "}
              <a
                href={config.fallbackUrl}
                target="_blank"
                className="text-blue-500 hover:underline"
              >
                fallback
              </a>
            </p>
          )}
        </div>
      )}
    </main>
  );
}

useGLTF.preload("/scene.glb");
