import {
  MOUSE,
  Vector2,
  Vector3,
  Vector4,
  Quaternion,
  Matrix4,
  Spherical,
  Box3,
  Sphere,
  Raycaster,
  MathUtils,

  // ---
  HemisphereLight,
  Clock,
  OrthographicCamera,
  PerspectiveCamera,
  Scene,
  WebGLRenderer,
  Object3D
} from 'three';
import elementResizeDetectorMaker from 'element-resize-detector';
import CameraControls from 'camera-controls';
import debounce from 'lodash/debounce';
// @ts-ignore
import TWEEN from 'tween.js';
import MyCameraControls from './myCameraControls';
import { getIntersects, getObjectsByName, getWorldPosition } from '../utils';
import createStats from './createStats';
import Stats from 'stats.js';
import { Events, IOptions, IStyle, IViewMode } from './index.type';
import mitt, { Handler, Emitter } from 'mitt';
import { PoisLayer } from '../poisLayer';
import ParkingLayer from '../parkingLayer';
import { IControl } from '../controls/index.type';
import { getCurrentLevelObj, getDollys } from '../parkingLayer/utils';
import { OBJECT_NAME } from '../parkingLayer/index.type';

const subsetOfTHREE = {
  MOUSE: MOUSE,
  Vector2: Vector2,
  Vector3: Vector3,
  Vector4: Vector4,
  Quaternion: Quaternion,
  Matrix4: Matrix4,
  Spherical: Spherical,
  Box3: Box3,
  Sphere: Sphere,
  Raycaster: Raycaster,
  MathUtils: {
    DEG2RAD: MathUtils.DEG2RAD,
    clamp: MathUtils.clamp
  }
};

export default class _Map {
  options: IOptions;
  private stats?: Stats;
  scene: Scene;
  camera: PerspectiveCamera;
  $map: HTMLElement;
  renderer: WebGLRenderer;

  private updateSizeMap: Map<any, () => void> = new Map();
  private emitter: Emitter<Events> = mitt<Events>();
  private timer: any = 0;
  private viewChanged = false;

  cameraControls: MyCameraControls;

  constructor(
    public container: string,
    { style = 'normal', showStats = false, viewMode = '3D' }: IOptions = {}
  ) {
    this.options = { style, showStats, viewMode };
    const { scene, camera, cameraControls, $map, renderer } = this.init();
    this.scene = scene;
    this.camera = camera;
    this.cameraControls = cameraControls;
    this.$map = $map;
    this.renderer = renderer;

    // 配置相机相关的参数
    this.reset();

    this.setShowStats(showStats);
    // this.marker.updateSize();
  }

  getScene() {
    return this.scene;
  }

  /**
   * @desc 二封 scene.add
   * to do：
   * - 需要判断新增的 layer 是否重名，判断 name 属性
   */
  add(...object: Object3D[] | PoisLayer[] | ParkingLayer[]) {
    const { camera, $map, scene, cameraControls, reDraw } = this;

    let obj = object[0];
    // https://blog.csdn.net/sujun10/article/details/64924125
    // switch (obj?.constructor?.name) {
    // @ts-ignore
    switch (obj?.MY_NAME) {
      case 'PoisLayer':
        obj = obj as PoisLayer;
        // 判断如果当前如果添加的是一个 marker 就需要添加到一个数组，后续会根据这个数组循环 updateSize
        // 注入需要的参数
        obj._inject(this);
        obj._init();
        this.updateSizeMap.set(obj, obj._updateSize.bind(obj));
        console.log('add-this.updateSizeMap :>> ', this.updateSizeMap);
        break;

      case 'ParkingLayer':
        obj = obj as ParkingLayer;
        // 判断如果当前如果添加的是一个 marker 就需要添加到一个数组，后续会根据这个数组循环 updateSize
        obj._inject(this);
        obj._init();
        this.updateSizeMap.set(obj, obj._updateSize.bind(obj));
        console.log('add-this.updateSizeMap :>> ', this.updateSizeMap);
        break;

      default:
        // @ts-ignore
        this.scene.add(...object);
        break;
    }
    this.reDraw();
    console.log('add-current-scene :>> ', this.scene);
    return this;
  }

