import { animate, cubicBezier } from "framer-motion";
import {
  type BufferGeometry,
  Matrix3,
  Mesh,
  MeshNormalMaterial,
  Object3D,
  PlaneGeometry,
  SphereGeometry,
  Vector3,
  type WebGLRenderer,
} from "three";
import { type GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
import { Pane } from "tweakpane";

import type CameraControl from "@/assets/three/helpers/CameraControl";
import AssetsProvider from "@/assets/utils/AssetsProvider";
import { QUIZ_STEPS, QUIZ_STEPS_VARIANTS } from "@/config/data";
import { getURLQuery } from "@/helpers/url";

import TextureGeometry from "../../gpu/render/TextureGeometry";
import SpringVectorSimulation from "../../gpu/simulation/spring/SpringVectorSimulation";

class SpringScene extends Object3D {
  private planeRaycast: Mesh;
  private springSimulation: SpringVectorSimulation;
  private textureMesh: TextureGeometry;
  private geometry: BufferGeometry = new SphereGeometry(2, 10, 10);
  private tmpMatrix: Matrix3 = new Matrix3();
  private debugPointMesh?: Mesh;
  private currentColor: string = "#7683CA";
  private newColor: string = "#ff0000";
  private openScale: number = 3.8;

  private debug: boolean = false;
  private showGui: boolean = getURLQuery("showgui") === "true";
  private toogledGui: boolean = true;
  private pane?: Pane;

  constructor(
    private renderer: WebGLRenderer,
    private cameraControls: CameraControl,
  ) {
    super();
    this.planeRaycast = new Mesh(
      new PlaneGeometry(100, 100, 1, 1),
      new MeshNormalMaterial(),
    );
    this.planeRaycast.position.z = -0.2;

    // if(this.debug) this.setupDebug();
    const gltf: GLTF = AssetsProvider.getAsset("heartBG").asset;

    this.renderOrder = -1;
    gltf?.scene?.traverse((child) => {
      const mesh = child as Mesh;
      if (mesh.isMesh) {
        this.geometry = mesh.geometry;
      }
    });

    this.springSimulation = new SpringVectorSimulation(
      this.geometry!,
      this.renderer,
    );
    this.textureMesh = new TextureGeometry(
      this.springSimulation,
      this.renderer,
    );
    this.textureMesh?.material.uniforms.diffuseColor?.value.set(
      this.currentColor,
    );
    this.textureMesh.scale.set(
      this.openScale * 0.1,
      this.openScale * 0.1,
      this.openScale * 0.1,
    );
    this.textureMesh.position.set(0, 0.7, -0.2);
    this.textureMesh.renderOrder = -1;
    this.add(this.textureMesh);

    if (this.showGui) this.setupGui();
  }

  private setupDebug() {
    this.add(this.planeRaycast);
    this.debugPointMesh = new Mesh(
      new SphereGeometry(0.3, 10, 10),
      new MeshNormalMaterial(),
    );
    this.add(this.debugPointMesh);
  }

  public update(dt: number) {
    if (dt > 0.032) dt = 0.032;
    const intersects = this.cameraControls.raycast(this.planeRaycast);
    if (intersects) {
      if (this.springSimulation) {
        this.springSimulation.velocitiesMaterial.uniforms.uPointers.value[0].copy(
          intersects[0].point,
        );
        this.springSimulation.velocitiesMaterial.uniforms.uPointers.value[1].copy(
          intersects[0].point,
        );

        if (this.debug) this.debugPointMesh?.position.copy(intersects[0].point);
      }
    }
    this.springSimulation.velocitiesMaterial.uniforms.uWorldPosition.value =
      this.textureMesh.position;
    this.springSimulation.velocitiesMaterial.uniforms.uModelMatrix.value =
      this.textureMesh.matrixWorld;
    this.springSimulation.velocitiesMaterial.uniforms.uNormalMatrix.value =
      this.tmpMatrix.getNormalMatrix(this.textureMesh.matrixWorld);

    this.springSimulation.velocitiesMaterial.uniforms.uStrengthDisplacement.value =
      this.cameraControls.influence;

    this.springSimulation?.update(dt);
    this.textureMesh?.update(dt);

    // this.debugPointMesh.scale.set(
    //   this.cameraControls.influence,
    //   this.cameraControls.influence,
    //   this.cameraControls.influence);
  }

  public setStep(step: number) {
    const duration = 2;
    const stepID = QUIZ_STEPS[step]?.id;

    if (
      QUIZ_STEPS_VARIANTS.results === stepID ||
      QUIZ_STEPS_VARIANTS["question-from"] === stepID
    ) {
      this.transitionOut();
    } else {
      this.transitionIn(0.6);
    }

    this.newColor = QUIZ_STEPS[step]?.heartColor;

    animate(this.currentColor, this.newColor, {
      duration,
      ease: cubicBezier(0.4, 0, 0.2, 1),
      onUpdate: (color) => {
        this.textureMesh?.material.uniforms.diffuseColor?.value.set(color);
      },
      onComplete: () => {
        this.currentColor = this.newColor;
      },
    });
  }

  public transitionIn(delay: number = 0) {
    animate(this.textureMesh.scale.x, this.openScale, {
      duration: 0.9,
      delay,
      ease: cubicBezier(0.35, 0.17, 0.3, 0.86),
      onUpdate: (scale: number) => {
        this.textureMesh!.scale.set(scale, scale, scale);
      },
    });
  }

  public transitionOut() {
    animate(this.textureMesh.scale.x, 0.001, {
      duration: 0.9,
      ease: cubicBezier(0.4, 0, 0.2, 1),
      onUpdate: (scale) => {
        this.textureMesh!.scale.set(scale, scale, scale);
      },
    });
  }

  private setupGui() {
    const pane = new Pane({ expanded: true, title: "Fine tunning console" });
    pane.element.parentElement!.style.display = "none";
    pane.element.parentElement!.style.fontSize = "10px";
    this.pane = pane;

    this.pane.addBinding(this.springSimulation, "springVelocity", {
      min: 0,
      max: 3,
      step: 0.001,
    });
    this.pane.addBinding(this.springSimulation, "springDamp", {
      min: 0,
      max: 2,
      step: 0.001,
    });
    this.pane.addBinding(this.springSimulation, "noiseStrengthFactor", {
      min: -0.08,
      max: 0.08,
      step: 0.0001,
    });
    this.pane.addBinding(this.springSimulation, "noiseZoom", {
      min: -5.0,
      max: 50.0,
      step: 0.0001,
    });
    this.pane.addBinding(this.springSimulation, "noiseNormalFactor", {
      min: -2.5,
      max: 2.5,
      step: 0.000001,
    });
    this.pane.addBinding(this.springSimulation, "noiseVelocity", {
      min: -2.5,
      max: 2.5,
      step: 0.000001,
    });
    this.pane.addBinding(this.springSimulation, "displacementRadius", {
      min: -2,
      max: 2,
      step: 0.000001,
    });
    this.pane.addBinding(this.springSimulation, "strengthFactor", {
      min: -0.5,
      max: 0.5,
      step: 0.0001,
    });

    this.pane.on("change", (ev: any) => {
      if (ev.presetKey === "bgColor") {
      }
    });

    window.addEventListener("dblclick", () => {
      if (this.toogledGui) {
        this.pane!.element.parentElement!.style.display = "block";
      } else {
        this.pane!.element.parentElement!.style.display = "none";
      }
      this.toogledGui = !this.toogledGui;
    });
  }
}

export default SpringScene;
