import { CanvasProps } from "@tarojs/components";
import Taro from "@tarojs/taro";
import { ComponentType } from "react";
import {
  $cancelAnimationFrame,
  $requestAnimationFrame,
  $window,
  AmbientLight,
  Camera,
  Color,
  DirectionalLight,
  Group,
  LoadingManager,
  MeshStandardMaterial,
  PLATFORM,
  PerspectiveCamera,
  Raycaster,
  Scene,
  TextureLoader,
  Vector2,
  WebGL1Renderer,
} from "three-platformize";
import { FBXLoader } from "three-platformize/examples/jsm/loaders/FBXLoader";
import { BrowserPlatform } from "three-platformize/src/BrowserPlatform";
import { OrbitControls } from "three-platformize/examples/jsm/controls/OrbitControls";
import { observable } from "mobx";

export type FileLoaderMap = {
  fbx: FBXLoader;
};

export enum ROTATE_TYPE {
  DELAY,
  FALSE,
  TRUE,
}

export class RenderModel {
  canvas: ComponentType<CanvasProps>;
  platform: BrowserPlatform;
  disposing = false;

  @observable
  loading = false;

  frameId = 0;
  /**
   * 文件加载器类型
   */
  fileLoaderMap: FileLoaderMap;
  /**
   * 加载进度监听
   */
  loadingManager: LoadingManager;

  /**
   * 模型
   */
  @observable
  model?: Group;

  /**
   * 场景
   */
  scene: Scene;
  /**
   * 相机
   */
  camera: Camera;
  /**
   * 渲染器
   */
  renderer: WebGL1Renderer;
  /**
   * 控制器
   */
  controls: OrbitControls;
  /**
   * 模型材质列表
   * TODO: 类型后续完善
   */
  modelMaterialList: any[] = [];
  /**
   * 射线
   */
  raycaster: Raycaster;

  /**
   * 画布属性
   */
  @observable
  rect?: Taro.NodesRef.BoundingClientRectCallbackResult;

  /**
   * 自动旋转
   */
  @observable
  autoRotate = false;

  constructor() {
    this.loadingManager = new LoadingManager();

    this.fileLoaderMap = {
      fbx: new FBXLoader(this.loadingManager),
    };
    this.scene = new Scene();
    this.raycaster = new Raycaster();
  }

  async init(selector: string) {
    await this.initWebGLPlatform(selector);
    this.initScene();
    this.initCamera();
    this.initControls();
    this.initRender();
    this.initLight();
  }

  /**
   * 平台初始化
   */
  initWebGLPlatform = (selector: string) => {
    return new Promise((resolve) => {
      Taro.createSelectorQuery()
        .select(selector)
        .node(async (res) => {
          const _canvas = res.node as ComponentType<CanvasProps>;

          this.platform = new BrowserPlatform();
          PLATFORM.set(this.platform);

          this.canvas = _canvas;
        })
        .select(selector)
        .boundingClientRect((rect) => {
          this.rect = rect;
        })
        .exec(() => {
          resolve(true);
        });
    });
  };

  /**
   * 创建相机
   */
  initCamera() {
    if (!this.rect) return;

    this.camera = new PerspectiveCamera(
      25,
      this.rect.width / this.rect.height,
      1,
      1e4
    );
  }

  /**
   * 创建渲染器
   */
  initRender() {
    if (!this.rect) return;

    this.renderer = new WebGL1Renderer({
      // @ts-ignore
      canvas: this.canvas,
      antialias: true,
      alpha: true,
      preserveDrawingBuffer: true,
    });
    this.renderer.setSize(this.rect.width, this.rect.height);
    this.renderer.setPixelRatio($window.devicePixelRatio);

    const render = () => {
      if (!this.disposing) this.frameId = $requestAnimationFrame(render);
      this.controls.update();
      this.renderer.render(this.scene, this.camera);
    };
    render();
  }

  /**
   * 创建场景
   */
  initScene() {
    this.scene = new Scene();
    this.scene.add(new AmbientLight(0xffffff, 1.0));
    this.scene.add(new DirectionalLight(0xffffff, 1.0));
    this.scene.background = new Color(0x462422);
  }

  /**
   * 创建控制器
   */
  initControls() {
    // @ts-ignore
    this.controls = new OrbitControls(this.camera, this.canvas);
  }

  /**
   * 初始化灯光
   */
  initLight() {
    // 环境光
    const ambientLight = new AmbientLight(4473924, 1);
    // 方向光
    const directionalLight = new DirectionalLight(16777215, 0.5);
    const directionalLight2 = new DirectionalLight(16777215, 0.5);

    this.scene.add(ambientLight);
    directionalLight.position.set(-50, 25, 62.5);
    directionalLight2.position.set(50, 25, -62.5);
    this.scene.add(directionalLight);
    this.scene.add(directionalLight2);
  }

