import {
  DoubleSide,
  Mesh,
  PlaneGeometry,
  ShaderMaterial,
  TextureLoader,
  Vector3,
} from "three-platformize";

export interface TagModelParams {
  url: string;
  maskUrl: string;
  width: number;
  height: number;
  position: Vector3;
  scale: number;
  rotation: { x: number; y: number; z: number };
}

export class TagModel {
  private showTime: NodeJS.Timeout;
  private hideTime: NodeJS.Timeout;

  mesh: Mesh;
  progress = 0;

  constructor(params: TagModelParams) {
    const planeGeometry = new PlaneGeometry(
      params.width,
      params.height,
      20,
      20
    );
    const shaderMaterial = new ShaderMaterial({
      uniforms: {
        uTexture: { value: new TextureLoader().load(params.url) },
        uMaskTexture: { value: new TextureLoader().load(params.maskUrl) },
        uProgress: { value: 0 },
      },
      transparent: true,
      vertexShader: `
        varying vec2 vUv;
        void main() {
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
      fragmentShader: `
        varying vec2 vUv;
        uniform sampler2D uTexture;
        uniform sampler2D uMaskTexture;
        uniform float uProgress;
        void main() {
          vec4 tex = texture2D(uTexture, vUv);
          vec4 maskTex = texture2D(uMaskTexture, vUv);
          float alpha = clamp(maskTex.r - (1.0 - uProgress), 0.0, 1.0);
          alpha = smoothstep(0.0, 0.0001, alpha);
          vec4 color = tex;
          color.a *= alpha;
          gl_FragColor = color;
        }
      `,
      side: DoubleSide,
      opacity: 1,
    });

    this.mesh = new Mesh(planeGeometry, shaderMaterial);
    this.mesh.rotation.set(
      params.rotation.x,
      params.rotation.y,
      params.rotation.z
    );
    this.mesh.position.copy(params.position);
    this.mesh.scale.set(params.scale, params.scale, params.scale);

    this.show();
  }

  show() {
    this.clearTimer();

    this.showTime = setInterval(() => {
      const material = this.mesh.material as any;

      this.progress += 0.05;
      material.uniforms.uProgress.value = this.progress;

      if (this.progress >= 1) {
        this.progress = 1;
        clearInterval(this.showTime);
      }
    }, 20);
  }

  hide() {
    this.clearTimer();

    this.hideTime = setInterval(() => {
      const material = this.mesh.material as any;

      this.progress -= 0.05;
      material.uniforms.uProgress.value = this.progress;

      if (this.progress <= 0) {
        this.progress = 0;
        clearInterval(this.hideTime);
      }
    }, 20);
  }

  clearTimer() {
    if (this.hideTime) {
      clearInterval(this.hideTime);
    }

    if (this.showTime) {
      clearInterval(this.showTime);
    }
  }
}
