<template>
  <div class="formsWrapper">
    <el-form class="form1" label-position="left">
      <el-form-item label="场景码">
        <el-input v-model="formData.sceneCode" autofocus />
      </el-form-item>
      <el-button type="primary" @click="getWholeData">
        获取、显示数据点
      </el-button>
      <div class="color-desc">
        （按照高度从最低到最高，颜色由黑至红。手动补的点有透明度。）
      </div>
    </el-form>
    <el-form class="form2" label-position="top">
      <el-form-item
        :label="`路径1数据(必填)(节点index，以英文逗号分隔)(浅蓝色大圆圈表示)（路径长度：${path1Length}）`"
      >
        <el-input v-model="formData.path1" />
      </el-form-item>
      <el-form-item
        :label="`路径2数据（选填）(节点index，以英文逗号分隔)（深蓝色小圆圈表示）(路径长度：${path2Length})`"
      >
        <el-input v-model="formData.path2" />
      </el-form-item>
      <el-button type="primary" @click="renderPath"> 显示路径 </el-button>
      <el-button @click="onResetPath"> 清空 </el-button>
    </el-form>
    <el-form class="form3" label-position="top">
      <el-form-item label="起点坐标(以英文逗号分隔)(浅绿色小圆圈表示)">
        <el-input v-model="formData.startPoint" />
      </el-form-item>
      <el-form-item label="终点坐标(以英文逗号分隔)(深绿色小圆圈表示)">
        <el-input v-model="formData.endPoint" />
      </el-form-item>
      <el-button type="primary" @click="renderStartEndPoint">
        显示起点终点
      </el-button>
      <el-button @click="onResetStartEndPoint"> 清空 </el-button>
    </el-form>
  </div>

  <div class="infoText">
    {{ infoText }}
  </div>
  <div class="map">
    <div class="svgWrapper" />
    <div class="map-control-area">
      <div class="zoom-control panel">
        聚焦操作
        <el-button class="btn" type="primary" @click="zoomInToStartPoint">
          聚焦到起点
        </el-button>
        <el-button class="btn" type="primary" @click="zoomInToEndPoint">
          聚焦到终点
        </el-button>
        <el-button class="btn" @click="resetZoom"> 取消聚焦 </el-button>
      </div>
      <el-form class="height-filter panel">
        高度筛选
        <el-form-item :label="`高度上限`">
          <el-input v-model="formData.maxHeight" type="number" step="any" />
        </el-form-item>
        <el-form-item :label="`高度下限`">
          <el-input v-model="formData.minHeight" type="number" step="any" />
        </el-form-item>
        <div class="btn-group">
          <el-button type="primary" @click="onSetHeightFilter">
            确定
          </el-button>
          <el-button @click="onResetHeightFilter"> 清空 </el-button>
        </div>
      </el-form>
      <el-form class="add-point panel">
        手动补点
        <el-form-item :label="`补点模式`">
          <el-switch v-model="formData.isAddingPoint" />
        </el-form-item>
        <el-form-item
          :label="`高度差上限`"
          title="要想判定两个相邻区域连通，z坐标值之差不得超过此上限"
        >
          <el-input
            v-model="formData.connectionMaxHeightGap"
            type="number"
            step="any"
          />
        </el-form-item>
        <el-form-item :label="`补点高度`">
          <el-input
            v-model="formData.addPointHeight"
            type="number"
            step="any"
          />
        </el-form-item>
        <el-button type="primary" @click="onAddPoint"> 确定 </el-button>
      </el-form>
      <div class="edit-button-group">
        <div class="button-row">
          <el-button
            class="btn"
            :disabled="rawWholeDataHistory.currentIdx <= 0"
            @click="onAddPointUndo"
          >
            undo
          </el-button>
          <el-button
            class="btn"
            :disabled="
              rawWholeDataHistory.currentIdx ===
              rawWholeDataHistory.history.length - 1
            "
            @click="onAddPointRedo"
          >
            redo
          </el-button>
        </div>
        <div class="button-row">
          <el-button
            type="danger"
            class="btn"
            :disabled="rawWholeDataHistory.currentIdx < 0"
            @click="onClickRestore"
          >
            复原
          </el-button>
          <el-button
            class="btn"
            type="primary"
            :disabled="rawWholeDataHistory.currentIdx < 0"
            @click="uploadAddPointResult"
          >
            保存
          </el-button>
        </div>
        <!-- <div>{{ rawWholeDataHistory.history.length }}</div>
        <div>{{ rawWholeDataHistory.currentIdx }}</div>
        <div
          v-for="(item, idx) in rawWholeDataHistory.history"
          :key="idx"
        >
          {{ item.length }}
        </div> -->
      </div>
    </div>

    <PointEditor
      v-if="isEditingPoint"
      :initial-point-data="pointDataForEditor"
      @cancel="isEditingPoint = false"
      @confirm="onPointEditorConfirm"
      @delete="onPointEditorDelete"
    />
  </div>
</template>

<script>
import * as d3 from "d3";
import { getWholeData, uploadWholeData, resetWholeData } from "./api.js";
import { ElLoading, ElMessage, ElMessageBox } from "element-plus";
import {
  getDistance2D,
  computePointDistanceAndRowSlope,
  getNeighbourLocations,
} from "@/utils.js";
import deepClone from "lodash/cloneDeep";
import PointEditor from "@/components/PointEditor.vue";

// 视口尺寸
let svgWidth = document.documentElement.clientWidth - 200;
let svgHeight = document.documentElement.clientHeight - 280;
let svgRatio = svgWidth / svgHeight;

// 全体点位数据
let rawWholeData = [];

