import CameraControls from 'camera-controls';
import * as holdEvent from 'hold-event';
import {
  Box3,
  Box3Helper,
  BufferGeometry,
  EdgesGeometry,
  Group,
  Line,
  LineBasicMaterial,
  LineSegments,
  MathUtils,
  Mesh,
  MeshBasicMaterial,
  Object3D,
  Vector2,
  Vector3
} from 'three';
import {
  CLS_PREFIX,
  getAngle,
  getObjectsByName,
  getPointBetweenPoints
} from '../utils';
import { IOptions } from './index.type';

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
// @ts-ignore
import TWEEN from 'tween.js';

import MODEL_ARROW from './assets/model/arrow/scene.gltf';
import MODEL_ARROW_V2 from './assets/model/arrow_v2/scene.gltf';
import MODEL_ARROW_2 from './assets/model/arrow_2/scene.gltf';
import chevron from './assets/model/chevron/scene.gltf';

import AStar from '../aStar';
import { myTruncate, standardKeyPointsChanger } from '../aStar/utils';
import test from './assets/imgs/test.jpeg';
import IC_ARROW from './assets/imgs/ic_arrow.png';
export default class AR {
  private options: IOptions;
  private _nextTick: Array<(res: any) => void> = [];
  private aStar!: AStar;
  private arrowGroupCache!: Object3D;
  private beginCache: number[] = [];
  private endCache: number[] = [];
  private trackPointsCache: any[] = [];
  constructor({ map, data }: IOptions) {
    console.log('test-img :>> ', test);
    this.options = { map, data };
    this.init();
  }

  // constructor({ name = '', data, poiCollision = true }: IOptions) {
  //   super();
  //   this.options = { name, data, poiCollision };
  // }

  nextTick(cb?: (path: any[]) => void) {
    if (cb) {
      this._nextTick.push(cb);
      return undefined;
    } else {
      return new Promise<any[]>(resolve => this._nextTick.push(resolve));
    }
  }

  private async init() {
    // - 设置透明，为了 AR 效果
    const {
      map,
      map: { cameraControls, scene, $map, renderer, reDraw, container }
    } = this.options;
    // renderer.setClearAlpha(0);
    // - 获取数据绘制基础路网
    this.drawRoadNet();

    const path = await (this.nextTick() as Promise<any[]>);
    this.aStar = new AStar(path);

    // - 隐藏所有物体，除了箭头
    // console.log('scene :>> ', scene);
    // for (const item of scene.children) {
    //   if (['level', 'ar_track'].includes(item.name)) {
    //     item.visible = false;
    //   }
    // }

    // - 调用h5相机
    this.callCamera();

    // - 切换第一人称视角
    // this.changeFirstPerson();

    // - 监听手机罗盘，目前用小程序的 hash 方式
    this.deviceOrientationHandler();

    // - 监听 hash 变化，和地图进行交互
    this.onHashChange(query => {
      // 改变画布的位置
      const { direction } = query;

      const $compass = this.getCompass();
      $compass.innerText = `罗盘角度：${(+direction).toFixed(2)}`;

      // cameraControls.normalizeRotations();
      // cameraControls.rotateAzimuthTo(Math.PI * (+direction / 180), true);
      // cameraControls.rotateTo(
      //   Math.PI * ((360 - direction) / 180),
      //   Math.PI * (90 / 180),
      //   true
      // );

      // cameraControls.rotateTo(
      //   Math.PI * ((360 - direction) / 180),
      //   Math.PI * (90 / 180),
      //   true
      // );
      // direction.toFixed(0);

      cameraControls.rotateTo(
        Math.PI * ((360 - direction) / 180),
        Math.PI * (90 / 180),
        true
      );

      // 关键代码，在 0 到 360 度之间标准化相机方位角旋转。
      cameraControls.normalizeRotations();

      console.log('direction :>> ', direction);
    });

    // #region 测试
    // 测试移动
    // this['test.move']();

    this['test.markBeginAndEnd']();
    // #endregion

    // - 根据路网绘制箭头，./参考/提示箭头.png
    this.getArrow();

    // this.getRoadNetArrowV2();

    reDraw();

    // 初始化一些控件
    const $btnChangeMode = this['dev.addControl'](
      `${container}-ar__change-mode`,
      '切换视角',
      { top: '0px' }
    );

    let third = false;

    const _changeMode = async () => {
      if (!third) {
        await this.changeFirstPerson();
        third = true;
      } else {
        map.reset().setViewMode('3D');
        third = false;
      }
      map.reDraw();
    };
    $btnChangeMode.onclick = _changeMode;

    // // ARDemo
    // const $btnARDemo = this['dev.addControl'](
    //   `${container}-ar__demo`,
    //   'AR-demo',
    //   { top: '50px' }
    // );
    // $btnARDemo.onclick = async (ev: any) => {
    //   // await _changeMode();
    //   // 模拟用户在移动，实时更新箭头方向
    //   // this['dev.ARDemo']();
    // };

    this['dev.ARDemo']();
  }

