import { useEffect, useMemo, useRef, useState } from "react";
import { Button, View } from "@tarojs/components";
import Taro, { FC } from "@tarojs/taro";
import { observer } from "mobx-react";
import {
  ROTATE_TYPE,
  renderModel,
  ShadowModel,
  ReticleModel,
  TagModelParams,
  TagModel,
} from "../../models";
import {
  Clock,
  GammaEncoding,
  LinearFilter,
  Mesh,
  MeshStandardMaterial,
  PointLight,
  Vector2,
  Vector3,
} from "three-platformize";
import { Easing, Tween } from "@tweenjs/tween.js";
import { cancelAnimationFrame, requestAnimationFrame } from "@tarojs/runtime";
import { CanvasAdapter } from "../../components";
import "./index.scss";

enum MODEL_STATE {
  DEFAULT = 0,
  ZOOM_UP = 1,
  ZOOM_UP_CLICK = 2,
}

interface TweenHandlerOptions<T extends object> {
  /** 当前属性 */
  curProps: T;
  /** 目标属性 */
  targetProps: T;
  /** 动画更新回调 */
  onUpdate?: (e: T) => void;
  /** 动画完成回调 */
  cb?: Function;
  /** 动画时长 默认 1000 ms */
  delay?: number;
}

const DEFUALT_SCALE = 20;
const DENGZHAO_MIN_ROTATE = -1.2;
const DENGZHAO_MAX_ROTATE = -2.55;
const system = Taro.getSystemInfoSync();