// 用户输入的起点终点
let startPoint = null;
let endPoint = null;

// 由原始数据算出的几何信息
let pxPerUnitLength = 0; // 原始数据1单位长度对应的像素数
let pointDistance = 0; // 最近相邻点间距离（单位：原始数据中长度单位）
let rowSlope = 0; // 点位构成的排的斜率 [0deg, 90deg)
let xCenter = 0;
let yCenter = 0;

// svg、d3相关
let svgNode = null;
let gNode = null;
let zoomObj = null;
let brushObj = null;
let activeRectSelection = null;
let activeRectNeighboursSelection = [];

// d3选择框位置
let brushLeftPx = 0;
let brushTopPx = 0;
let brushRightPx = 0;
let brushBottomPx = 0;

function resetGlobalVars() {
  // 视口尺寸
  svgWidth = document.documentElement.clientWidth - 200;
  svgHeight = document.documentElement.clientHeight - 280;
  svgRatio = svgWidth / svgHeight;

  // 全体点位数据
  rawWholeData = [];

  // 由原始数据算出的几何信息
  pxPerUnitLength = 0; // 原始数据1单位长度对应的像素数
  pointDistance = 0; // 最近相邻点间距离（单位：原始数据中长度单位）
  rowSlope = 0; // 点位构成的排的斜率 [0deg, 90deg)
  xCenter = 0;
  yCenter = 0;

  // d3选择框位置
  brushLeftPx = 0;
  brushTopPx = 0;
  brushRightPx = 0;
  brushBottomPx = 0;
}

function zoomed({ transform }) {
  gNode.attr("transform", transform);
}

function brushed(e) {
  if (e.selection) {
    console.log(
      "bursh area in px: ",
      e.selection[0][0],
      e.selection[0][1],
      e.selection[1][0],
      e.selection[1][1],
    );
    brushLeftPx = e.selection[0][0];
    brushTopPx = e.selection[0][1];
    brushRightPx = e.selection[1][0];
    brushBottomPx = e.selection[1][1];
  } else {
    brushLeftPx = 0;
    brushTopPx = 0;
    brushRightPx = 0;
    brushBottomPx = 0;
  }
}