  /**
   * @desc 模拟AR
   * 判断第一个箭头是否有在当前视野，没有的话给出提示箭头
   * https://www.cnblogs.com/minnong/p/12017035.html
   * https://stackoverflow.com/questions/66972302/check-in-which-direction-of-camera-the-object-is-in-threejs
   */
  private 'dev.ARDemo'() {
    const {
      map: { cameraControls, container, scene, camera }
    } = this.options;
    // 初始化一些控件
    const $btnUp = this['dev.addControl'](`${container}-ar__up`, 'up', {
      top: '50px'
    });
    const $btnDown = this['dev.addControl'](`${container}-ar__down`, 'down', {
      top: '100px'
    });
    const $btnLeft = this['dev.addControl'](`${container}-ar__left`, 'left', {
      top: '150px'
    });
    const $btnRight = this['dev.addControl'](
      `${container}-ar__right`,
      'right',
      { top: '200px' }
    );

    // register keyboard
    const KEYCODE = {
      // W: 87,
      // A: 65,
      // S: 83,
      // D: 68,
      ARROW_LEFT: 37,
      ARROW_UP: 38,
      ARROW_RIGHT: 39,
      ARROW_DOWN: 40
    };

    const holdUpKey = new holdEvent.KeyboardKeyHold(
      KEYCODE.ARROW_UP
      // 16.666
    );
    const holdDownKey = new holdEvent.KeyboardKeyHold(
      KEYCODE.ARROW_DOWN
      // 16.666
    );
    const holdLeftKey = new holdEvent.KeyboardKeyHold(
      KEYCODE.ARROW_LEFT
      // 16.666
    );
    const holdRightKey = new holdEvent.KeyboardKeyHold(
      KEYCODE.ARROW_RIGHT
      // 16.666
    );

    // register elem
    const holdUp = new holdEvent.ElementHold($btnUp);
    const holdDown = new holdEvent.ElementHold($btnDown);
    const holdLeft = new holdEvent.ElementHold($btnLeft);
    const holdRight = new holdEvent.ElementHold($btnRight);

    const factorForward = 10;
    const target = new Vector3();

    holdUp.addEventListener(holdEvent.HOLD_EVENT_TYPE.HOLDING, event => {
      cameraControls.forward(factorForward * event!.deltaTime, false);
      cameraControls.getTarget(target);
      // 更新用户起点，重新计算最佳线路，重新绘制箭头
      this.search([target.x, target.y], this.endCache);
    });
    holdDown.addEventListener(holdEvent.HOLD_EVENT_TYPE.HOLDING, event => {
      cameraControls.forward(-factorForward * event!.deltaTime, false);
      cameraControls.getTarget(target);
      // 更新用户起点，重新计算最佳线路，重新绘制箭头
      this.search([target.x, target.y], this.endCache);
    });

    holdUpKey.addEventListener(holdEvent.HOLD_EVENT_TYPE.HOLDING, event => {
      cameraControls.forward(factorForward * event!.deltaTime, false);
      cameraControls.getTarget(target);
      // 更新用户起点，重新计算最佳线路，重新绘制箭头
      this.search([target.x, target.y], this.endCache);
    });
    holdDownKey.addEventListener(holdEvent.HOLD_EVENT_TYPE.HOLDING, event => {
      cameraControls.forward(-factorForward * event!.deltaTime, false);
      cameraControls.getTarget(target);
      // 更新用户起点，重新计算最佳线路，重新绘制箭头
      this.search([target.x, target.y], this.endCache);
    });

    const _rotate = (event: any, direction: string) => {
      const $arrow = this.getArrow();

      let n = factorRotate * MathUtils.DEG2RAD * event!.deltaTime;
      n = direction === 'left' ? n : -n;
      cameraControls.rotate(n, 0, true);

      // 第一个箭头的位置
      const firstArrow = getObjectsByName<Group>('ar_roadNetArrow', scene)[0];
      // firstArrow.forEach((item, i) => {
      //   if (i === 0) {
      //     item.visible = true;
      //   } else {
      //     item.visible = false;
      //   }
      // });
      // console.log('firstArrow :>> ', firstArrow[0]);
      let status: 'in' | 'out' | 'none' = 'none';

      if (firstArrow) {
        const tempV = firstArrow.position
          .clone()
          .applyMatrix4(camera.matrixWorldInverse)
          .applyMatrix4(camera.projectionMatrix);

        if (
          Math.abs(tempV.x) > 1 ||
          Math.abs(tempV.y) > 1 ||
          Math.abs(tempV.z) > 1
        ) {
          status = 'out';
          // 在视野外了
          // console.log('在视野外了 :>> ', '在视野外了');
        } else {
          status = 'in';
          // 在视野内了
          // console.log('在视野内 :>> ', '在视野内');
        }

        // given obj1...
        // const temp = obj1.position.clone();
        const temp = firstArrow.position.clone();
        camera.worldToLocal(temp);
        if (status === 'out') {
          if (temp.x >= 0) {
            $arrow.turnRight();
            // console.log('right half of the screen!');
          } else if (temp.x < 0) {
            $arrow.turnLeft();
            // console.log('left half of the screen!');
          }
        } else {
          $arrow.hide();
        }
        // else {
        //   console.log('The object is in the exact middle!');
        // }
      }
    };

    const factorRotate = 0.1;
    holdLeft.addEventListener(holdEvent.HOLD_EVENT_TYPE.HOLDING, event => {
      _rotate(event, 'left');
    });
    holdRight.addEventListener(holdEvent.HOLD_EVENT_TYPE.HOLDING, event => {
      _rotate(event, 'right');
    });

    holdLeftKey.addEventListener(holdEvent.HOLD_EVENT_TYPE.HOLDING, event => {
      _rotate(event, 'left');
    });
    holdRightKey.addEventListener(holdEvent.HOLD_EVENT_TYPE.HOLDING, event => {
      _rotate(event, 'right');
    });
  }

  /**
   * @desc ar-demo
   */
  // private 'dev.ARDemoV1'() {
  //   const {
  //     map: { cameraControls }
  //   } = this.options;
  //   const self = this;