  remove(...object: Object3D[] | PoisLayer[]) {
    let obj = object[0];
    // switch (obj?.constructor?.name) {
    // @ts-ignore
    switch (obj?.MY_NAME) {
      case 'PoisLayer':
        obj = obj as PoisLayer;
        // 判断如果当前如果添加的是一个 marker 就需要添加到一个数组，后续会根据这个数组循环 updateSize
        this.updateSizeMap.delete(obj);
        console.log('delete-this.updateSizeMap :>> ', this.updateSizeMap);
        this.scene.remove(obj._group);
        break;

      default:
        // @ts-ignore
        this.scene.remove(...object);
        break;
    }
    this.reDraw();
    console.log('add-current-scene :>> ', this.scene);
    return this;
  }

  on<Key extends keyof Events>(
    type: Key,
    handler: Handler<Events[Key]>,
    ext?: Record<string, any>
  ) {
    // mitt 监听
    this.emitter.on(type, handler);

    switch (type) {
      case 'click': {
        // 做长按兼容
        document.onmousedown = ev => {
          // 右键可拖拽画布，保持 controls 原来功能
          if (ev.which === 3) return;
          const time = +new Date();
          document.onmouseup = () => {
            const newTime = +new Date();
            if (newTime - time > 300) {
            } else {
              const { scene, camera, $map } = this;
              const obj = getWorldPosition({
                event: ev,
                scene,
                camera,
                $map,
                objects: ext?.objects?.()
              });
              this.emitter.emit('click', Object.assign(ev, obj));
            }
          };
        };
        // document.getElementById(this.container)!.onclick = ev => {
        //   const { scene, camera, $map } = this;
        //   const worldPosition = getWorldPosition({
        //     event: ev,
        //     scene,
        //     camera,
        //     $map,
        //     objects: ext?.objects
        //   });
        //   this.emitter.emit('click', Object.assign(ev, { worldPosition }));
        // };
        break;
      }
      default:
        break;
    }

    return this;
  }

