import * as THREE from 'three';
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
import { Message } from 'element-ui';

var _this = null;
var MeasureClearDistance = function (renderer, scene, camera, width, height) {
  this.renderer = renderer;
  this.scene = scene;
  this.camera = camera;
  this.pointArray = []; // 保存当前操作所添加的点
  this.raycaster = new THREE.Raycaster();
  this.points = []; // 保存页面中所添加的点
  this.polyline = []; //保存页面中所添加的直线
  this.labels = []; // 保存页面中所添加的文本
  this.timer = null;
  this.width = width;
  this.height = height;
  this.firstTime = 0;
  this.measureName = 'measureObj'
  this.selectedObjects = []
};

MeasureClearDistance.prototype = {
  start() {
    _this = this;
    this.renderer.domElement.style.cursor = 'crosshair';
    this.renderer.domElement.addEventListener('mouseup', this.click, false);
    this.renderer.domElement.addEventListener('mousedown', this.mousedown, false);
  },
  updateParams(width, height) {
    this.camera.aspect = width / height;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(width, height, true);
    this.width = width;
    this.height = height;
  },
  getPosition(e) {
    const mouse = new THREE.Vector2();
    const elRect = this.renderer.domElement.getBoundingClientRect();
    const canvasX = e.clientX - elRect.left;
    const canvasY = e.clientY - elRect.top;

    mouse.x = (canvasX / elRect.width) * 2.0 - 1.0;
    mouse.y = -(canvasY / elRect.height) * 2.0 + 1.0;

    _this.raycaster.setFromCamera(mouse, this.camera);
    let intersects = _this.raycaster.intersectObjects(_this.scene.children, true);
    let l = intersects.length
    let obj = null
    if (l > 0) {
      for (let index = 0; index < l; index++) {
        if (intersects[index].object.type === 'Mesh') {
          obj = intersects[index]
          break
        }
      }
    }
    return obj
  },
  createLine(p1, p2, config = { color: 0xff0000 }) {
    const lineMaterial = new THREE.LineBasicMaterial({
      color: config.color,
      linewidth: 15,
      depthTest: false,
      depthWrite: false,
      transparent: true,
    });
    const lineGeometry = new THREE.BufferGeometry().setFromPoints([p1, p2]);
    const line = new THREE.Line(lineGeometry, lineMaterial);
    line.name = this.measureName;
    line.renderOrder = 999;
    line.frustumCulled = false;
    return line;
  },

  createLabel(name, text, position) {
    const div = document.createElement('div');
    div.className = name;
    div.textContent = text;
    const divLabel = new CSS2DObject(div);
    divLabel.name = this.measureName
    divLabel.position.set(position.x, position.y, position.z);
    return divLabel;
  },
  createTipsLabel(label, position) {
    const div = document.createElement('div');
    div.className = 'tips-label';
    div.textContent = label;
    const tipsLabel = new CSS2DObject(div);
    tipsLabel.name = this.measureName
    tipsLabel.position.set(position.x + 0.1, position.y, position.z + 0.05);
    return tipsLabel;
  },
  mousedown() {
    this.firstTime = new Date().getTime();
  },
  click(e) {
    let lastTime = new Date().getTime();
    if (lastTime - this.firstTime < 300) {
      const measureObj = _this.getPosition(e);
      console.log(measureObj)
      if (measureObj) {
        _this.selectedObjects.push(measureObj)
        // 高亮选中的
        // const instanceInfo = measureObj.object.userData?.instancesMap?.get(measureObj.object.name);
        // const { instanceIndex } = instanceInfo;
        let instanceIndex = measureObj.instanceId
        measureObj.object.setColorAt(instanceIndex, new THREE.Color(0x53a8ff));
        measureObj.object.instanceColor.needsUpdate = true;
        if (_this.selectedObjects.length % 2 === 0) {
          _this.calculateClearDistance()
        }
      } else {
        Message.warning('请点击模型进行测量');
      }
    }
  },
  calculateClearDistance() {
    const l = this.selectedObjects.length;
    // 每次都取最后两个
    const obj1 = this.selectedObjects[l - 2];
    const obj2 = this.selectedObjects[l - 1];
    const isParallel = this.checkParallelism(obj1.object, obj1.instanceId, obj2.object, obj2.instanceId);

    if (!isParallel) {
      Message.warning('请选择两个平行的物体');
      return
    }
    // 全局采样
    const localPts1 = this.sampleGlobal(obj1.object.geometry, 1000);
    const localPts2 = this.sampleGlobal(obj2.object.geometry, 1000);
    const wm1 = this.getInstanceWorldMatrix(obj1.object, obj1.instanceId);
    const wm2 = this.getInstanceWorldMatrix(obj2.object, obj2.instanceId);
    const worldPts1 = localPts1.map(p => p.clone().applyMatrix4(wm1));
    const worldPts2 = localPts2.map(p => p.clone().applyMatrix4(wm2));
    let closestPair = this.findClosestPair(worldPts1, worldPts2);
    // 局部精细搜索
    const radius = closestPair.distance * 4;
    const localPts1Fine = this.sampleLocal(obj1.object, obj1.instanceId, closestPair.point1, radius, 1000);
    const localPts2Fine = this.sampleLocal(obj2.object, obj2.instanceId, closestPair.point2, radius, 1000);
    const combined1 = worldPts1.filter(p => p.distanceTo(closestPair.point1) < radius).concat(localPts1Fine);
    const combined2 = worldPts2.filter(p => p.distanceTo(closestPair.point2) < radius).concat(localPts2Fine);
    if (combined1.length > 0 && combined2.length > 0) {
      closestPair = this.findClosestPair(combined1, combined2, true);
    }

    const position = new THREE.Vector3((closestPair.point1.x + closestPair.point2.x) / 2, (closestPair.point1.y + closestPair.point2.y) / 2, (closestPair.point1.z + closestPair.point2.z) / 2);
    // 将测量结果显示在页面中
    const circleTag1 = this.createLabel('circle-tag', '', closestPair.point1);
    const circleTag2 = this.createLabel('circle-tag', '', closestPair.point2);
    const line = this.createLine(closestPair.point1, closestPair.point2)
    const label = this.createLabel('measure-label', `${this.numberToString(closestPair.distance)}`, position)
    this.points.push(circleTag1);
    this.points.push(circleTag1);
    this.polyline.push(line);
    this.labels.push(label);
    this.scene.add(circleTag1);
    this.scene.add(circleTag2);
    this.scene.add(line);
    this.scene.add(label);
  },
  // 全部采样
  sampleGlobal(geometry, maxPoints = 600) {
    const triangles = this.extractTriangles(geometry);
    if (triangles.length === 0) return [];
    const areas = triangles.map(t => {
      const ab = new THREE.Vector3().subVectors(t.b, t.a);
      const ac = new THREE.Vector3().subVectors(t.c, t.a);
      return new THREE.Vector3().crossVectors(ab, ac).length() * 0.5;
    });
    const totalArea = areas.reduce((s, a) => s + a, 0);
    const points = [];
    for (let i = 0; i < triangles.length; i++) {
      const { a, b, c } = triangles[i];
      const count = Math.max(1, Math.round((areas[i] / totalArea) * maxPoints));
      for (let j = 0; j < count; j++) {
        const u = Math.random(), v = Math.random() * (1 - u), w = 1 - u - v;
        points.push(new THREE.Vector3().addScaledVector(a, u).addScaledVector(b, v).addScaledVector(c, w));
      }
    }
    while (points.length < maxPoints) {
      const t = triangles[Math.floor(Math.random() * triangles.length)];
      const u = Math.random(), v = Math.random() * (1 - u), w = 1 - u - v;
      points.push(new THREE.Vector3().addScaledVector(t.a, u).addScaledVector(t.b, v).addScaledVector(t.c, w));
    }
    return points.slice(0, maxPoints);
  },
  // 局部采样
  sampleLocal(instancedMesh, instanceId, centerWorld, radius, pointsCount = 300) {
    const worldMatrix = this.getInstanceWorldMatrix(instancedMesh, instanceId);
    const invMatrix = new THREE.Matrix4().copy(worldMatrix).invert();
    const centerLocal = centerWorld.clone().applyMatrix4(invMatrix);
    const triangles = this.extractTriangles(instancedMesh.geometry);
    if (triangles.length === 0) return [];

    const relevantTris = triangles.filter(t => {
      const box = new THREE.Box3().setFromPoints([t.a, t.b, t.c]);
      return box.distanceToPoint(centerLocal) <= radius;
    });
    if (relevantTris.length === 0) return [];

    const localPoints = [];
    const perTri = Math.ceil(pointsCount / relevantTris.length);
    for (const { a, b, c } of relevantTris) {
      for (let j = 0; j < perTri; j++) {
        const u = Math.random(), v = Math.random() * (1 - u), w = 1 - u - v;
        const pt = new THREE.Vector3().addScaledVector(a, u).addScaledVector(b, v).addScaledVector(c, w);
        if (pt.distanceTo(centerLocal) <= radius) localPoints.push(pt);
      }
    }
    while (localPoints.length < pointsCount && relevantTris.length > 0) {
      const { a, b, c } = relevantTris[Math.floor(Math.random() * relevantTris.length)];
      const u = Math.random(), v = Math.random() * (1 - u), w = 1 - u - v;
      const pt = new THREE.Vector3().addScaledVector(a, u).addScaledVector(b, v).addScaledVector(c, w);
      if (pt.distanceTo(centerLocal) <= radius) localPoints.push(pt);
    }
    return localPoints.map(p => p.clone().applyMatrix4(worldMatrix));
  },
  extractTriangles(geometry) {
    const pos = geometry.getAttribute('position');
    if (!pos) return [];
    const triangles = [];
    if (geometry.index) {
      const idx = geometry.index.array;
      for (let i = 0; i < idx.length; i += 3) {
        const a = new THREE.Vector3(pos.getX(idx[i]), pos.getY(idx[i]), pos.getZ(idx[i]));
        const b = new THREE.Vector3(pos.getX(idx[i + 1]), pos.getY(idx[i + 1]), pos.getZ(idx[i + 1]));
        const c = new THREE.Vector3(pos.getX(idx[i + 2]), pos.getY(idx[i + 2]), pos.getZ(idx[i + 2]));
        triangles.push({ a, b, c });
      }
    } else {
      for (let i = 0; i < pos.count; i += 3) {
        const a = new THREE.Vector3(pos.getX(i), pos.getY(i), pos.getZ(i));
        const b = new THREE.Vector3(pos.getX(i + 1), pos.getY(i + 1), pos.getZ(i + 1));
        const c = new THREE.Vector3(pos.getX(i + 2), pos.getY(i + 2), pos.getZ(i + 2));
        triangles.push({ a, b, c });
      }
    }
    return triangles;
  },
  findClosestPair(points1, points2) {
    let minDist = Infinity;
    const c1 = new THREE.Vector3();
    const c2 = new THREE.Vector3();
    for (const p1 of points1) {
      for (const p2 of points2) {
        const d = Number((p1.distanceTo(p2)).toFixed(2));
        if (d <= minDist) {
          minDist = d;
          c1.copy(p1);
          c2.copy(p2);
        }
      }
    }
    return { point1: c1, point2: c2, distance: minDist };
  },
  checkParallelism(inst1, id1, inst2, id2) {
    // 1. 获取世界矩阵
    const mat1 = this.getInstanceWorldMatrix(inst1, id1);
    const mat2 = this.getInstanceWorldMatrix(inst2, id2);

    // 2. 提取纯旋转部分（消除缩放和位移）
    const rotMat1 = new THREE.Matrix4().extractRotation(mat1);
    const rotMat2 = new THREE.Matrix4().extractRotation(mat2);

    // 如果物体可能有非均匀缩放，extractRotation 可能会出错，
    // 更稳健的方法是手动归一化列向量
    const normalizeMatrix = (m) => {
        const col0 = new THREE.Vector3().setFromMatrixColumn(m, 0).normalize();
        const col1 = new THREE.Vector3().setFromMatrixColumn(m, 1).normalize();
        const col2 = new THREE.Vector3().setFromMatrixColumn(m, 2).normalize();
        const normMat = new THREE.Matrix4();
        normMat.makeBasis(col0, col1, col2);
        return normMat;
    };
    const cleanRot1 = normalizeMatrix(rotMat1);
    const cleanRot2 = normalizeMatrix(rotMat2);

    // 3. 定义三个基向量（局部坐标系的X, Y, Z轴）
    const axes = [
        new THREE.Vector3(1, 0, 0),
        new THREE.Vector3(0, 1, 0),
        new THREE.Vector3(0, 0, 1)
    ];

    // 4. 检查是否有任意一对基向量在世界空间中平行
    const threshold = 0.99; // 提高阈值，更严格
    for (const axis1 of axes) {
        // 将局部基向量转换到世界方向
        const worldDir1 = axis1.clone().applyMatrix4(cleanRot1).normalize();
        for (const axis2 of axes) {
            const worldDir2 = axis2.clone().applyMatrix4(cleanRot2).normalize();
            const dot = Math.abs(worldDir1.dot(worldDir2));
            if (dot > threshold) {
                return true; // 只要有一对方向平行即判为平行
            }
        }
    }
    return false;
  },
  getInstanceWorldMatrix(instancedMesh, instanceId) {
    const matrix = new THREE.Matrix4();
    const instanceMatrix = new THREE.Matrix4();
    instancedMesh.getMatrixAt(instanceId, instanceMatrix);
    matrix.multiplyMatrices(instancedMesh.matrixWorld, instanceMatrix);
    return matrix;
  },
  close(isClear) {
    this.renderer.domElement.removeEventListener('mousedown', this.mousedown);
    this.renderer.domElement.removeEventListener('mouseup', this.click)
    if (!isClear) {
      this.remove(this.points);
      this.remove(this.polyline);
      this.remove(this.labels);
      this.pointArray.splice(0);
      this.points.splice(0);
      this.polyline.splice(0);
      this.labels.splice(0);
      this.firstTime = 0;
      this.selectedObjects.forEach(item => {
        let instanceIndex = item.instanceId
        item.object.setColorAt(instanceIndex, item.object.material.userData.nColor);
        item.object.instanceColor.needsUpdate = true;
      })
      this.selectedObjects.splice(0)
    }
    this.renderer.domElement.style.cursor = 'pointer';
  },
  clear() {
    this.selectedObjects.forEach(item => {
      let instanceIndex = item.instanceId
      item.object.setColorAt(instanceIndex, item.object.material.userData.nColor);
      item.object.instanceColor.needsUpdate = true;
    })
    this.selectedObjects.splice(0)
    this.remove(this.points);
    this.remove(this.polyline);
    this.remove(this.labels);
    this.pointArray.splice(0);
    this.points.splice(0);
    this.polyline.splice(0);
    this.labels.splice(0);
    this.firstTime = 0;
  },
  remove(array) {
    for (let index = 0; index < array.length; index++) {
      const element = array[index];
      if (element.geometry) {
        element.geometry.dispose();
      }
      this.scene.remove(element);
    }
  },
  numberToString(num) {
    if (num < 0.0001) {
      return num.toString();
    }
    let fractionDigits = 2;
    if (num < 0.01) {
      fractionDigits = 4;
    } else if (num < 0.1) {
      fractionDigits = 3;
    }
    return num.toFixed(fractionDigits);
  },
  preventContextMenu(event) {
    event.preventDefault();
  },
};
export default {
  MeasureClearDistance
};