  //   /**
  //    * @desc 创建补间动画
  //    */
  //   const createTween = (oldT: Vector3, newT: Vector3, cb: () => void) => {
  //     // 需要执行动画的路线形成数组，然后进行移动，
  //     const t = new TWEEN.Tween({
  //       x: oldT.x,
  //       y: oldT.y,
  //       z: oldT.z
  //     })
  //       // time = distance / speed，保持每段距离都匀速
  //       .to(
  //         { x: newT.x, y: newT.y, z: newT.z },
  //         this.getDistance(oldT, newT) / 3
  //       )
  //       .easing(TWEEN.Easing.Linear.None)
  //       .onStart(function () {
  //         // 动画过程中不能操作画布相关
  //         // cameraControls.enabled = false;
  //         const { radian } = getAngle(oldT, newT);

  //         // to do 上下旋转，当前先一段45度，一段30度切换的方式

  //         // 扣掉90°表示垂直向上
  //         // cameraControls.rotateTo(radian - Math.PI / 2, Math.PI / 4, true);
  //         cameraControls.rotateTo(
  //           radian + Math.PI / 2,
  //           Math.PI * (90 / 180),
  //           true
  //         );

  //         // // 导航指标的指向
  //         // naviPointer.rotation.z = radian - Math.PI / 2;

  //         // 正常化 controls 的角度，控制在0-360°之间，否则会累积角度
  //         cameraControls.normalizeRotations();
  //       })
  //       .onUpdate(function (values: number) {
  //         // @ts-ignore
  //         const object = this;
  //         cameraControls.moveTo(object.x, object.y, object.z, true);

  //         // 实时更新计算箭头位置
  //         self.drawRoadNetArrows(newTrackPoints);

  //         // naviPointer.position.set(object.x, object.y, object.z);
  //         // reDraw();
  //       })
  //       .onComplete(function () {
  //         // 到最后一个动画走完才会开启控制
  //         cb();
  //         // last && (controls.enabled = true);
  //       });
  //     return t;
  //   };

  //   const startAnimation = (trackPoints: Vector3[]) => {
  //     let cb: () => void = () => {};
  //     // @ts-ignore
  //     const p = new Promise(resolve => (cb = resolve));

  //     const tweens: any[] = [];
  //     for (let i = 1; i < trackPoints.length; i++) {
  //       tweens.push(
  //         createTween(
  //           trackPoints[i - 1],
  //           trackPoints[i],
  //           // tweens 的最后一个才会进行 promise 的 resolve 回调
  //           i === trackPoints.length - 1 ? cb : () => {}
  //         )
  //       );
  //     }

  //     for (let i = 1; i < tweens.length; i++) {
  //       tweens[i - 1].chain(tweens[i]);
  //     }
  //     // 动画开始
  //     tweens[0].start();

  //     return p;
  //   };

  //   // this.changeFirstPerson();
  //   startAnimation(this.trackPointsCache);
  // }

  private getVideo() {
    const {
      map: { $map, container }
    } = this.options;
    const id = `${container}-ar__video`;
    let $video = document.getElementById(id);
    if (!$video) {
      // 动态append $video
      $video = document.createElement('video');
      $video.id = id;
      $video.className = `${CLS_PREFIX}-ar__video`;
      $video.setAttribute('webkit-playsinline', 'webkit-playsinline');
      $video.setAttribute('playsinline', 'playsinline');
      $video.setAttribute('x5-playsinline', 'x5-playsinline');
      $map.appendChild($video);
    }
    return $video as HTMLVideoElement;
  }

  /**
   * @desc 调用相机
   */
  private callCamera() {
    // 这里直接先判断环境，不是https 就不开启了
    if (location.protocol !== 'https:') {
      return;
    }
    const {
      map: { $map, container }
    } = this.options;
    const $video = this.getVideo();

    // 访问用户媒体设备的兼容方法
    function getUserMedia(
      constraints: any,
      success: (stream: any) => void,
      error: (error: Error) => void
    ) {
      if (navigator.mediaDevices.getUserMedia) {
        // 最新的标准API
        navigator.mediaDevices
          .getUserMedia(constraints)
          .then(success)
          .catch(error);
        // @ts-ignore
      } else if (navigator.webkitGetUserMedia) {
        // webkit核心浏览器
        // @ts-ignore
        navigator.webkitGetUserMedia(constraints, success, error);
        // @ts-ignore
      } else if (navigator.mozGetUserMedia) {
        // firfox浏览器
        // @ts-ignore
        navigator.mozGetUserMedia(constraints, success, error);
        // @ts-ignore
      } else if (navigator.getUserMedia) {
        // 旧版API
        // @ts-ignore
        navigator.getUserMedia(constraints, success, error);
      }
    }

    function success(stream: any) {
      // 兼容webkit核心浏览器
      const CompatibleURL = window.URL || window.webkitURL;
      // 将视频流设置为video元素的源
      // console.log(stream);

      // video.src = CompatibleURL.createObjectURL(stream);
      $video.srcObject = stream;

      // ios 不会自动播放，需要有触发事件，所以这里无效
      $video.play();
    }

    function error(error: Error) {
      console.log(`访问用户媒体设备失败${error.name}, ${error.message}`);
    }

    // 开始
    if (
      // @ts-ignore
      navigator.mediaDevices.getUserMedia ||
      // @ts-ignore
      navigator.getUserMedia ||
      // @ts-ignore
      navigator.webkitGetUserMedia ||
      // @ts-ignore
      navigator.mozGetUserMedia
    ) {
      // 调用用户媒体设备, 访问摄像头
      getUserMedia(
        {
          video: {
            // width: 100, height: 320,
            // width: window.innerWidth,
            // height: window.innerHeight,

            // 默认前置摄像头
            facingMode:
              location.hash.indexOf('environment') !== -1
                ? {
                    exact: 'environment'
                  }
                : 'user'
          }
        },
        success,
        error
      );
    } else {
      // alert('不支持访问用户媒体');
    }

    // // 在webview中添加代码
    // let hash;
    // // 监听hash变化
    // window.addEventListener(
    //   'popstate',
    //   function () {
    //     // 有hash值时返回上一个页面，并储存hash。
    //     // 因为返回了上一个页面，会立马进入else分支。
    //     // 将存好的hash渲染到页面
    //     if (location.hash) {
    //       hash = location.hash.split('#')[1];
    //       history.back();
    //     } else {
    //       // hash值最好是编码后的数据
    //       // document.getElementsByTagName('body')[0].innerHTML = '解码后参数：' + decodeURIComponent(hash);
    //     }
    //     video.play();
    //   },
    //   false
    // );
  }