  off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>) {
    switch (type) {
      case 'click':
        document.getElementById(this.container)!.onclick = null;
        break;
      default:
        break;
    }

    this.emitter.off(type, handler);
    return this;
  }

  /**
   * @desc 重渲染，为了提高性能，往场景添加元素，需要调用 redraw
   * 箭头函数，保证 this 指向正确
   */
  reDraw = (time = 3000) => {
    this.viewChanged = true;
    clearTimeout(this.timer);
    this.timer = setTimeout(() => {
      this.viewChanged = false;
    }, time);
  };

  /**
   * @desc 初始化地图：场景，相机，灯光，渲染器，rendererResize，轨道控制器
   */
  private init() {
    const $map = document.getElementById(this.container)!;
    // #region 场景
    const scene = new Scene();
    // #endregion

    // #region 相机
    const width = $map.clientWidth;
    const height = $map.clientHeight;
    const k = width / height;

    /**
     * 后面两个参数的概念，就像人的眼睛
     * 第三个参数想象成距离人的眼睛，多近才会看不到，比如 0.1，表示小于0.1，人才会看不到
     * 第四个参数想象成距离人的眼睛，多远才会看不到，比如 10000，表示大于10000，人才会看不到
     */
    // const camera = new THREE.PerspectiveCamera(45, k, 0.01, 83150);

    // 最后一个参数是要进行判断，还是说直接设置一个很大的值？？
    // const camera = new THREE.PerspectiveCamera(45, k, 0.01, 1000000);
    // const camera = new PerspectiveCamera(45, k, 0.01, Math.pow(10, 100));
    const camera = new PerspectiveCamera(45, k, 0.01, Math.pow(10, 100));
    // const camera = new PerspectiveCamera(45, k, 0.01, Math.pow(10, 100));
    // const camera = new PerspectiveCamera(45, k, Math.pow(0.01, 100), 100);
    // const camera = new THREE.PerspectiveCamera(45, k, 0.01, 2000);

    // 把z轴向上
    camera.up.x = 0;
    camera.up.y = 0;
    camera.up.z = 1;

    // 设置相机方向(指向的场景对象)
    // camera.position.set(0, -736903.9005224737, 425451.6653335355)
    camera.lookAt(scene.position);
    // #endregion

    // #region 灯光
    const light = new HemisphereLight('#ffffff', '#e7e7e7', 1.0);
    light.name = 'light';
    light.position.set(-100, -100, 0);
    scene.add(light);
    // #endregion

    // #region 渲染器
    const renderer = new WebGLRenderer({
      // 抗锯齿，开启来会比较卡，按情况开启
      antialias: true,
      alpha: true,
      logarithmicDepthBuffer: true
    });
    renderer.setSize($map.clientWidth, $map.clientHeight);
    // 设置背景颜色
    renderer.setClearColor('#e7f2fe', 1);
    // renderer.setClearColor(0xd3df56, 1) // 设置背景颜色

    // 暗黑模式
    // renderer.setClearColor(THEME.dark.renderer.color, 1); //设置背景颜色
    renderer.setPixelRatio(window.devicePixelRatio);
    $map.appendChild(renderer.domElement);

    renderer.render(scene, camera);
    // #endregion

    // #region rendererResize
    const onWindowResize = debounce(() => {
      const width = $map.clientWidth;
      const height = $map.clientHeight;
      camera.aspect = width / height;
      camera.updateProjectionMatrix();
      renderer.setSize(width, height);
      this.reDraw();
    }, 100);

    const observer = elementResizeDetectorMaker();
    observer.listenTo($map, onWindowResize);
    // #endregion

    // #region 相机控制器
    const cameraControls = this.createCameraControls(
      scene,
      camera,
      renderer,
      () => {
        for (const [, value] of this.updateSizeMap) {
          value?.();
        }
      }
    );
    // #endregion

    // 地图渲染完毕
    // emitter.emit('complete')

    return { scene, camera, cameraControls, $map, renderer };
  }

  /**
   * @desc 相机控制器
   */
  private createCameraControls(
    scene: Scene,
    camera: PerspectiveCamera | OrthographicCamera,
    renderer: WebGLRenderer,
    updateFn: () => void
  ) {
    MyCameraControls.install({ THREE: subsetOfTHREE });

    const clock = new Clock();
    const controls = new MyCameraControls(camera, renderer.domElement);

    const _this = this;
    (function anim() {
      _this.stats?.begin();
      // snip
      const delta = clock.getDelta();
      const hasControlsUpdated = controls.update(delta);
      TWEEN.update();
      // you can skip this condition to render though

      // 不再把 viewChanged 放置 CameraControls 作为静态属性，这样子不能保证多例，因为静态属性会共享一个副本
      // if (hasControlsUpdated || CameraControls.viewChanged) {
      if (hasControlsUpdated || _this.viewChanged) {
        renderer.render(scene, camera);
        // 一些会受到相机改变影响的方法
        updateFn();
      }
      _this.stats?.end();
      requestAnimationFrame(anim);
    })();

    renderer.render(scene, camera);

    return controls;
  }

  /**
   * @desc 恢复到相机的初始状态
   */
  reset() {
    const { cameraControls, camera } = this;

    // 休息阈值，默认 0.0025，调整会影响到 await 的过渡时长
    // controls.restThreshold = 0.0025;
    // controls.restThreshold = 0.01;
    // controls.restThreshold = 10;

    // 阻尼惯性，默认 0.05，调整会影响到 await 的过渡时长
    cameraControls.dampingFactor = 0.05 * 2;
    // 拖动时的阻尼惯性，默认 0.25
    cameraControls.draggingDampingFactor = 0.25 * 2;

    // controls.azimuthRotateSpeed = 1 * 2;
    // controls.polarRotateSpeed = 1 * 2;
    // controls.dollySpeed = 1 * 1.5;
    cameraControls.dollySpeed = 1 * 1.5;

    // 如果为true，表示前后（远近）
    cameraControls.verticalDragToForward = true;

    // 上下
    cameraControls.minPolarAngle = 0;
    cameraControls.maxPolarAngle = Math.PI;

    // dolly
    cameraControls.minDistance = 0;
    cameraControls.maxDistance = Infinity;

    // 左右
    cameraControls.minAzimuthAngle = -Infinity;
    cameraControls.maxAzimuthAngle = Infinity;

    // 改变相机 fov
    camera.fov = 45;
    camera.updateProjectionMatrix();

    return this;
  }

  destroy() {}

  /**
   * @desc 添加地图上的控件，如二三维切换器，缩放器等
   */
  addControl(control: IControl) {
    control
      ._inject(this)
      // 初始化挂载
      ._init();

    return this;
  }

  /**
   * @desc 添加地图上的辅助对象，如灯光线条或者
   */
  addHelper() {
    return this;
  }

  // #region 一些控制地图的方法

  // 皮肤变化
  setStyle(style: keyof IStyle) {
    // this.options.style = style
    return this;
  }

  setShowStats(showStats: boolean) {
    if (showStats) {
      this.stats = createStats(this.container);
      this.reDraw();
    }
    return this;
  }

  // 维度变化
  setViewMode(viewMode: IViewMode) {
    this.options.viewMode = viewMode;
    viewMode === '3D' ? this.fitToBox3d() : this.fitToBox2d();
    return this;
  }

  // mesh 获取当前楼层为基准
  fitToBox2d() {
    const _getTarget = () => {
      const allLevelObjs = getObjectsByName(OBJECT_NAME.level, this.scene);
      // 最高楼层
      return allLevelObjs[allLevelObjs.length - 1];
    };
    // const levelObj = getCurrentLevelObj(unref(currentLotInfo).zIndex)

    const target = _getTarget();
    const { fitDistance, dollys } = getDollys(target, this.cameraControls);

    const enableTransition = true;
    // 控制水平垂直旋转
    this.cameraControls.minPolarAngle =
      this.cameraControls.maxPolarAngle =
      this.cameraControls.minAzimuthAngle =
      this.cameraControls.maxAzimuthAngle =
        0;

    // @ts-ignore
    this.cameraControls.dollyTo(fitDistance, enableTransition);
    this.cameraControls.rotateTo(0, 0, enableTransition);
    // moveTo 到合适的位置
    // this.cameraControls.moveTo(0, 0, levelObj?.position.z!, true)
    this.cameraControls.moveTo(0, 0, 0, true);
  }

  fitToBox3d() {
    // 开启只能上下移动的模式
    // this.cameraControls.truckYDisabled = true
    // this.cameraControls.verticalDragToForward = false
    const _getTarget = () => {
      const allLevelObjs = getObjectsByName(OBJECT_NAME.level, this.scene);
      // 最高楼层
      return allLevelObjs[allLevelObjs.length - 1];
    };
    const enableTransition = true;

    // [多层] to do... 判断以面积最小的楼层来设置 dollys
    // if (stateLevelSwitcher.mode === 'multi') {
    //   // 上下旋转范围
    //   global.controls.minPolarAngle = Math.PI * (60 / 180)
    //   global.controls.maxPolarAngle = Math.PI * (60 / 180)
    // } else {
    //   // 上下旋转范围
    //   global.controls.minPolarAngle = 0
    //   global.controls.maxPolarAngle = Math.PI * (60 / 180)
    // }
    // 控制上下旋转
    // this.cameraControls.maxPolarAngle = Math.PI * (60 / 180); // radians
    // 左右
    this.cameraControls.minAzimuthAngle = -Infinity;
    this.cameraControls.maxAzimuthAngle = Infinity;
    const target = _getTarget();
    const { fitDistance, dollys } = getDollys(target, this.cameraControls);
    if (fitDistance) {
      this.cameraControls.dollyTo(fitDistance, enableTransition);
    }
    this.cameraControls.rotateTo(0, Math.PI * (60 / 180), enableTransition);
    this.cameraControls.moveTo(0, 0, 0, true);
  }
}