  /**
   * 加载模型
   */
  loadModel(params: { filePath: string; fileType: keyof FileLoaderMap }) {
    return new Promise((resolve, reject) => {
      try {
        this.loading = true;

        const loader = this.fileLoaderMap[params.fileType];

        loader.load(params.filePath, (res) => {
          switch (params.fileType) {
            case "fbx":
              this.model = res;
              this.scene.add(res);
              break;
          }

          this.getModelMaterialList();

          resolve(res);
        });
      } catch (err) {
        reject(err);
      } finally {
        this.loading = false;
      }
    });
  }

  /**
   * 获取当前模型材质
   */
  getModelMaterialList() {
    this.model?.traverse((v: any) => {
      // TODO: 小程序待确认
      if (v.isMesh && v.material) {
        v.castShadow = true;
        v.frustumCulled = false;
        // 标记初始位置
        v._position = v.position.clone();

        this.modelMaterialList.push(v);
      }
    });
  }

  /**
   * 设置贴图
   * @param meshStandardMaterial
   * @param url 贴图地址
   */
  setModelMap(meshStandardMaterial: MeshStandardMaterial, url: string) {
    const texture = new TextureLoader().load(url);
    meshStandardMaterial.map = texture;
    meshStandardMaterial.needsUpdate = true;
    texture.dispose();
  }

  /**
   * 设置金属度
   * @param meshStandardMaterial
   * @param url 贴图地址
   */
  setModelMetalness(meshStandardMaterial: MeshStandardMaterial, url: string) {
    const texture = new TextureLoader().load(url);
    meshStandardMaterial.metalnessMap = texture;
    meshStandardMaterial.needsUpdate = true;
    texture.dispose();
  }

  /**
   * 设置表面粗糙度
   * @param meshStandardMaterial
   * @param url 贴图地址
   */
  setModelRoughness(meshStandardMaterial: MeshStandardMaterial, url: string) {
    const texture = new TextureLoader().load(url);
    meshStandardMaterial.roughnessMap = texture;
    meshStandardMaterial.needsUpdate = true;
    texture.dispose();
  }

  /**
   * 设置发光贴图
   * @param meshStandardMaterial
   * @param url 贴图地址
   */
  setModelEmissive(meshStandardMaterial: MeshStandardMaterial, url: string) {
    const texture = new TextureLoader().load(url);
    meshStandardMaterial.emissiveMap = texture;
    meshStandardMaterial.needsUpdate = true;
    texture.dispose();
  }

  private autoRotateTimer: NodeJS.Timeout;
  /**
   * 设置自动旋转
   * @param type
   */
  setAutoRotate(type: ROTATE_TYPE) {
    clearTimeout(this.autoRotateTimer);

    switch (type) {
      case ROTATE_TYPE.DELAY:
        this.controls.autoRotate = false;

        if (this.autoRotate) {
          this.autoRotateTimer = setTimeout(() => {
            this.controls.autoRotate = true;
          }, 10000);
        }
        break;
      case ROTATE_TYPE.FALSE:
        this.autoRotate = false;
        this.controls.autoRotate = false;
        break;
      default:
        this.autoRotate = true;
        this.controls.autoRotate = true;
    }
  }

  /**
   * 设置控制器状态
   * @param enableZoom 是否可以放大
   * @param enableRotate 是否可以旋转
   * @param enablePan 是否可以平移
   */
  setControlsStatus(
    enableZoom: boolean,
    enableRotate: boolean,
    enablePan: boolean,
    enableRotateX?: boolean,
    enableRotateY?: boolean
  ) {
    this.controls.enablePan = enablePan;
    this.controls.enableZoom = enableZoom;
    this.controls.enableRotate = enableRotate;

    if (typeof enableRotateX === "boolean" && !enableRotateX) {
      const angle = this.controls.getAzimuthalAngle();
      this.controls.maxAzimuthAngle = angle;
      this.controls.minAzimuthAngle = angle;
    } else if (enableRotate) {
      this.controls.maxAzimuthAngle = Infinity;
      this.controls.minAzimuthAngle = -Infinity;
    }

    if (typeof enableRotateY === "boolean" && !enableRotateY) {
      const angle = this.controls.getPolarAngle();
      this.controls.minPolarAngle = angle;
      this.controls.maxPolarAngle = angle;
    } else if (enableRotate) {
      this.controls.minPolarAngle = 0;
      this.controls.maxPolarAngle = Math.PI / 2;
    }
  }

  mouseRaycaster(v2: Vector2) {
    this.raycaster.setFromCamera(v2, this.camera);
    return this.raycaster.intersectObjects(this.scene.children, true);
  }

  dispose() {
    this.disposing = true;
    $cancelAnimationFrame(this.frameId);
    this.platform?.dispose();
  }
}

export const renderModel = new RenderModel();