  private async drawRoadNet() {
    const { data } = this.options;
    // 目前直接获取到改楼层对应的路网数据
    if (typeof data === 'function') {
      try {
        const res = await (data() as Promise<any[]>);
        this._drawRoadNet(res);
        for (const fn of this._nextTick) {
          fn?.(res);
        }
      } catch (error) {}
    } else {
      this._drawRoadNet(data);
      for (const fn of this._nextTick) {
        fn?.(data);
      }
    }
  }

  private initAStar() {
    // this.keyPoints = standardKeyPointsChanger(this.path);
    // this.aStar = new AStar();
    //  // 路网最佳线路计算开始，目前只考虑一层
    //  let { point: userPoint } = this.userPoint;
    //  let { point: parkPoint } = this.parkPoint;
    //  userPoint = myTruncate([userPoint.x, userPoint.y]);
    //  parkPoint = myTruncate([parkPoint.x, parkPoint.y]);
    //  // 得到绘制轨迹的点集合
    //  let trackPoints = this.calcShortestPath(userPoint, parkPoint);
    //  console.log(
    //    'trackPoints-before :>> ',
    //    JSON.parse(JSON.stringify(trackPoints))
    //  );
    //  trackPoints = this.trackOptimize(trackPoints);
    //  console.log(
    //    'trackPoints-optimize :>> ',
    //    JSON.parse(JSON.stringify(trackPoints))
    //  );
    //  // 开始绘制
    //  removeObjectByName(OBJECT_NAME.track);
    //  if (trackPoints.length >= 2) {
    //    this.drawTrackByPoints(trackPoints);
    //  } else {
    //    console.warn('线段长度小于2，无法进行绘制');
    //  }
  }

  /**
   * @desc 拿到路网数据，绘制基础路网
   */
  private _drawRoadNet(data: any[]) {
    const {
      map: { reDraw }
    } = this.options;

    // const json = {
    //   begin: ['-132214.271', '38721.291'],
    //   distance: '22512.083490543166',
    //   end: ['-122146.552', '18585.876'],
    //   entry: 0,
    //   id: 1,
    //   type: 'path'
    // };
    for (const item of data) {
      item.begin[0] = +item.begin[0];
      item.begin[1] = +item.begin[1];

      item.end[0] = +item.end[0];
      item.end[1] = +item.end[1];

      const { begin, end, id } = item;
      this.drawNormalLine([new Vector3(...begin, 0), new Vector3(...end, 0)], {
        name: 'ar_roadNetPath',
        color: 'red'
      });
    }
    reDraw();
  }

  /**
   * @desc 绘制普通线段
   */
  private drawNormalLine(
    points: Vector3[],
    // end: [number, number],
    ext?: {
      name?: string;
      color?: string;
      renderOrder?: number;
    }
  ) {
    const {
      map: { scene }
    } = this.options;
    const line = new Line(
      new BufferGeometry().setFromPoints(points),
      new LineBasicMaterial({
        // visible: false,
        color: ext?.color || 'red',
        opacity: 1,
        transparent: true,
        depthTest: false
      })
    );
    line.name = ext?.name || '';
    // line.renderOrder = 998;
    line.userData = {
      // id,
      // begin,
      // end
    };
    scene.add(line);
  }

  /**
   * @desc 第一视角切换
   */
  private async changeFirstPerson() {
    const {
      map: { camera, cameraControls, reDraw }
    } = this.options;

    // 地板目前有1米，车位目前有1.5米，墙3米，假设人1.8米
    const floorHeight = 1 * 1000;
    const personHeight = 1.8 * 1000;
    // const personHeight = 5 * 1000;

    // 画布移动因子
    // 阻尼惯性，默认 0.05，调整会影响到 await 的过渡时长
    // cameraControls.dampingFactor = 0.05 * 2;
    cameraControls.dampingFactor = 0.05 / 1.25;
    // cameraControls.dampingFactor = 0.05 / 2;

    // 地图控制在90°
    const limitRadian = Math.PI * (90 / 180);
    const height = floorHeight + personHeight;
    // const height = 1000 + 500;
    // const limitDolly = -1;
    const limitDolly = -personHeight;
    // const limitDolly = -5000;
    // const limitDolly = -height;
    // const limitDolly = -1;
    // const limitDolly = 0.1;
    // const limitDolly = 100000;

    // 在"第一人称"， cameraControls.setPosition和dolly功能是一样的
    // 上下旋转
    cameraControls.minPolarAngle = limitRadian;
    cameraControls.maxPolarAngle = limitRadian;

    // // dolly
    cameraControls.minDistance = limitDolly;
    cameraControls.maxDistance = limitDolly;

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

    // cameraControls.enabled = false;

    // 禁止拖拽
    // cameraControls.truckSpeed = 0;

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

    cameraControls.dollyTo(limitDolly, false);
    // moveTo 为变量，需要根据实时定位来变化
    if (this.beginCache.length > 0) {
      cameraControls.moveTo(this.beginCache[0], this.beginCache[1], height);
    } else {
      cameraControls.moveTo(0, 0, height);
    }

    // moveTo 到用户所站的位置
    // cameraControls.moveTo(6015.380734193866, -3760.4037080452727, height);

    await cameraControls.rotateTo(0, limitRadian, false);
  }