export default {
  name: "App",
  components: {
    PointEditor,
  },
  data() {
    return {
      infoText: "",
      loadingHandler: null,
      formData: {
        // sceneCode: process.env.NODE_ENV === 'production' ? '' : 'SS-t-XkquhxxurM',
        sceneCode:
          process.env.NODE_ENV === "production" ? "" : "SS-t-NZUICC2fRLi",
        path1:
          process.env.NODE_ENV === "production"
            ? ""
            : "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,47,76,101,125",
        path2: "",
        startPoint: process.env.NODE_ENV === "production" ? "" : "-38.5, 10.8",
        endPoint: process.env.NODE_ENV === "production" ? "" : "-28.4, 12.2",
        maxHeight: "",
        minHeight: "",
        isAddingPoint: false,
        connectionMaxHeightGap: 1,
        addPointHeight: 0,
      },
      rawWholeDataHistory: {
        history: [],
        currentIdx: -1,
      },
      isEditingPoint: false,
      pointDataForEditor: {},
    };
  },
  computed: {
    path1Length() {
      return this.getInputPathLength(this.formData.path1);
    },
    path2Length() {
      return this.getInputPathLength(this.formData.path2);
    },
  },
  watch: {
    "formData.isAddingPoint": {
      handler(vNew) {
        if (vNew) {
          svgNode.on(".zoom", null);
          svgNode.append("g").attr("class", "brush").call(brushObj);
        } else {
          svgNode.call(zoomObj);
          svgNode.selectAll("g.brush").remove();
        }
      },
    },
  },
  mounted() {
    svgNode = d3
      .select(".svgWrapper")
      .append("svg")
      .attr("width", svgWidth)
      .attr("height", svgHeight);

    gNode = svgNode.append("g");

    zoomObj = d3.zoom().on("zoom", zoomed);
    svgNode.call(zoomObj);

    brushObj = d3.brush().on("end", (e) => {
      brushed(e);
    });

    svgNode.on("click", function (e, d) {
      activeRectSelection &&
        activeRectSelection.attr("fill", (d) => {
          return activeRectSelection.attr("initial-fill");
        });
      for (const neib of activeRectNeighboursSelection) {
        neib.attr("fill", (d) => {
          return neib.attr("initial-fill");
        });
      }
      gNode.selectAll("text").remove();
      activeRectSelection = null;
      activeRectNeighboursSelection = [];
    });
  },
  methods: {
    getInputPathLength(input) {
      let temp = input.trim();
      if (temp[0] === "[") {
        temp = temp.substr(1);
      }
      if (temp[temp.length - 1] === "]") {
        temp = temp.substr(0, temp.length - 1);
      }
      temp = temp.trim();
      if (!temp) {
        return 0;
      }
      let mapSuccess = true;
      temp = temp.split(",").map((item) => {
        item = item.trim();
        if (!item) {
          return undefined;
        } else {
          const num = Number(item);
          if (!Number.isSafeInteger(num)) {
            mapSuccess = false;
            return undefined;
          } else {
            return num;
          }
        }
      });
      if (!mapSuccess) {
        return "?";
      } else {
        return temp.filter((item) => {
          return item !== undefined;
        }).length;
      }
    },
    inputPathStringToArray(input) {
      let temp = input.trim();
      if (temp[0] === "[") {
        temp = temp.substr(1);
      }
      if (temp[temp.length - 1] === "]") {
        temp = temp.substr(0, temp.length - 1);
      }
      temp = temp.trim();
      if (!temp) {
        return [];
      }
      let mapSuccess = true;
      temp = temp.split(",").map((item) => {
        item = item.trim();
        if (!item) {
          return undefined;
        } else {
          const num = Number(item);
          if (!Number.isSafeInteger(num)) {
            mapSuccess = false;
            return undefined;
          } else {
            return num;
          }
        }
      });
      if (!mapSuccess) {
        window.alert(`解析输入路径失败：${input}`);
        return -1;
      } else {
        return temp.filter((item) => {
          return item !== undefined;
        });
      }
    },
    getWholeData() {
      if (!this.formData.sceneCode.trim()) {
        window.alert("场景码必填！");
        return;
      }
      this.loadingHandler = ElLoading.service({
        lock: true,
        text: "Loading",
        background: "rgba(0, 0, 0, 0.7)",
      });

      resetGlobalVars();
      gNode.selectAll("rect").remove();
      gNode.selectAll("text").remove();
      gNode.selectAll("circle").remove();

      getWholeData(this.formData.sceneCode)
        .then((res) => {
          this.rawWholeDataHistory.history = [];
          this.rawWholeDataHistory.currentIdx = -1;

          rawWholeData = res;
          this.renderWholePoints();

          this.rawWholeDataHistory.history.push(deepClone(rawWholeData));
          this.rawWholeDataHistory.currentIdx++;
        })
        .finally(() => {
          this.loadingHandler.close();
        });
    },
    renderWholePoints() {
      const that = this;

      // 相邻点位间距离
      const temp = computePointDistanceAndRowSlope(rawWholeData);
      pointDistance = temp[0];
      rowSlope = temp[1];

      // 所有点的分布情况
      let xArray = rawWholeData.map((eachPoint) => {
        return eachPoint.x;
      });
      let xLength = Math.max(...xArray) - Math.min(...xArray);
      xCenter = (Math.max(...xArray) + Math.min(...xArray)) / 2;

      let yArray = rawWholeData.map((eachPoint) => {
        return eachPoint.y;
      });
      let yLength = Math.max(...yArray) - Math.min(...yArray);
      yCenter = (Math.max(...yArray) + Math.min(...yArray)) / 2;
      let zArray = rawWholeData.map((eachPoint) => {
        return eachPoint.z;
      });
      let zLength = Math.max(...zArray) - Math.min(...zArray);
      let zMin = Math.min(...zArray);
      let areaRatio = xLength / yLength;

      // 各个点坐标映射到视口坐标
      if (svgRatio >= areaRatio) {
        // 分布范围应略小于svg尺寸
        pxPerUnitLength = (svgHeight / yLength) * 0.9;
      } else {
        pxPerUnitLength = (svgWidth / xLength) * 0.9;
      }
      let wholeXArrayInPx = xArray.map((eachX) => {
        return (eachX - xCenter) * pxPerUnitLength + svgWidth / 2;
      });
      let wholeYArrayInPx = yArray.map((eachY) => {
        return -1 * (eachY - yCenter) * pxPerUnitLength + svgHeight / 2; // 注意svg的y轴方向和人们习惯上的是相反的
      });

      // 组合成最终数据用来渲染
      let wholeDataForRender = [];
      for (let index = 0; index < rawWholeData.length; index++) {
        console.assert(
          rawWholeData[index].id === index + 1,
          "数据点id和数据点在数组中的位置不相符！",
        );
        wholeDataForRender.push([
          wholeXArrayInPx[index],
          wholeYArrayInPx[index],
          zArray[index],
          rawWholeData[index].isManuallyAdded,
          JSON.stringify(rawWholeData[index]),
          rawWholeData[index].id,
        ]);
      }

      gNode
        .selectAll("rect")
        .data(wholeDataForRender)
        .enter()
        .append("rect")
        .attr("raw-id", (d) => {
          return d[d.length - 1];
        })
        .attr("x", (d) => d[0] - (pointDistance * pxPerUnitLength) / 2)
        .attr("y", (d) => d[1] - (pointDistance * pxPerUnitLength) / 2)
        .attr("width", pointDistance * pxPerUnitLength)
        .attr("height", pointDistance * pxPerUnitLength)
        .attr("fill", (d) => {
          return `rgba(${Math.round(((d[2] - zMin) / zLength) * 255)}, 0, 0, ${
            d[3] ? "0.7" : "1"
          })`;
        })
        .attr("initial-fill", (d) => {
          return `rgba(${Math.round(((d[2] - zMin) / zLength) * 255)}, 0, 0, ${
            d[3] ? "0.7" : "1"
          })`;
        })
        .attr("style", (d) => {
          let pointId = d[d.length - 1];
          let ids = rawWholeData[pointId - 1].ids;
          let isDel = true;
          ids.forEach((id, idx) => {
            if (id !== "-1") {
              isDel = false;
            }
          });
          return isDel ? `display: none;` : "";
        });

      gNode
        .selectAll("rect")
        .on("mouseenter", function (e, d) {
          const id = d[d.length - 1];
          if (
            id !==
              activeRectSelection?.datum()[
                activeRectSelection?.datum().length - 1
              ] &&
            !activeRectNeighboursSelection?.some((item) => {
              return id === item.datum()[item.datum().length - 1];
            })
          ) {
            d3.select(this).attr("fill", "orange");
          }
          that.infoText = d[4];
        })
        .on("mouseleave", function (e, d) {
          const id = d[d.length - 1];
          if (
            id !==
              activeRectSelection?.datum()[
                activeRectSelection?.datum().length - 1
              ] &&
            !activeRectNeighboursSelection?.some((item) => {
              return id === item.datum()[item.datum().length - 1];
            })
          ) {
            d3.select(this).attr("fill", d3.select(this).attr("initial-fill"));
          }
          that.infoText = "";
        });

      gNode.selectAll("rect").on("click", function (e, d) {
        e.stopPropagation();

        // console.log(d)
        const id = d[d.length - 1];

        activeRectSelection &&
          activeRectSelection.attr("fill", (d) => {
            return activeRectSelection.attr("initial-fill");
          });
        for (const neib of activeRectNeighboursSelection) {
          neib.attr("fill", (d) => {
            return neib.attr("initial-fill");
          });
        }

        activeRectSelection = d3.select(this);
        activeRectSelection.attr("fill", (d) => {
          return "rgba(0, 0, 255, 1)";
        });
        activeRectNeighboursSelection = [];
        for (const neibId of rawWholeData[id - 1].ids) {
          if (neibId === "-1") {
            continue;
          }
          const neibNode = gNode.select(`rect[raw-id='${neibId}']`);
          neibNode.attr("fill", (d) => {
            return "rgba(0, 0, 128, 1)";
          });
          activeRectNeighboursSelection.push(neibNode);
        }

        gNode.selectAll("text").remove();
        const idTextList = [deepClone(wholeDataForRender[id - 1])];
        for (const neibId of rawWholeData[id - 1].ids) {
          if (neibId === "-1") {
            continue;
          }
          idTextList.push(wholeDataForRender[Number(neibId) - 1]);
        }
        gNode
          .selectAll("text")
          .data(idTextList)
          .enter()
          .append("text")
          .attr("x", (d) => d[0])
          .attr("y", (d) => d[1])
          .attr(
            "font-size",
            (d) => ((pointDistance * pxPerUnitLength) / d.length) * 1.3,
          )
          .attr("text-anchor", "middle")
          .attr("dominant-baseline", "middle")
          .attr("fill", "white")
          .attr("pointer-events", "none")
          .text((d) => {
            return d[d.length - 1];
          });

        that.formData.addPointHeight = d[2];
      });

      gNode.selectAll("rect").on("dblclick", function (e, d) {
        // console.log(d)
        const id = d[d.length - 1];
        console.log(rawWholeData[id - 1]);
        that.isEditingPoint = true;
        that.pointDataForEditor = deepClone(rawWholeData[id - 1]);
        e.stopPropagation();
      });
    },
    renderPath() {
      if (!this.formData.path1.trim()) {
        window.alert("路径1必填！");
        return;
      }

      gNode.selectAll("circle.path1").remove();
      gNode.selectAll("circle.path2").remove();

      let rawPathDataIndex = this.inputPathStringToArray(this.formData.path1);
      if (rawPathDataIndex === -1) {
        return;
      }
      let rawPathDataIndex2 = this.inputPathStringToArray(this.formData.path2);
      if (rawPathDataIndex2 === -1) {
        return;
      }
      // 基于path index 拿到path节点数组
      let rawPathData = [];
      for (let index = 0; index < rawPathDataIndex.length; index++) {
        const element = rawWholeData[rawPathDataIndex[index] - 1];
        // 假设节点id和节点在数组中出现顺序相符
        console.assert(
          element.id === rawPathDataIndex[index],
          "按照id寻找路径节点失败！",
        );
        rawPathData.push(element);
      }
      let rawPathData2 = [];
      for (let index = 0; index < rawPathDataIndex2.length; index++) {
        const element = rawWholeData[rawPathDataIndex2[index] - 1];
        // 假设节点id和节点在数组中出现顺序相符
        console.assert(
          element.id === rawPathDataIndex2[index],
          "按照id寻找路径节点失败！",
        );
        rawPathData2.push(element);
      }

      // 各个点坐标映射到视口坐标
      let pathXArrayInPx = rawPathData
        .map((eachPoint) => {
          return eachPoint.x;
        })
        .map((eachX) => {
          return (eachX - xCenter) * pxPerUnitLength + svgWidth / 2;
        });
      let pathYArrayInPx = rawPathData
        .map((eachPoint) => {
          return eachPoint.y;
        })
        .map((eachY) => {
          return -1 * (eachY - yCenter) * pxPerUnitLength + svgHeight / 2;
        });
      let pathXArrayInPx2 = rawPathData2
        .map((eachPoint) => {
          return eachPoint.x;
        })
        .map((eachX) => {
          return (eachX - xCenter) * pxPerUnitLength + svgWidth / 2;
        });
      let pathYArrayInPx2 = rawPathData2
        .map((eachPoint) => {
          return eachPoint.y;
        })
        .map((eachY) => {
          return -1 * (eachY - yCenter) * pxPerUnitLength + svgHeight / 2;
        });

      // 组合成最终数据用来渲染
      let pathDataForRender = [];
      for (let index = 0; index < rawPathData.length; index++) {
        pathDataForRender.push([pathXArrayInPx[index], pathYArrayInPx[index]]);
      }
      let pathDataForRender2 = [];
      for (let index = 0; index < rawPathData2.length; index++) {
        pathDataForRender2.push([
          pathXArrayInPx2[index],
          pathYArrayInPx2[index],
        ]);
      }

      // 进行渲染
      gNode
        .selectAll("circle.path1")
        .data(pathDataForRender)
        .enter()
        .append("circle")
        .classed("path1", true)
        .attr("cx", (d) => d[0])
        .attr("cy", (d) => d[1])
        .attr("r", (pointDistance * pxPerUnitLength) / 2)
        .attr("fill", "#000088")
        .attr("pointer-events", "none");
      gNode
        .selectAll("circle.path2")
        .data(pathDataForRender2)
        .enter()
        .append("circle")
        .classed("path2", true)
        .attr("cx", (d) => d[0])
        .attr("cy", (d) => d[1])
        .attr("r", (pointDistance * pxPerUnitLength) / 3)
        .attr("fill", "blue")
        .attr("pointer-events", "none");
    },
    onResetPath() {
      this.formData.path1 = "";
      this.formData.path2 = "";
      gNode.selectAll("circle.path1").remove();
      gNode.selectAll("circle.path2").remove();
    },
    renderStartEndPoint() {
      gNode.selectAll("circle.start").remove();
      gNode.selectAll("circle.end").remove();

      startPoint = this.formData.startPoint.trim();
      if (startPoint[0] === "[") {
        startPoint = startPoint.substr(1);
      }
      if (startPoint[startPoint.length - 1] === "]") {
        startPoint = startPoint.substr(0, startPoint.length - 1);
      }
      startPoint = startPoint.trim();
      startPoint = startPoint.split(",").map((item) => {
        item = item.trim();
        if (!item) {
          return undefined;
        } else {
          return Number(item);
        }
      });
      startPoint = startPoint.filter((item) => {
        return item !== undefined;
      });
      if (startPoint.length !== 2) {
        window.alert(`解析起点坐标失败`);
        startPoint = null;
      }

      endPoint = this.formData.endPoint.trim();
      if (endPoint[0] === "[") {
        endPoint = endPoint.substr(1);
      }
      if (endPoint[endPoint.length - 1] === "]") {
        endPoint = endPoint.substr(0, endPoint.length - 1);
      }
      endPoint = endPoint.trim();
      endPoint = endPoint.split(",").map((item) => {
        item = item.trim();
        if (!item) {
          return undefined;
        } else {
          return Number(item);
        }
      });
      endPoint = endPoint.filter((item) => {
        return item !== undefined;
      });
      if (endPoint.length !== 2) {
        window.alert(`解析终点坐标失败`);
        endPoint = null;
      }

      if (startPoint) {
        // 起点坐标映射到视口坐标
        startPoint[0] =
          (startPoint[0] - xCenter) * pxPerUnitLength + svgWidth / 2;
        startPoint[1] =
          -1 * (startPoint[1] - yCenter) * pxPerUnitLength + svgHeight / 2;
        // 进行渲染
        gNode
          .selectAll("circle.start")
          .data([startPoint])
          .enter()
          .append("circle")
          .classed("start", true)
          .attr("cx", (d) => d[0])
          .attr("cy", (d) => d[1])
          .attr("r", (pointDistance * pxPerUnitLength) / 3.5)
          .attr("fill", "rgba(50, 255, 50, 1)")
          .attr("pointer-events", "none");
      }

      if (endPoint) {
        // 终点坐标映射到视口坐标
        endPoint[0] = (endPoint[0] - xCenter) * pxPerUnitLength + svgWidth / 2;
        endPoint[1] =
          -1 * (endPoint[1] - yCenter) * pxPerUnitLength + svgHeight / 2;
        // 进行渲染
        gNode
          .selectAll("circle.end")
          .data([endPoint])
          .enter()
          .append("circle")
          .classed("end", true)
          .attr("cx", (d) => d[0])
          .attr("cy", (d) => d[1])
          .attr("r", (pointDistance * pxPerUnitLength) / 3.5)
          .attr("fill", "green")
          .attr("pointer-events", "none");
      }
    },
    onResetStartEndPoint() {
      this.formData.startPoint = "";
      this.formData.endPoint = "";
      gNode.selectAll("circle.start").remove();
      gNode.selectAll("circle.end").remove();
    },
    zoomIn(coordinate) {
      if (!Array.isArray(coordinate) || coordinate.length !== 2) {
        return;
      }
      svgNode
        .transition()
        .duration(1000)
        .call(
          zoomObj.transform,
          d3.zoomIdentity
            .translate(svgWidth / 2, svgHeight / 2)
            .scale(100 / pxPerUnitLength)
            .translate(-coordinate[0], -coordinate[1]),
        );
    },
    zoomInToStartPoint() {
      this.zoomIn(startPoint);
    },
    zoomInToEndPoint() {
      this.zoomIn(endPoint);
    },
    resetZoom() {
      svgNode
        .transition()
        .duration(1000)
        .call(zoomObj.transform, d3.zoomIdentity.scale(1));
    },
    onSetHeightFilter() {
      gNode.selectAll("rect").attr("visibility", (d) => {
        if (
          this.formData.maxHeight !== "" &&
          Number(this.formData.maxHeight) < d[2]
        ) {
          return "hidden";
        } else if (
          this.formData.minHeight !== "" &&
          Number(this.formData.minHeight) > d[2]
        ) {
          return "hidden";
        } else {
          return "visible";
        }
      });
    },
    onResetHeightFilter() {
      this.formData.maxHeight = "";
      this.formData.minHeight = "";
      gNode.selectAll("rect").attr("visibility", (d) => {
        return "visible";
      });
    },
    onAddPointUndo() {
      if (this.rawWholeDataHistory.currentIdx > 0) {
        this.rawWholeDataHistory.currentIdx--;
        rawWholeData = deepClone(
          this.rawWholeDataHistory.history[this.rawWholeDataHistory.currentIdx],
        );

        gNode.selectAll("rect").remove();
        gNode.selectAll("text").remove();
        this.renderWholePoints();
      }
    },
    onAddPointRedo() {
      if (
        this.rawWholeDataHistory.currentIdx <
        this.rawWholeDataHistory.history.length - 1
      ) {
        this.rawWholeDataHistory.currentIdx++;
        rawWholeData = deepClone(
          this.rawWholeDataHistory.history[this.rawWholeDataHistory.currentIdx],
        );

        gNode.selectAll("rect").remove();
        gNode.selectAll("text").remove();
        this.renderWholePoints();
      }
    },
    onAddPoint() {
      // 解析svg的transform信息
      let translateX = 0;
      let translateY = 0;
      let scale = 1;
      const gNodetransformStr = gNode.attr("transform");
      if (gNodetransformStr) {
        const translateStr = gNodetransformStr.split(" ")[0];
        const translateRegex = /translate\(([^,]+),([^,]+)\)/;
        const translateMatch = translateStr.match(translateRegex);

        const scaleStr = gNodetransformStr.split(" ")[1];
        const scaleRegex = /scale\(([^)]+)\)/;
        const scaleMatch = scaleStr.match(scaleRegex);

        translateX = Number(translateMatch[1]);
        translateY = Number(translateMatch[2]);
        scale = Number(scaleMatch[1]);
      }
      console.log("transform info: ", translateX, translateY, scale);

      // 选择框位置复原到svg transfrom前
      const brushLeftPxBeformTransform = (brushLeftPx - translateX) / scale;
      const brushTopPxBeformTransform = (brushTopPx - translateY) / scale;
      const brushRightPxBeformTransform = (brushRightPx - translateX) / scale;
      const brushBottomPxBeformTransform = (brushBottomPx - translateY) / scale;

      // 选择框位置复原到原始坐标系
      const brushLeft =
        (brushLeftPxBeformTransform - svgWidth / 2) / pxPerUnitLength + xCenter;
      const brushTop =
        ((brushTopPxBeformTransform - svgHeight / 2) / pxPerUnitLength) * -1 +
        yCenter;
      const brushRight =
        (brushRightPxBeformTransform - svgWidth / 2) / pxPerUnitLength +
        xCenter;
      const brushBottom =
        ((brushBottomPxBeformTransform - svgHeight / 2) / pxPerUnitLength) *
          -1 +
        yCenter;
      console.log(
        "brush area in raw coordinate: ",
        brushLeft,
        brushTop,
        brushRight,
        brushBottom,
      );

      // 计算出选择框的外围影响区域
      const affectionAreaLeft = brushLeft - pointDistance * Math.SQRT2 * 1.25;
      const affectionAreaTop = brushTop + pointDistance * Math.SQRT2 * 1.25;
      const affectionAreaRight = brushRight + pointDistance * Math.SQRT2 * 1.25;
      const affectionAreaBottom =
        brushBottom - pointDistance * Math.SQRT2 * 1.25;
      console.log(
        "affection area in raw coordinate: ",
        affectionAreaLeft,
        affectionAreaTop,
        affectionAreaRight,
        affectionAreaBottom,
      );

      // 筛选出框选区域可能影响到的所有外围点
      // 筛选规则：除了x、y坐标，还要看z坐标是否在当前连通规则给定的高度区间。
      // 筛选后，如果各已存在的点之间有重叠的，则报错，要求调整可判定连通的最大高度差。
      const affectedPointList = [];
      for (const point of rawWholeData) {
        // 如果z不合格，pass
        if (
          Math.abs(point.z - Number(this.formData.addPointHeight)) >
          Number(this.formData.connectionMaxHeightGap)
        ) {
          continue;
        }
        // 如果在affection范围外，pass
        if (
          point.x < affectionAreaLeft ||
          point.x > affectionAreaRight ||
          point.y < affectionAreaBottom ||
          point.y > affectionAreaTop
        ) {
          continue;
        }
        // 如果在框选区域内，pass
        if (
          point.x >= brushLeft &&
          point.x <= brushRight &&
          point.y <= brushTop &&
          point.y >= brushBottom
        ) {
          continue;
        }
        affectedPointList.push(point);
      }
      // todo: 如果各已存在的点之间有重叠的，则报错，要求调整可判定连通的最大高度差。
      console.log("affectedPointList: ", affectedPointList);

      // 筛选出框选区域内所有已存在的点
      // 筛选规则：除了x、y坐标，还要看z坐标是否在当前连通规则给定的高度区间。
      // 筛选后，如果各已存在的点之间有重叠的，则报错，要求调整可判定连通的最大高度差。
      const pointInBrushList = [];
      for (const point of rawWholeData) {
        // 如果z不合格，pass
        if (
          Math.abs(point.z - Number(this.formData.addPointHeight)) >
          Number(this.formData.connectionMaxHeightGap)
        ) {
          continue;
        }
        // 如果在框选区域内，留下
        if (
          point.x >= brushLeft &&
          point.x <= brushRight &&
          point.y <= brushTop &&
          point.y >= brushBottom
        ) {
          pointInBrushList.push(point);
        }
      }

      if (!pointInBrushList.length) {
        window.alert(
          "请保证：1.框选区域内至少已存在一个点位;2.该点位高度与补点高度之间差距不超过高度差上限。该点位将作为点位生长起点。",
        );
        return;
      }

      // 拿到框选区域中已存在点的datasetId（所有点都一样），并计算weight平均值
      const pointDatasetId = pointInBrushList[0].datasetId;
      let pointWeightSum = 0;
      for (const pointInBrush of pointInBrushList) {
        pointWeightSum += pointInBrush.weight;
      }
      const pointWeight = pointWeightSum / pointInBrushList.length;

      // 开始补点
      let activePointIdx = 0;
      let newPointId = rawWholeData[rawWholeData.length - 1].id;
      // 位于框选区域内的所有点位构成稀疏图模型，用邻接表表示，依次处理其顶点表中各顶点。
      while (activePointIdx <= pointInBrushList.length - 1) {
        // 拿到当前顶点
        const activePoint = pointInBrushList[activePointIdx];
        console.log("顶点表：", pointInBrushList);
        console.log("当前处理顶点Idx:", activePointIdx);
        console.log("当前处理顶点：", activePoint);

        // 当前顶点有可能是本次补点前就存在的，所以清空其邻居信息
        activePoint.ids = [];
        const tempIds1 = []; // 临时记录上下左右邻居
        const tempIds2 = []; // 临时记录斜角邻居

        // 拿到当前顶点的8个相邻点位
        const neighbourPosList = getNeighbourLocations(
          pointInBrushList[activePointIdx],
          pointDistance,
          rowSlope,
        );

        // 处理8个相邻点位
        for (const key in neighbourPosList) {
          if (Object.hasOwnProperty.call(neighbourPosList, key)) {
            // 看看这个相邻点位属于上下左右邻居还是斜角邻居。
            let neighbourType = 0;
            if (["posRight", "posBottom", "posLeft", "posTop"].includes(key)) {
              neighbourType = 1;
            } else {
              neighbourType = 2;
            }

            const neiPos = neighbourPosList[key];

            // 如果点位在框选区域外
            if (
              neiPos.x < brushLeft ||
              neiPos.x > brushRight ||
              neiPos.y > brushTop ||
              neiPos.y < brushBottom
            ) {
              // 在外围点位表中找匹配的点
              const matchedPoint = affectedPointList.find((affectedPoint) => {
                return (
                  getDistance2D(affectedPoint, neiPos) < pointDistance * 0.1
                );
              });
              // 如果找到了匹配点
              if (matchedPoint) {
                console.log(matchedPoint, "sdfsdf");
                // 记录自己与其关系。
                if (neighbourType === 1) {
                  tempIds1.push(String(matchedPoint.id));
                } else {
                  tempIds2.push(String(matchedPoint.id));
                }

                // 酌情修改这个外围点位与自己的关系。

                // 相邻点位数组ids中，前4项表示上下左右邻居，后四项表示斜角邻居。
                let idsStartIdx = -1;
                let idsEndIdx = -1;
                if (neighbourType === 1) {
                  idsStartIdx = 0;
                  idsEndIdx = 4;
                } else {
                  idsStartIdx = 4;
                  idsEndIdx = 8;
                }

                if (
                  !matchedPoint.ids.slice(idsStartIdx, idsEndIdx).find((id) => {
                    return id === String(activePoint.id);
                  })
                ) {
                  const emptyIdx = matchedPoint.ids
                    .slice(idsStartIdx, idsEndIdx)
                    .findIndex((id) => {
                      return id === "-1";
                    });
                  console.assert(emptyIdx !== -1, "emptyIdx居然不存在？！");
                  matchedPoint.ids[idsStartIdx + emptyIdx] = String(
                    activePoint.id,
                  );
                }
              }
              // 如果没找到匹配点
              else {
                // 记录自己与其关系。
                if (neighbourType === 1) {
                  tempIds1.push("-1");
                } else {
                  tempIds2.push("-1");
                }
              }
            }
            // 如果点位在框选区域内
            else {
              // 在顶点表中找匹配的点
              const matchedPoint = pointInBrushList.find((pointInBrush) => {
                return (
                  getDistance2D(pointInBrush, neiPos) < pointDistance * 0.3
                );
              });
              // 如果找到了匹配点
              if (matchedPoint) {
                // 记录自己与其关系。
                if (neighbourType === 1) {
                  tempIds1.push(String(matchedPoint.id));
                } else {
                  tempIds2.push(String(matchedPoint.id));
                }
              }
              // 如果没找到匹配点
              else {
                newPointId++;

                // 创建新点位，加入顶点表
                pointInBrushList.push({
                  id: newPointId,
                  x: neiPos.x,
                  y: neiPos.y,
                  z: Number(this.formData.addPointHeight),
                  datasetId: pointDatasetId,
                  weight: pointWeight,
                });

                // 记录自己与其关系
                if (neighbourType === 1) {
                  tempIds1.push(String(newPointId));
                } else {
                  tempIds2.push(String(newPointId));
                }
              }
            }
          }
        } // end of 处理8个相邻点位

        console.assert(tempIds1.length === 4, "tempIds1长度咋不是4呢？");
        console.assert(tempIds2.length === 4, "tempIds1长度咋不是4呢？");
        tempIds1.sort((a, b) => {
          if (a === "-1" && b === "-1") {
            return 0;
          } else if (a === "-1") {
            return 1;
          } else if (b === "-1") {
            return -1;
          } else {
            return 0;
          }
        });
        tempIds2.sort((a, b) => {
          if (a === "-1" && b === "-1") {
            return 0;
          } else if (a === "-1") {
            return 1;
          } else if (b === "-1") {
            return -1;
          } else {
            return 0;
          }
        });
        activePoint.ids = [...tempIds1, ...tempIds2];

        activePointIdx++;
      } // end of 依次处理顶点表中各顶点

      // 新补的点加入rawWholeData
      const oldIdMax = rawWholeData.length; // 旧有的点的id最大值，据此判断哪些点是新增的
      for (const pointInBrush of pointInBrushList) {
        if (pointInBrush.id > oldIdMax) {
          pointInBrush.isManuallyAdded = true;
          rawWholeData.push(pointInBrush);
        }
      }

      // 渲染
      this.renderWholePoints();

      // 存入历史记录
      if (
        this.rawWholeDataHistory.currentIdx <
        this.rawWholeDataHistory.history.length - 1
      ) {
        this.rawWholeDataHistory.history =
          this.rawWholeDataHistory.history.slice(
            0,
            this.rawWholeDataHistory.currentIdx + 1,
          );
      }
      this.rawWholeDataHistory.history.push(deepClone(rawWholeData));
      this.rawWholeDataHistory.currentIdx++;
    }, // end of method addPoint
    onClickRestore() {
      if (!this.formData.sceneCode.trim()) {
        window.alert("场景码必填！");
        return;
      }

      ElMessageBox.confirm(
        "此操作将会恢复点位数据为算法提供的初始值。",
        "禁告",
        {
          type: "warning",
          confirmButtonText: "确定",
          cancelButtonText: "取消",
        },
      )
        .then(() => {
          this.loadingHandler = ElLoading.service({
            lock: true,
            text: "Loading",
            background: "rgba(0, 0, 0, 0.7)",
          });

          return resetWholeData(this.formData.sceneCode);
        })
        .then(() => {
          return this.getWholeData();
        })
        .then(() => {
          ElMessage({
            message: "操作成功",
            type: "success",
          });
        })
        .catch((err) => {
          if (err === "cancel") {
            return;
          }
          ElMessage({
            message: err,
            type: "error",
          });
        })
        .finally(() => {
          this.loadingHandler.close();
        });
    },
    uploadAddPointResult() {
      if (!this.formData.sceneCode.trim()) {
        window.alert("场景码必填！");
        return;
      }

      this.loadingHandler = ElLoading.service({
        lock: true,
        text: "Loading",
        background: "rgba(0, 0, 0, 0.7)",
      });

      uploadWholeData(this.formData.sceneCode, rawWholeData)
        .then((res) => {
          ElMessage({
            message: res,
            type: "success",
          });
        })
        .catch((err) => {
          ElMessage({
            message: err,
            type: "error",
          });
        })
        .finally(() => {
          this.loadingHandler.close();
        });
    },
    onPointEditorConfirm(data) {
      rawWholeData[data.id - 1].x = data.x;
      rawWholeData[data.id - 1].y = data.y;
      rawWholeData[data.id - 1].z = data.z;
      rawWholeData[data.id - 1].weight = data.weight;
      rawWholeData[data.id - 1].ids = data.ids;

      // 渲染
      gNode.selectAll("rect").remove();
      gNode.selectAll("text").remove();
      this.renderWholePoints();

      gNode.select(`rect[raw-id='${data.id}']`).dispatch("click");

      // 存入历史记录
      if (
        this.rawWholeDataHistory.currentIdx <
        this.rawWholeDataHistory.history.length - 1
      ) {
        this.rawWholeDataHistory.history =
          this.rawWholeDataHistory.history.slice(
            0,
            this.rawWholeDataHistory.currentIdx + 1,
          );
      }
      this.rawWholeDataHistory.history.push(deepClone(rawWholeData));
      this.rawWholeDataHistory.currentIdx++;

      this.isEditingPoint = false;
    },
    onPointEditorDelete() {
      console.error(this.pointDataForEditor);
      rawWholeData[this.pointDataForEditor.id - 1].ids = [
        "-1",
        "-1",
        "-1",
        "-1",
        "-1",
        "-1",
        "-1",
        "-1",
      ];

      rawWholeData.forEach((item, index) => {
        if (item.ids.includes(String(this.pointDataForEditor.id))) {
          item.ids = item.ids.map((id) => {
            if (id === String(this.pointDataForEditor.id)) {
              return "-1";
            } else {
              return id;
            }
          });
        }
      });

      // 渲染
      gNode.selectAll("rect").remove();
      gNode.selectAll("text").remove();
      this.renderWholePoints();

      // 存入历史记录
      if (
        this.rawWholeDataHistory.currentIdx <
        this.rawWholeDataHistory.history.length - 1
      ) {
        this.rawWholeDataHistory.history =
          this.rawWholeDataHistory.history.slice(
            0,
            this.rawWholeDataHistory.currentIdx + 1,
          );
      }
      this.rawWholeDataHistory.history.push(deepClone(rawWholeData));
      this.rawWholeDataHistory.currentIdx++;

      this.isEditingPoint = false;
    },
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}
.formsWrapper {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.formsWrapper .form1 {
  width: 25vw;
}
.formsWrapper .form1 .color-desc {
  margin-top: 10px;
  font-size: 12px;
}
.formsWrapper .form2 {
  width: 45vw;
}
.formsWrapper .form3 {
  width: 25vw;
}
.infoText {
  min-height: 2em;
}
.map {
  display: flex;
  align-items: center;
}
.map > .map-control-area {
  margin-left: 20px;
}
.map > .map-control-area > .panel {
  border: 1px solid black;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  margin-bottom: 10px;
}
.map > .map-control-area > .panel > .btn {
  margin: 5px;
}
.map > .map-control-area > .panel {
  padding-left: 10px;
  padding-right: 10px;
  padding-bottom: 10px;
}
.map > .map-control-area > .panel input {
  width: 80px;
}
.map > .map-control-area > .panel > .btn-group {
  display: flex;
  justify-content: space-between;
}
.map > .map-control-area > .add-point.panel > .btn-group {
  margin-bottom: 10px;
}
.map > .map-control-area > .edit-button-group > .button-row {
  margin-bottom: 10px;
  display: flex;
  justify-content: space-evenly;
}
.map > .map-control-area > .edit-button-group > .button-row > .btn {
}

.svgWrapper {
  display: inline-block;
  overflow: hidden;
  background: #eee;
}
</style>
