import { cancelFrame, frame } from "framer-motion/dom";
import {
  Clock,
  ColorManagement,
  HemisphereLight,
  LinearToneMapping,
  MathUtils,
  type Mesh,
  type MeshStandardMaterial,
  PerspectiveCamera,
  PMREMGenerator,
  Scene,
  SRGBColorSpace,
  type Texture,
  Vector2,
  Vector3,
  WebGLRenderer,
} from "three";

import { Background } from "@/assets/three/objects/Background";
import AssetsProvider from "@/assets/utils/AssetsProvider";
import { type SunoSong } from "@/types";

import HeartSpringScene from "./graphics/scenes/objects/SpringScene";
import CameraControl from "./helpers/CameraControl";
import StepsController from "./objects/StepsController";

export default class Three {
  public renderer!: WebGLRenderer;
  public camera: PerspectiveCamera;
  public scene: Scene;
  private halfWindow = new Vector2();
  private currentMouse = new Vector2();
  private clock: Clock = new Clock();

  private light!: HemisphereLight;

  private background: Background;

  private cameraControls?: CameraControl;
  private cameraTarget: Vector3 = new Vector3();

  private stepsController: StepsController | undefined;
  private heartSpringScene?: HeartSpringScene;

  constructor(el: HTMLCanvasElement) {
    this.camera = new PerspectiveCamera(
      70,
      window.innerWidth / window.innerHeight,
      0.1,
      100,
    );
    this.camera.position.z = 2;

    this.scene = new Scene();

    const canvas = el as HTMLCanvasElement;
    this.renderer = new WebGLRenderer({
      canvas: canvas,
      //* Test /*/ context: canvas.getContext("webgl")!, //*/
      antialias: true,
    });
    this.renderer.setPixelRatio(window.devicePixelRatio);
    ColorManagement.enabled = true;
    this.renderer.outputColorSpace = SRGBColorSpace;

    this.background = new Background(this.renderer);
    this.scene.add(this.background);

    this.cameraControls = new CameraControl(
      this.camera,
      this.cameraTarget,
      document.body,
    );
    this.renderer.toneMapping = LinearToneMapping;
    this.renderer.toneMappingExposure = 1.2;

    this.light = new HemisphereLight(0xeeeecc, 0x040400, 2.0);
    this.scene.add(this.light);

    this.onWindowResize = this.onWindowResize.bind(this);
    window.addEventListener("resize", this.onWindowResize);
    this.onWindowResize();

    this.onMouseMove = this.onMouseMove.bind(this);
    window.addEventListener("mousemove", this.onMouseMove);

    this.animateLoop = this.animateLoop.bind(this);
    frame.render(this.animateLoop, true);
  }

  onWindowResize() {
    this.halfWindow.set(window.innerWidth / 2, window.innerHeight / 2);

    this.camera.aspect = window.innerWidth / window.innerHeight;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(window.innerWidth, window.innerHeight);
  }

  onMouseMove(event: MouseEvent) {
    this.currentMouse.set(
      (event.clientX - this.halfWindow.x) * 0.02,
      -(event.clientY - this.halfWindow.y) * 0.05,
    );
  }

  setStep(step: number, lastStep: number, firstCall: boolean = false) {
    this.background.setStep(step);
    this.heartSpringScene?.setStep(step);
    if (!firstCall) this.stepsController?.setStep(step, lastStep);
  }

  forceTransition(elementIn: number, elementOut: number) {
    this.stepsController?.forceTransition(elementIn, elementOut);
    this.heartSpringScene?.transitionOut();
  }

  setSongs(songs: Array<SunoSong>) {
    this.stepsController?.setSongs(songs);
  }

  setIsMuted(isMuted: boolean) {
    this.stepsController?.setIsMuted(isMuted);
  }

  showError() {
    this.stepsController?.showError();
  }
  setSong(song: SunoSong, direction: number) {
    this.stepsController?.setSong(song, direction);
  }

  onScrollResults(scroll: number) {
    this.stepsController?.onScrollResults(scroll);
  }

  init3DScene(step: number, songs: Array<SunoSong>, isMuted: boolean) {
    const pmremGenerator = new PMREMGenerator(this.renderer);

    this.scene.environment = pmremGenerator.fromEquirectangular(
      AssetsProvider.getAsset("environment").asset as Texture,
    ).texture;

    this.heartSpringScene = new HeartSpringScene(
      this.renderer,
      this.cameraControls!,
    );
    this.scene.add(this.heartSpringScene);

    this.stepsController = new StepsController(
      step,
      songs,
      isMuted,
      this.renderer,
    );
    this.stepsController.traverse((obj) => {
      const mesh = obj as Mesh;
      if (mesh.isMesh) {
        const material = mesh.material as MeshStandardMaterial;
        material.emissiveMap && this.renderer.initTexture(material.emissiveMap);
        material.map && this.renderer.initTexture(material.map);
        this.renderer.compile(mesh, this.camera, this.scene);
      }
    });
    this.scene.add(this.stepsController);

    this.setStep(step, step, true);
  }

  animateLoop() {
    const delta = this.clock.getDelta();

    this.heartSpringScene?.update(delta);
    this.cameraControls?.update(this.clock.elapsedTime, delta);

    this.stepsController?.update(delta);

    this.renderer.render(this.scene, this.camera);
  }

  destroy() {
    window.removeEventListener("resize", this.onWindowResize);
    cancelFrame(this.animateLoop);
  }
}