  private deviceOrientationHandler() {
    // const _deviceOrientationHandler = (evt: any) => {
    //   const { alpha, beta, gamma } = evt;
    //   // Z轴方向，X轴方向，Y轴方向
    //   console.log('{ alpha, beta, gamma } :>> ', { alpha, beta, gamma });
    // };

    const ua = navigator.userAgent.toLowerCase();
    console.log('uauauaua :>> ', ua);
    const deviceOrientationHandler = (evt: any) => {
      console.log('evtevtevt :>> ', evt);
      // document.getElementById('alpha').innerHTML = Math.round(
      //   360 - event.alpha
      // );
      // $('#compass').css(
      //   'transform',
      //   'rotate(-' + Math.round(360 - event.alpha) + 'deg)'
      // );
    };
    if (/android/.test(ua)) {
      window.addEventListener(
        'deviceorientationabsolute',
        deviceOrientationHandler,
        false
      );
    } else {
      window.addEventListener(
        'deviceorientation',
        deviceOrientationHandler,
        false
      );
      // function deviceOrientationHandler(evt: any) {
      //   console.log('evtevtevt :>> ', evt);
      //   // document.getElementById('alpha').innerHTML = Math.round(
      //   //   360 - event.alpha
      //   // );
      //   // $('#compass').css(
      //   //   'transform',
      //   //   'rotate(-' + Math.round(360 - event.alpha) + 'deg)'
      //   // );
      // }
      // function DeviceOrientationHandler(event) {
      //   document.getElementById('alpha').innerHTML = event.webkitCompassHeading;
      //   $('#compass').css(
      //     'transform',
      //     'rotate(-' + event.webkitCompassHeading + 'deg)'
      //   );
      // }
    }
  }

  private onHashChange(cb: (query: Record<string, any>) => void) {
    // 本地测试，模拟小程序罗盘
    // let n = 0;
    // location.href = `${location.origin}#direction=first&environment=true`;
    // setInterval(() => {
    //   location.href = `${location.origin}#direction=${n}&environment=true`;
    //   n++;
    // }, 3000);

    const firstHash = location.hash;
    let newHash = '';

    const getQuery = (query: string) => {
      return query.split('&').reduce((prev, cur) => {
        const [k, v] = cur.split('=');
        return { ...prev, [k]: v };
      }, {});
    };
    window.addEventListener(
      'popstate',
      function () {
        if (location.hash !== firstHash) {
          newHash = location.hash;
          history.back();
        } else {
          const query = getQuery(newHash.split('#')[1]);
          console.log('queryqueryquery :>> ', query);
          cb?.(query);
        }

        // newHash = location.hash;
        // const query = getQuery(newHash.split('#')[1]);
        // cb?.(query);
      },
      false
    );
  }

  /**
   * @desc 获取箭头指向模型
   */
  private getRoadNetArrow() {
    const {
      map: { scene, reDraw }
    } = this.options;

    const floorHeight = 1 * 1000;
    const personHeight = 1.8 * 1000;
    const ARROW_HEIGHT = floorHeight;

    // position = new Vector3(position.x, position.y, ARROW_HEIGHT);

    if (this.arrowGroupCache) {
      const arrowGroupClone = this.arrowGroupCache.clone();
      return arrowGroupClone;
    }

    return new Promise<Object3D>((resolve, reject) => {
      // 获取到 mesh 用来改变属性
      const _getMesh = (arrow: Group) => {
        return arrow.children[0]?.children[0]?.children[0]?.children[0] as Mesh;
      };
      const loader = new GLTFLoader();
      loader.load(
        MODEL_ARROW,
        ({ scene: arrow }) => {
          const SCALE = 20;
          const multiplyFactor = 20;
          // - 获取 mesh 改变颜色
          const mesh = _getMesh(arrow);

          arrow.name = 'ar_roadNetArrow';

          // 创建一个新的Object3D，包含箭头和线框
          const arrowGroup = new Object3D();
          arrowGroup.name = 'ar_roadNetArrow';

          // scene.add(arrowGroup);
          // arrowGroup.rotateZ(Math.PI * (90 / 180));

          // 添加线框
          const arrowBorder = new LineSegments();
          const matLine = new LineBasicMaterial({
            color: '#fff',
            opacity: 1,
            linewidth: 2,
            transparent: true
            // depthTest: false
          });
          arrowBorder.material = matLine;
          arrowBorder.geometry = new EdgesGeometry(mesh.geometry);
          arrowBorder.rotateX(-Math.PI * (90 / 180));
          arrowBorder.scale.multiply(
            new Vector3(multiplyFactor, multiplyFactor, multiplyFactor)
          );
          // arrowBorder.scale.addScalar(SCALE);

          // 添加到一个新的组
          // arrowGroup.add(arrow, arrowBorder);

          arrowGroup.add(arrow);

          // @ts-ignore
          mesh.material.color.set('#366ef6');

          // mesh.rotateX(Math.PI * (90 / 180));

          // - 合适的大小，控制在屏幕的 1/3
          // arrow.scale.addScalar(SCALE);
          arrow.scale.multiply(
            new Vector3(multiplyFactor, multiplyFactor, multiplyFactor)
          );
          // - 初始化向上，也就是正北
          // arrow.rotateZ(Math.PI * (90 / 180));

          // 加入缓存
          // this.arrowGroupCache = arrowGroup;
          // const arrowGroupCache = this.arrowGroupCache.clone();
          // resolve(arrowGroupCache);

          this.arrowGroupCache = arrow;
          const arrowGroupCache = this.arrowGroupCache.clone();
          resolve(arrowGroupCache);
        },
        undefined,
        error => {
          console.error(error);
          reject(error);
        }
      );
    });
  }