const IndexPage: FC = observer(() => {
  const clock = useRef(new Clock());
  const bulbLight = useRef<PointLight>();
  const cameraPosition = useRef(new Vector3(0, 100, 360));
  /**
   * 底部阴影
   */
  const shadowPlan = useRef<Mesh>();
  const [modelState, setModelState] = useState<MODEL_STATE>(
    MODEL_STATE.DEFAULT
  );
  /**
   * 是否分解
   */
  const isSeparate = useMemo(
    () => modelState === MODEL_STATE.ZOOM_UP,
    [modelState]
  );
  /**
   * 是否单独展示某个模型
   */
  const isSingleModel = useMemo(
    () => modelState === MODEL_STATE.ZOOM_UP_CLICK,
    [modelState]
  );
  const hotSeparateAnimArr = useRef<ReticleModel[]>([]);
  const tagArr = useRef<TagModel[]>([]);
  const startMouse = useRef(new Vector2(0, 0));
  const mouseV2 = useRef(new Vector2(0, 0));

  useEffect(() => {
    setTimeout(async () => {
      await init();

      animate();
    }, 100);

    return renderModel.dispose;
  }, []);

  const init = async () => {
    await renderModel.init("#wgl");

    // 创建点光源
    bulbLight.current = new PointLight(16772744, 2.5, 50, 2);
    bulbLight.current.position.set(1.45, -2.95, -1.17);
    bulbLight.current.castShadow = true;
    bulbLight.current.visible = false;

    renderModel.scene.add(bulbLight.current);
    renderModel.setControlsStatus(true, true, false);
    handleControlsAngle(true);

    renderModel.camera.position.copy(cameraPosition.current);

    await renderModel.loadModel({
      filePath:
        "https://houseoss.4dkankan.com/project/yzdyh-dadu/test/model.FBX",
      fileType: "fbx",
    });

    renderModel.modelMaterialList.forEach((mesh) => {
      const meshStandardMaterial = new MeshStandardMaterial();

      // 设置物体是否投射阴影
      mesh.castShadow = true;
      // 设置物体是否接收阴影
      mesh.receiveShadow = true;

      meshStandardMaterial.copy(mesh.material);
      // 设置材质透明度
      meshStandardMaterial.opacity = 0.01;
      // 告诉 three 库该材质渲染为半透明
      meshStandardMaterial.transparent = true;
      // 设置材质渲染面，2：双面渲染；1：只渲染正面；0：只渲染背面
      meshStandardMaterial.side = 2;

      switch (mesh.name) {
        case "dengzhao1":
        case "dengzhao2":
        case "dengpan":
          setModelMaterial(meshStandardMaterial, "dengzhao");
          break;
        case "dengguan":
          setModelMaterial(meshStandardMaterial, "dengguan");
          break;
        case "tongniudengdizuo":
          setModelMaterial(meshStandardMaterial, "tongniudengdizuo");
          break;
      }

      // 设置材质粗糙度
      meshStandardMaterial.roughness = 0.9;
      // 设置材质金属度
      meshStandardMaterial.metalness = 0.3;
      // 设置材质纹理过滤器
      meshStandardMaterial.map!.minFilter = LinearFilter;
      meshStandardMaterial.map!.magFilter = LinearFilter;
      // 设置材质纹理编码方式
      meshStandardMaterial.map!.encoding = GammaEncoding;

      mesh.material.dispose();
      mesh.material = meshStandardMaterial;
      mesh.material.needsUpdate = true;
    });

    setTimeout(() => {
      shadowPlaneShow();
      modelChildRotation(5);

      tweenHandler({
        curProps: {
          x: 0,
        },
        targetProps: {
          x: 20,
        },
        onUpdate: (e) => {
          renderModel.model?.scale.copy(new Vector3(e.x, e.x, e.x));
        },
        cb: () => {
          setTimeout(() => {
            renderModel.setAutoRotate(ROTATE_TYPE.TRUE);
          }, 800);
        },
      });
    }, 300);
  };

  const setModelMaterial = (
    meshStandardMaterial: MeshStandardMaterial,
    name: string
  ) => {
    renderModel.setModelMap(
      meshStandardMaterial,
      require(`./resource/${name}_D.jpg`)
    );
    renderModel.setModelRoughness(
      meshStandardMaterial,
      require(`./resource/${name}_S.jpg`)
    );
    renderModel.setModelMetalness(
      meshStandardMaterial,
      require(`./resource/${name}_R.jpg`)
    );
    renderModel.setModelEmissive(
      meshStandardMaterial,
      require(`./resource/${name}_D.png`)
    );
  };

  /**
   * 相机动画
   */
  function tweenHandler<T extends object>(options: TweenHandlerOptions<T>) {
    let animaId: number | NodeJS.Timeout = 0;
    const { curProps, targetProps, onUpdate, cb } = options;

    const tween = new Tween(curProps)
      .to(targetProps, options.delay || 1000)
      .onUpdate(onUpdate)
      .onComplete(() => {
        cancelAnimationFrame(animaId as number);
        cb && cb();
      })
      .easing(Easing.Quintic.Out)
      .start();

    function animate(time?: number) {
      animaId = requestAnimationFrame(animate);
      tween.update(time);
    }

    animate();
  }

  const modelChildRotation = (e: number) => {
    const dengzhao1Mesh = renderModel.model?.getObjectByName("dengzhao1");

    if (dengzhao1Mesh) {
      dengzhao1Mesh.rotation.z =
        DENGZHAO_MIN_ROTATE +
        (DENGZHAO_MAX_ROTATE - DENGZHAO_MIN_ROTATE) * (e - 1) * 0.1;

      renderModel.modelMaterialList.forEach((mesh) => {
        if (e > 5) {
          const t = 1 - 10 * (e - 8) * (1 / 30);
          if (e >= 8) {
            mesh.material.opacity = t;
          }

          mesh.visible = !(t <= 0.01);
        } else {
          mesh.material.opacity = 1;
          mesh.visible = true;
        }
      });
    }
  };

  /**
   * 是否展示底部阴影
   */
  const shadowPlaneShow = (show = true) => {
    if (!shadowPlan.current && renderModel.model) {
      shadowPlan.current = new ShadowModel(
        "https://houseoss.4dkankan.com/project/yzdyh-dadu/test/shadow.png",
        1024,
        1024,
        { x: Math.PI / 2, y: Math.PI / 80, z: -Math.PI / 2 },
        new Vector3(0, -2.35, 0),
        0.0055,
        renderModel.model
      ).mesh;
    }
    shadowPlan.current!.visible = show;
  };

  /**
   * 拆解/合并模型
   */
  const handleSeparate = () => {
    handleControlsAngle(isSeparate);
    renderModel.setAutoRotate(ROTATE_TYPE.FALSE);
    renderModel.setControlsStatus(false, false, false);

    if (!isSeparate) {
      handleControlsDistance(320);
      shadowPlaneShow(false);

      const pos = renderModel.camera.position.clone();
      const lookAt = renderModel.controls.target.clone();
      const targetPos = new Vector3(0, 0, 320);
      const targetLookAt = new Vector3(0, 0, 0);

      tweenHandler({
        curProps: {
          x1: pos.x,
          y1: pos.y,
          z1: pos.z,
          x2: lookAt.x,
          y2: lookAt.y,
          z2: lookAt.z,
        },
        targetProps: {
          x1: targetPos.x,
          y1: targetPos.y,
          z1: targetPos.z,
          x2: targetLookAt.x,
          y2: targetLookAt.y,
          z2: targetLookAt.z,
        },
        onUpdate: (e) => {
          renderModel.camera.position.set(e.x1, e.y1, e.z1);
          renderModel.controls.target.set(e.x2, e.y2, e.z2);
          renderModel.controls.update();
        },
        cb: () => {
          handleModelSeparateAnimation(true, () => {
            renderModel.setControlsStatus(true, true, false, true, false);
            renderModel.setAutoRotate(ROTATE_TYPE.TRUE);
            setModelState(MODEL_STATE.ZOOM_UP);
          });
        },
      });
    } else {
      handleModelSeparateAnimation(false, () => {
        revertModel(() => {
          renderModel.setControlsStatus(true, true, false);
          renderModel.setAutoRotate(ROTATE_TYPE.TRUE);
          shadowPlaneShow(true);
          setModelState(MODEL_STATE.DEFAULT);
        });
      });
    }
  };

  /**
   * 恢复模型
   */
  const revertModel = (cb?: Function) => {
    const pos = renderModel.camera.position.clone();
    const lookAt = renderModel.controls.target.clone();
    const targetPos = new Vector3(0, 30, 372);
    const targetLookAt = new Vector3(0, 0, 0);

    tweenHandler({
      curProps: {
        x1: pos.x,
        y1: pos.y,
        z1: pos.z,
        x2: lookAt.x,
        y2: lookAt.y,
        z2: lookAt.z,
      },
      targetProps: {
        x1: targetPos.x,
        y1: targetPos.y,
        z1: targetPos.z,
        x2: targetLookAt.x,
        y2: targetLookAt.y,
        z2: targetLookAt.z,
      },
      onUpdate: (e) => {
        renderModel.camera.position.set(e.x1, e.y1, e.z1);
        renderModel.controls.target.set(e.x2, e.y2, e.z2);
        renderModel.controls.update();
      },
      cb,
    });
  };

  const handleControlsDistance = (dis: number, maxDis = 470) => {
    renderModel.controls.minDistance = dis;
    renderModel.controls.maxDistance = maxDis;
  };

  /**
   * 修改控制器垂直最大角度
   */
  const handleControlsAngle = (type: boolean) => {
    renderModel.controls.maxPolarAngle = type ? 0.59 * Math.PI : Math.PI;
  };

  /**
   * 模型分离动画
   * @param type 是否分离
   */
  const handleModelSeparateAnimation = (type: boolean, cb?: Function) => {
    let loadCount = 0;

    renderModel.modelMaterialList.forEach((item) => {
      if (type) {
        const pos = item.position.clone();
        switch (item.name) {
          case "dengguan":
            pos.add(new Vector3(0, 1, 0));
            break;
          case "dengpan":
            pos.add(new Vector3(0, -0.5, 0));
            break;
          case "dengzhao1":
            pos.add(new Vector3(-0.3, 0.2, 0));
            break;
          case "dengzhao2":
            pos.add(new Vector3(0.3, 0.2, 0));
            break;
          case "tongniudengdizuo":
            pos.add(new Vector3(0, -1, 0));
        }

        tweenHandler({
          curProps: item.position,
          targetProps: pos,
          cb: () => {
            loadCount++;

            if (loadCount === 5) {
              initTag(true);
              initReticleMeshs(true);
              cb && cb();
            }
          },
        });
      } else {
        initTag(false);
        initReticleMeshs(false);
        tweenHandler({
          curProps: item.position,
          targetProps: item._position,
          cb,
        });
      }
    });
  };

  const initTag = (visible: boolean) => {
    if (!tagArr.current.length) {
      const temp: TagModel[] = [];
      const tagStack: TagModelParams[] = [
        {
          url: require("./resource/daoyanguan.png"),
          maskUrl: require("./resource/biaoqian_left.png"),
          width: 256,
          height: 36,
          position: new Vector3(1.1, 3.1, 0),
          scale: 0.007,
          rotation: { x: 0, y: 0, z: 0 },
        },
        {
          url: require("./resource/denggai.png"),
          maskUrl: require("./resource/biaoqian_left.png"),
          width: 256,
          height: 36,
          position: new Vector3(1.7, 2.4, 0),
          scale: 0.005,
          rotation: { x: 0, y: 0, z: 0 },
        },
      ];

      tagStack.forEach((tag) => {
        const tagIns = new TagModel(tag);

        temp.push(tagIns);
        renderModel.model?.add(tagIns.mesh);
      });

      tagArr.current.push(...temp);
    }

    tagArr.current.forEach((tag) => {
      visible ? tag.show() : tag.hide();
    });
  };

  const initReticleMeshs = (visible: boolean) => {
    if (!hotSeparateAnimArr.current.length) {
      const temp: ReticleModel[] = [];
      const vectorStack = [
        new Vector3(0, 2.7, 0.65),
        new Vector3(0, 0.9, 0.65),
        new Vector3(0, -0.7, 0.65),
        new Vector3(0, -2, 0.65),
      ];

      vectorStack.forEach((vector) => {
        const reticle = new ReticleModel({
          url: require("./resource/reticle-animation.png"),
          planeWidth: 150,
          tilesHorizontal: 50,
          tilesVertical: 1,
          tileDisplayDuration: 50,
          scale: 0.0022,
          position: vector,
          camera: renderModel.camera,
        });
        renderModel.model?.add(reticle.reticleAnim);
        temp.push(reticle);
      });

      hotSeparateAnimArr.current.push(...temp);
    }

    hotSeparateAnimArr.current.forEach((item) => {
      visible ? item.show() : item.hide();
    });
  };

  const animate = () => {
    const time = clock.current.getDelta();

    hotSeparateAnimArr.current.forEach((item) => {
      item.update(time);
    });

    requestAnimationFrame(animate);
  };

  const clickHandler = (x: number, y: number) => {
    const v2 = new Vector2(x, y);

    // 比较两个向量是否相等
    if (v2.equals(startMouse.current)) {
      setCanvasPosition(x, y);

      const res = renderModel.mouseRaycaster(mouseV2.current);
      for (let i = 0; i < res.length; i++) {
        const name = res[i].object.name;

        if (!name) continue;

        console.log("click model:", name);

        if (
          isSeparate &&
          res[i].object.visible &&
          res[i].object.parent?.visible
        ) {
          handleModelZoomUp(name);
          break;
        }
      }
    }
  };

  /**
   * 单独展示某个模型
   */
  const handleModelZoomUp = (name: string) => {
    renderModel.setAutoRotate(ROTATE_TYPE.FALSE);
    modelSingleClick(name, () => {
      renderModel.setControlsStatus(false, true, false, true, false);
      setModelState(MODEL_STATE.ZOOM_UP_CLICK);
    });
  };

  const modelSingleClick = (name: string, cb: Function) => {
    let targetProps = {
      x: 0,
      y: 0,
      z: 0,
      scale: DEFUALT_SCALE,
    };

    switch (name) {
      case "dengguan":
        targetProps = {
          x: 0,
          y: -68,
          z: 0,
          scale: 0.6 * DEFUALT_SCALE,
        };
        break;
    }

    if (name) {
      renderModel.modelMaterialList.forEach((model) => {
        model.visible = model.name.includes(name);
      });
    }

    initTag(false);
    initReticleMeshs(false);
    tweenHandler({
      curProps: {
        x: renderModel.model!.position.x,
        y: renderModel.model!.position.y,
        z: renderModel.model!.position.z,
        scale: renderModel.model!.scale.x,
      },
      targetProps,
      onUpdate: (e) => {
        renderModel.model?.position.set(e.x, e.y, e.z);
        renderModel.model?.scale.set(e.scale, e.scale, e.scale);
      },
      cb: () => {
        if (name === "") {
          renderModel.modelMaterialList.forEach((model) => {
            model.visible = true;
          });
          initTag(true);
          initReticleMeshs(true);
        }

        cb && cb();
      },
    });
  };

  const setCanvasPosition = (x: number, y: number) => {
    mouseV2.current.x = (x / system.windowWidth) * 2 - 1;
    mouseV2.current.y = (-y / system.windowHeight) * 2 + 1;
  };

  /**
   * 返回分解模型
   */
  const backForSeparate = () => {
    modelSingleClick("", () => {
      renderModel.setControlsStatus(true, true, false, true, false);
      setModelState(MODEL_STATE.ZOOM_UP);
    });
  };

  return (
    <View className="page">
      <CanvasAdapter
        onTouchStart={(e) => {
          // @ts-ignore
          startMouse.current = new Vector2(e.clientX, e.clientY);
          renderModel.setAutoRotate(ROTATE_TYPE.DELAY);
        }}
        onTouchEnd={(e) => {
          // @ts-ignore
          clickHandler(e.clientX, e.clientY);
        }}
      />

      <View className="toolbar">
        {isSingleModel ? (
          <Button className="btn" onClick={backForSeparate}>
            返回
          </Button>
        ) : (
          <Button className="btn" onClick={handleSeparate}>
            {!isSeparate ? "拆解" : "合并"}
          </Button>
        )}
      </View>
    </View>
  );
});

export default IndexPage;
