import { Group, Mesh, ShaderMaterial, VideoTexture } from "three";
import { SlideIdx, Slider } from "#/parts/helper/slider/Slider";
import lilGUI from "lil-gui";

type Uniforms = ShaderMaterial["uniforms"];

/**
 * 複数メッシュを使ったスライダの基底クラス
 * *継承して使用してください
 *
 * 複数のメッシュを保持するスライダはこちらのクラスを継承して作成すると実装の手間が省けます。
 *
 * 例）slider-world、slider-3dなど
 *
 * Base class for sliders using multiple meshes
 * *Please use by inheriting.
 *
 * Sliders that hold multiple meshes can be created by inheriting this class, which saves implementation effort.
 *
 * Examples: slider-world, slider-3d, etc.
 */
class MultiMeshSlider extends Slider {
  /**
   * activeSlideIdx を終点として少しずつ変更される値
   * A value that gradually changes towards the activeSlideIdx
   */
  easeSlideIdx: SlideIdx = 0;

  beforeCreateMesh() {
    this.easeSlideIdx = this.activeSlideIdx = 0;
    this.slides = []; // 複数のメッシュを格納する配列を準備. Prepare an array to store multiple meshes.
    this.playingVideo = null;
    this.slideSpeed = 0.07;
  }

  setupUniforms() {
    const uniforms = super.setupUniforms();
    uniforms.uSlideIdx = { value: 0 }; // 各毎にスライド番号 0, 1, 2, ... Slide number for each, e.g., 0, 1, 2, ...
    uniforms.uSlideTotal = { value: this.texes.size }; // スライド枚数. Number of slides.
    uniforms.uActiveSlideIdx = { value: this.activeSlideIdx };
    return uniforms;
  }

  setupMesh() {
    const groupMesh = new Group();
    let i = 0;
    // テクスチャ（Slider）の枚数だけメッシュを作成ていgroupMeshに追加
    // Create as many meshes as the number of textures (Sliders) and add them to groupMesh
    this.texes.forEach((tex) => {
      const mate = this.material.clone();
      if (mate instanceof ShaderMaterial) {
        mate.uniforms.tex1 = { value: tex };
        mate.uniforms.uTick = this.uniforms.uTick;
        mate.uniforms.uActiveSlideIdx = this.uniforms.uActiveSlideIdx;
        mate.uniforms.uDist = this.uniforms.uDist;
        mate.uniforms.uSlideIdx.value = i;
      }
      const _mesh = new Mesh(this.geometry, mate);
      groupMesh.add(_mesh);
      i++;
    });

    this.slides = [...groupMesh.children]; // 作成した個々のメッシュを配列にも追加. Add the created individual meshes to an array.

    return groupMesh;
  }

  goTo(idx: SlideIdx) {
    this.activeSlideIdx = idx;
    this.playVideo();
  }

  setTo(idx: SlideIdx) {
    this.activeSlideIdx = idx;
    this.easeSlideIdx = idx;
  }

  goNext() {
    this.goTo(this.activeSlideIdx + 1);
  }

  goPrev() {
    this.goTo(this.activeSlideIdx - 1);
  }

  setupTexes(uniforms: Uniforms) {
    return uniforms;
  }

  async playVideo() {
    const idx = this.activeSlideIdx % this.slides.length; // 整数. Integer
    const i = this.getModIdx(idx); // 0 ~ テクスチャの枚数の間の整数. Integer between 0 and the number of textures
    const slide = this.slides.at(i); // アクティブなメッシュを取得. Get the active mesh
    const tex1Value = slide.material.uniforms.tex1.value; // 動画テクスチャを取得. Get the video texture
    this.playingVideo?.pause(); // 動画要素であれば、その動画の再生を一時停止. If it is a video element, pause the video
    if (tex1Value instanceof VideoTexture) {
      this.playInterval = setInterval(() => {
        // 以下の条件が満たされるまで、200msおきに繰り返す
        // Repeat every 200ms until the following conditions are met
        if (i === idx) {
          this.playingVideo = tex1Value.source.data;
          this.playingVideo!.play?.();
          clearInterval(this.playInterval);
        }
      }, 200);
    }
  }

  debug(folder: lilGUI) {
    const sliderIdx = { value: 0 };
    folder
      .add(sliderIdx, "value", -50, 50, 1)
      .name("goTo")
      .listen()
      .onChange(() => {
        this.goTo(sliderIdx.value);
      });
  }

  /**
   * idx をスライド枚数の範囲内の数値で返却します
   * Returns a value within the range of the number of slides.
   * @param idx
   * @returns
   */
  getModIdx(idx: SlideIdx) {
    idx = idx ?? this.activeSlideIdx;
    return (
      ((idx % this.slides.length) + this.slides.length) % this.slides.length
    );
  }

  /**
   * クリックされた際の data-click-* の番号を返します。
   * Returns the number of data-click-* when clicked.
   * @param idx
   * @returns 実行したい data-click-* の番号( Number of data-click-* you want to execute.)
   */
  getClickIdx() {
    return this.slides.indexOf(this.hoveringMesh) + 1;
  }
}

export { MultiMeshSlider };