  /**
   * @desc 获取箭头指向模型
   */
  private getRoadNetArrowV2() {
    const {
      map: { scene, reDraw }
    } = this.options;

    if (this.arrowGroupCache) {
      const arrowGroupClone = this.arrowGroupCache.clone();
      return arrowGroupClone;
    }

    return new Promise<Object3D>((resolve, reject) => {
      // 获取到 mesh 用来改变属性
      const _getMesh = (obj: Object3D): Mesh => {
        if (obj instanceof Mesh) {
          return obj;
        } else {
          return _getMesh(obj.children[0]);
        }
      };
      const loader = new GLTFLoader();
      loader.load(
        MODEL_ARROW_2,
        ({ scene: arrow }) => {
          const SCALE = 1000;
          // const multiplyFactor = 10000;
          // const multiplyFactor = 5000;
          // - 获取 mesh 改变颜色
          // const mesh = _getMesh(arrow);
          arrow.name = 'ar_roadNetArrow';
          // @ts-ignore
          // mesh.material.color.set('#366ef6');
          // arrow.scale.multiply(
          //   new Vector3(multiplyFactor, multiplyFactor, multiplyFactor)
          // );
          arrow.scale.addScalar(SCALE);
          this.arrowGroupCache = arrow;
          const arrowGroupCache = this.arrowGroupCache.clone();
          resolve(arrowGroupCache);
        },
        undefined,
        error => {
          console.error(error);
          reject(error);
        }
      );
    });
  }

  // private updateMeshArrowDirection(arrowMesh: Mesh, arrowBorder: LineSegments) {
  //   const {
  //     map: { scene, reDraw }
  //   } = this.options;

  //   reDraw();
  // }
  private getCompass() {
    const {
      map: { $map, container }
    } = this.options;
    const id = `${container}-ar__compass`;
    let $elem = document.getElementById(id);
    if (!$elem) {
      // 动态append $video
      $elem = document.createElement('div');
      $elem.id = id;
      $elem.className = `${CLS_PREFIX}-ar__compass`;
      $elem.style.position = 'fixed';
      $elem.style.zIndex = '998';
      $elem.style.left = '0';
      $elem.style.top = '0';
      $map.appendChild($elem);
    }
    return $elem as HTMLElement;
  }

  private 'test.move'() {
    const {
      map: { camera, cameraControls, reDraw }
    } = this.options;

    const oldT = new Vector3(0, 0, 2800);
    const newT = new Vector3(0, 10000, 2800);

    const t = new TWEEN.Tween({
      x: oldT.x,
      y: oldT.y,
      z: oldT.z
    })
      // time = distance / speed，保持每段距离都匀速
      .to({ x: newT.x, y: newT.y, z: newT.z }, 5000)
      .easing(TWEEN.Easing.Linear.None)
      .onStart(function () {})
      .onUpdate(function (values: number) {
        // @ts-ignore
        const object = this;
        cameraControls.moveTo(object.x, object.y, object.z, true);
        reDraw();
      })
      .onComplete(function () {});

    t.start();
  }

  /**
   * @desc 添加一个箭头
   */
  private getArrow() {
    const {
      map,
      map: { $map, container }
    } = this.options;

    const id = `${container}-ar__arrow`;
    let $elem = document.getElementById(id);
    if (!$elem) {
      $elem = document.createElement('div');
      $elem.style.display = 'none';
      $elem.id = id;
      $elem.className = `${CLS_PREFIX}-ar__arrow`;

      const $img = document.createElement('img');
      $img.src = IC_ARROW;
      const $span = document.createElement('span');
      $span.innerText = '往这边看';
      $elem.append($img, $span);
      $map.appendChild($elem);
    }
    const turnLeft = () => {
      $elem && ($elem.style.display = 'flex');
      $elem?.classList.remove('right');
      $elem?.classList.add('left');
    };
    const turnRight = () => {
      $elem && ($elem.style.display = 'flex');
      $elem?.classList.remove('left');
      $elem?.classList.add('right');
    };
    const hide = () => {
      $elem && ($elem.style.display = 'none');
    };

    return { $elem, turnLeft, turnRight, hide };
  }

  /**
   * @desc 添加一个按钮
   */
  private 'dev.addControl'(
    id: string,
    text: string,
    style: Record<string, any> = {}
  ) {
    const {
      map,
      map: { $map, container }
    } = this.options;

    // const id = `${container}-ar__demo`;
    let $elem = document.getElementById(id);
    if (!$elem) {
      $elem = document.createElement('button');
      $elem.id = id;
      $elem.innerText = text;
      Object.assign(
        $elem.style,
        {
          position: 'fixed',
          zIndex: '998',
          top: '0',
          right: '0'
          // userSelect: 'none'
          // '-webkitUserSelect': 'none'
        },
        style
      );
      $map.appendChild($elem);
    }
    return $elem as HTMLElement;
  }

  /**
   * @desc 选择起点和终点
   */
  private 'test.markBeginAndEnd'() {}

  /**
   * @desc 线段延长后的端点
   * @param p1 起点
   * @param p2 终点
   * @param d 需要新增距离
   * @returns 新的终点坐标
   */
  private getExtendedEndPoint(p1: Vector2, p2: Vector2, d: number): Vector2 {
    const vec2 = new Vector2();
    const _d = vec2.copy(p2).sub(p1).length() + d;
    let temp = vec2.copy(p2).sub(p1).normalize();
    temp = temp.multiplyScalar(_d);
    temp = temp.add(p1);
    return temp;
  }

  private getDistance(p1: Vector3 | Vector2, p2: Vector3 | Vector2) {
    return Math.abs(
      Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2))
    );
  }

  /**
   * @desc 根据 最佳线路 绘制箭头指向
   * 箭头绘制逻辑：
   * - 起/终点，是用户的真实位置，所以要补充两端箭头分别在头尾
   * - 只绘制部分箭头，不绘制全路网
   * - 只绘制前方 MAX 米，存在两种场景，1.箭头都是直线 2.箭头有几处拐弯，”拐弯提醒是比较重要的“
   * - 需要距离起点一点距离 DIST 米进行绘制，保证用户可以在第一视角看到”第一个箭头“
   */
  private async drawRoadNetArrows(trackPoints: any[]) {
    const {
      map: { scene, reDraw }
    } = this.options;

    /**
     * @desc 根据起点和终点还有和起点的距离 获取路径中的某个点
     */
    const _getTrackPoint = (p1: Vector2, p2: Vector2, len: number) => {
      const p1p2Len = Math.abs(
        Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2))
      );
      const cos = (p2.x - p1.x) / p1p2Len;
      const sin = (p2.y - p1.y) / p1p2Len;
      return new Vector2(p1.x + len * cos, p1.y + len * sin);
    };

    const MAX = 30 * 1000;
    const MIN = 5 * 1000;
    let p1;
    let p2;

    // 地板目前有1米，车位目前有1.5米，墙3米，假设人1.8米
    const floorHeight = 1 * 1000;
    const personHeight = 1.8 * 1000;

    // arrowTemp 只是用来计算一些需要的数据
    const arrowTemp = await this.getRoadNetArrowV2();
    const aabb = new Box3().setFromObject(arrowTemp);
    // const ARROW_LEN = aabb.max.y - aabb.min.y;
    let ARROW_LEN = aabb.max.x - aabb.min.x;
    // ARROW_LEN = ARROW_LEN + ARROW_LEN * 1.5;
    ARROW_LEN = ARROW_LEN + ARROW_LEN * 0.8;

    // const box3 = new Box3Helper(aabb, 'red');
    // scene.add(box3);

    // 先把自身的 z 清除设置为 -z，实际的 z 轴就是后面累加的值
    const ARROW_POSITION_Z = -aabb.min.z + floorHeight;

    console.log('aabb :>> ', aabb);

    // // 箭头数组组
    // const arrowArrGroup = new Object3D();
    // arrowArrGroup.name = 'ar_arrowArrGroup';

    // scene.add(arrowArrGroup);

    // const _getMesh = (arrow: Group) => {
    //   return arrow.children[0]?.children[0]?.children[0]?.children[0]
    //     ?.children[0] as Mesh;
    // };

    // 判断前方 MAX 米会涉及到几条 path

    // 模拟AR，模拟手机摇晃

    const getNewTrackPoints = (dist: number, trackPoints: any[]) => {
      const newTrackPoints: any[] = [];
      // 累加大于 MAX 就停止
      let total = 0;
      let prevTotal = 0;
      let d = 0;
      let index = -1;
      for (let i = 0, len = trackPoints.length; i < len; i++) {
        p1 = trackPoints[i];
        if (trackPoints[i + 1]) {
          p2 = trackPoints[i + 1];
          d = this.getDistance(p1, p2);
          total += d;
          console.log(`第${i + 1}段距离 :>> `, this.getDistance(p1, p2));
          // 小于就直接 push
          if (total < dist) {
            // 记录之前的总长度
            prevTotal += d;
            newTrackPoints.push(p1);
          } else {
            // 记录当前的位置
            index = i;
            console.log('index :>> ', index);
            console.log('之前总长度 :>> ', prevTotal);
            // 扣除掉累加的 total
            const newEnd = _getTrackPoint(p1, p2, dist - prevTotal);
            // 处理
            newTrackPoints.push(p1, newEnd);
            // 跳出循环
            break;
          }
        } else {
          // 所有线段累加都没有超过 MAX 这里就要补充最后一个点，使其完整
          newTrackPoints.push(p1);
        }
      }

      return { newTrackPoints, index };
    };

    const { newTrackPoints: newTrackPoints1, index } = getNewTrackPoints(
      MIN,
      trackPoints
    );
    if (index !== -1) {
      // 把起点路线的最后一个点放在合适的位置
      trackPoints.splice(index, 1, newTrackPoints1[newTrackPoints1.length - 1]);
      // 删掉多余的开始的点
      trackPoints = trackPoints.slice(index);
    }
    const { newTrackPoints } = getNewTrackPoints(MAX, trackPoints);
    console.log('newTrackPoints :>> ', newTrackPoints);
    for (let i = 0, len = newTrackPoints.length; i < len; i++) {
      if (newTrackPoints[i + 1]) {
        p1 = newTrackPoints[i];
        p2 = newTrackPoints[i + 1];

        const p1p2Len = Math.abs(
          Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2))
        );

        // // 控制箭头绘制数量，只在前方 MAX米 绘制箭头
        // if (p1p2Len > MAX) {
        //   // https://blog.csdn.net/weixin_45565771/article/details/106714600

        //   const cos = (p2.x - p1.x) / p1p2Len;
        //   const sin = (p2.y - p1.y) / p1p2Len;
        //   p2 = new Vector2(p1.x + MAX * cos, p1.y + MAX * sin);

        //   p1p2Len = MAX;
        // }

        // 角度
        const { radian } = getAngle(
          new Vector2(p1.x, p1.y),
          new Vector2(p2.x, p2.y)
        );

        // const arrowNum = Math.ceil(p1p2Len / ARROW_LEN);
        const arrowNum = Math.floor(p1p2Len / ARROW_LEN);
        // const arrowNum = Math.round(p1p2Len / ARROW_LEN);
        console.log('arrowNumarrowNumarrowNum :>> ', arrowNum);

        // gap 跳过几个进行绘制，使其有间隔
        const gap = 0;
        // 第一条 path 的第一个第二个不进行绘制，因为第一视角下，会看不到
        // for (let j = i === 0 ? 3 : 0; j < arrowNum; j += gap + 1) {
        for (let j = i === 0 ? 1 : 0; j < arrowNum; j += gap + 1) {
          // for (let j = 0; j < arrowNum; j += gap + 1) {
          const arrow = await this.getRoadNetArrowV2();
          // 位置
          const x = ((p2.x - p1.x) / arrowNum) * j + p1.x;
          const y = ((p2.y - p1.y) / arrowNum) * j + p1.y;

          arrow.position.copy(new Vector3(x, y, ARROW_POSITION_Z));
          arrow.rotateZ(radian);
          // arrow.rotateY(-Math.PI * (30 / 180));
          scene.add(arrow);

          // const tempaabb = new Box3().setFromObject(arrow);
          // const tempBox3 = new Box3Helper(tempaabb, 'red');
          // scene.add(tempBox3);

          // 添加到一个组，然后控制这个组的倾斜角
          // arrowArrGroup.add(arrow);
        }

        // box3.rotateZ(radian);
        // box3.rotateY(-Math.PI * (20 / 180));

        // box3.rotateZ(Math.PI * (20 / 180));
        // box3.position.setZ(5000);

        // arrowArrGroup.rotateY(-Math.PI * (30 / 180));
        // arrowArrGroup.rotateX(Math.PI * (30 / 180));

        // arrowArrGroup.rotateZ(radian);
        // arrowArrGroup.rotateY(Math.PI * (10 / 180));
        // arrowArrGroup.position.setZ(1000);

        // const aabb2 = new Box3().setFromObject(arrowArrGroup);
        // const box3 = new Box3Helper(aabb2, 'red');
        // scene.add(box3);
        reDraw();
      }
    }
  }

  /**
   * @desc 线路计算
   */
  search(
    begin: number[],
    end: number[],
    _cb?: (status: 'complete' | 'error', result: any) => void
  ) {
    begin = myTruncate(begin);
    end = myTruncate(end);

    // 缓存
    this.beginCache = begin;
    this.endCache = end;

    const {
      map: { scene }
    } = this.options;

    if (begin.length > 0 && end.length > 0) {
      // 模拟测试
      // begin = [0, 0];
      // begin = [6015.380734193866, -3760.4037080452727];
      // end = [-23497.355, 28685.989];

      // 得到绘制轨迹的点集合
      const trackPoints = this.aStar.calcShortestPath(begin, end);
      const {
        map: { scene, reDraw }
      } = this.options;
      // 先删除已经存在的线段

      const tracks = getObjectsByName<Mesh>('ar_track', scene);
      scene.remove(...tracks);

      // 删除已经存在的箭头对象
      const roadNetArrows = getObjectsByName<Mesh>('ar_roadNetArrow', scene);
      scene.remove(...roadNetArrows);

      reDraw();

      if (trackPoints.length >= 2) {
        const points: Vector3[] = [];
        // 绘制简单的线段
        for (const item of trackPoints) {
          // @ts-ignore
          points.push(new Vector3(item.x, item.y, 0));
        }
        this.drawNormalLine(points, {
          name: 'ar_track',
          color: 'blue'
        });

        // 在线路上绘制指示箭头，加入用户的真实起点，要连接用户到路网上
        const newTrackPoints = (this.trackPointsCache = [
          new Vector2(begin[0], begin[1]),
          ...trackPoints
        ]);
        this.drawRoadNetArrows(newTrackPoints);

        _cb?.('complete', trackPoints);
      } else {
        console.warn('线段长度小于2，无法进行绘制');
        _cb?.('error', '线段长度小于2，无法进行绘制');
      }
      reDraw();
    }
  }

  // walking.search(
  //   [116.399028, 39.845042],
  //   [116.436281, 39.880719],
  //   function (status, result) {
  //     // result即是对应的步行路线数据信息，相关数据结构文档请参考  https://lbs.amap.com/api/javascript-api/reference/route-search#m_WalkingResult
  //     if (status === 'complete') {
  //       log.success('绘制步行路线完成');
  //     } else {
  //       log.error('步行路线数据查询失败' + result);
  //     }
  //   }
  // );
}
