<script lang="ts" setup>
import { computed, onBeforeUnmount, reactive, ref } from "vue";

type DeviceStatus = "空闲" | "离线" | "充电中";
type PatrolPointType = "常规巡航" | "热成像" | "泵房复核";
type PatrolTaskMode = "定时" | "周期" | "临时";
type EditorMode = "select" | "normal" | "thermal" | "pump";
type ConfigSideTab = "point" | "plan";

interface DeviceRecord {
  id: string;
  name: string;
  battery: string;
  status: DeviceStatus;
  project: string;
  gimbalSerial: string;
  createdAt: string;
}

interface DeviceFormState {
  id: string;
  name: string;
  battery: string;
  status: DeviceStatus;
  project: string;
  gimbalSerial: string;
  createdAt: string;
}

interface MapRecord {
  id: string;
  name: string;
  version: string;
  coverage: string;
  updatedAt: string;
  pointCloudCount: number;
}

interface PatrolPoint {
  id: string;
  name: string;
  type: PatrolPointType;
  x: number;
  y: number;
  routeSpeed: string;
  stopDuration: string;
  liftHeight: string;
  gimbalPreset: string;
  note: string;
}

interface PatrolTask {
  id: string;
  name: string;
  mode: PatrolTaskMode;
  time: string;
  cycle: string;
  routeRange: string;
  enabled: boolean;
  updatedAt: string;
}

interface DeviceConfigState {
  maps: MapRecord[];
  activeMapId: string;
  mapLoaded: boolean;
  mapSyncing: boolean;
  syncProgress: number;
  lastSyncedAt: string;
  points: PatrolPoint[];
  selectedPointIds: string[];
  plans: PatrolTask[];
}

interface PlanFormState {
  name: string;
  mode: PatrolTaskMode;
  time: string;
  cycle: string;
  routeRange: string;
  enabled: boolean;
}

interface BatchFormState {
  routeSpeed: string;
  stopDuration: string;
  liftHeight: string;
  gimbalPreset: string;
}

const statusOptions: DeviceStatus[] = ["空闲", "离线", "充电中"];
const projectOptions = ["西江隧道", "青云隧道", "南山隧道"];
const pointTypeOptions: PatrolPointType[] = ["常规巡航", "热成像", "泵房复核"];
const patrolModeOptions: PatrolTaskMode[] = ["定时", "周期", "临时"];

const editorModes: Array<{ id: EditorMode; label: string }> = [
  { id: "select", label: "框选 / 多选" },
  { id: "normal", label: "新增常规点" },
  { id: "thermal", label: "新增热成像点" },
  { id: "pump", label: "新增泵房点" },
];

const devices = ref<DeviceRecord[]>([
  {
    id: "ROBOT-001",
    name: "巡检机器人 R1",
    battery: "82%",
    status: "空闲",
    project: "西江隧道",
    gimbalSerial: "YPT-R1-24001",
    createdAt: "2026-04-11 09:30:22",
  },
  {
    id: "ROBOT-002",
    name: "巡检机器人 R2",
    battery: "76%",
    status: "空闲",
    project: "西江隧道",
    gimbalSerial: "YPT-R2-24002",
    createdAt: "2026-04-12 13:18:45",
  },
  {
    id: "ROBOT-003",
    name: "巡检机器人 R3",
    battery: "48%",
    status: "充电中",
    project: "西江隧道",
    gimbalSerial: "YPT-R3-24003",
    createdAt: "2026-04-15 10:42:10",
  },
  {
    id: "ROBOT-004",
    name: "巡检机器人 R4",
    battery: "91%",
    status: "空闲",
    project: "西江隧道",
    gimbalSerial: "YPT-R4-24004",
    createdAt: "2026-04-18 08:25:37",
  },
  {
    id: "ROBOT-101",
    name: "巡检机器人 Q1",
    battery: "88%",
    status: "空闲",
    project: "青云隧道",
    gimbalSerial: "YPT-Q1-24101",
    createdAt: "2026-04-19 14:06:51",
  },
  {
    id: "ROBOT-102",
    name: "巡检机器人 Q2",
    battery: "12%",
    status: "离线",
    project: "青云隧道",
    gimbalSerial: "YPT-Q2-24102",
    createdAt: "2026-04-21 11:09:26",
  },
  {
    id: "ROBOT-103",
    name: "巡检机器人 Q3",
    battery: "67%",
    status: "空闲",
    project: "青云隧道",
    gimbalSerial: "YPT-Q3-24103",
    createdAt: "2026-04-23 16:33:12",
  },
  {
    id: "ROBOT-104",
    name: "巡检机器人 Q4",
    battery: "39%",
    status: "充电中",
    project: "青云隧道",
    gimbalSerial: "YPT-Q4-24104",
    createdAt: "2026-04-25 09:56:08",
  },
  {
    id: "ROBOT-201",
    name: "巡检机器人 N1",
    battery: "95%",
    status: "空闲",
    project: "南山隧道",
    gimbalSerial: "YPT-N1-24201",
    createdAt: "2026-04-27 15:20:49",
  },
  {
    id: "ROBOT-202",
    name: "巡检机器人 N2",
    battery: "73%",
    status: "空闲",
    project: "南山隧道",
    gimbalSerial: "YPT-N2-24202",
    createdAt: "2026-04-28 10:14:33",
  },
  {
    id: "ROBOT-203",
    name: "巡检机器人 N3",
    battery: "64%",
    status: "空闲",
    project: "南山隧道",
    gimbalSerial: "YPT-N3-24203",
    createdAt: "2026-05-02 17:08:54",
  },
  {
    id: "ROBOT-204",
    name: "巡检机器人 N4",
    battery: "44%",
    status: "充电中",
    project: "南山隧道",
    gimbalSerial: "YPT-N4-24204",
    createdAt: "2026-05-05 08:48:19",
  },
]);

const pageSize = 6;
const currentPage = ref(1);
const keyword = ref("");
const dialogVisible = ref(false);
const configDialogVisible = ref(false);
const editingId = ref<string | null>(null);
const configuringDeviceId = ref<string | null>(null);
const editingPlanId = ref<string | null>(null);
const formError = ref("");
const mapEditorMode = ref<EditorMode>("select");
const activeConfigSideTab = ref<ConfigSideTab>("point");
const mapStageRef = ref<HTMLElement | null>(null);
const suppressStageClick = ref(false);

const form = reactive<DeviceFormState>({
  id: "",
  name: "",
  battery: "",
  status: "空闲",
  project: projectOptions[0],
  gimbalSerial: "",
  createdAt: "",
});

const planForm = reactive<PlanFormState>({
  name: "",
  mode: "定时",
  time: "02:00",
  cycle: "每日",
  routeRange: "全线路",
  enabled: true,
});

const batchForm = reactive<BatchFormState>({
  routeSpeed: "",
  stopDuration: "",
  liftHeight: "",
  gimbalPreset: "",
});

const selectionBox = reactive({
  active: false,
  moved: false,
  startX: 0,
  startY: 0,
  endX: 0,
  endY: 0,
});

let syncTimer: number | null = null;

function nowString() {
  return new Date().toLocaleString("zh-CN", { hour12: false });
}

function buildDeviceMaps(project: string): MapRecord[] {
  return [
    {
      id: `${project}-main`,
      name: `${project} 主通道环境地图`,
      version: "v3.4",
      coverage: "0m - 2200m",
      updatedAt: "2026-06-25 21:40:18",
      pointCloudCount: 1648,
    },
    {
      id: `${project}-equipment`,
      name: `${project} 设备区补充地图`,
      version: "v1.8",
      coverage: "泵房 / 风机 / 配电间",
      updatedAt: "2026-06-24 17:16:05",
      pointCloudCount: 932,
    },
  ];
}

function buildDevicePoints(device: DeviceRecord): PatrolPoint[] {
  return [
    {
      id: `${device.id}-P01`,
      name: `${device.project} 入口巡航点`,
      type: "常规巡航",
      x: 12,
      y: 62,
      routeSpeed: "0.42",
      stopDuration: "8",
      liftHeight: "1350",
      gimbalPreset: "P-01",
      note: "入口洞门全景与车道观察",
    },
    {
      id: `${device.id}-P02`,
      name: `${device.project} 热点复核点`,
      type: "热成像",
      x: 38,
      y: 44,
      routeSpeed: "0.28",
      stopDuration: "12",
      liftHeight: "1680",
      gimbalPreset: "T-03",
      note: "热成像复核桥架及电缆温升",
    },
    {
      id: `${device.id}-P03`,
      name: `${device.project} 泵房设备点`,
      type: "泵房复核",
      x: 66,
      y: 56,
      routeSpeed: "0.24",
      stopDuration: "18",
      liftHeight: "1820",
      gimbalPreset: "G-07",
      note: "停留采集泵房铭牌与压力表",
    },
    {
      id: `${device.id}-P04`,
      name: `${device.project} 终点回库点`,
      type: "常规巡航",
      x: 84,
      y: 34,
      routeSpeed: "0.35",
      stopDuration: "6",
      liftHeight: "1260",
      gimbalPreset: "P-09",
      note: "回库前执行末端巡检确认",
    },
  ];
}

function buildDevicePlans(device: DeviceRecord): PatrolTask[] {
  return [
    {
      id: `${device.id}-TASK01`,
      name: `${device.project} 日常定时巡检`,
      mode: "定时",
      time: "02:00",
      cycle: "每日",
      routeRange: "入口至中段",
      enabled: true,
      updatedAt: "2026-06-26 08:10:00",
    },
    {
      id: `${device.id}-TASK02`,
      name: `${device.project} 夜间设备复核`,
      mode: "周期",
      time: "22:30",
      cycle: "每2小时",
      routeRange: "泵房 / 配电区",
      enabled: true,
      updatedAt: "2026-06-25 18:24:36",
    },
  ];
}

function buildDeviceConfig(device: DeviceRecord): DeviceConfigState {
  const maps = buildDeviceMaps(device.project);
  const points = buildDevicePoints(device);

  return {
    maps,
    activeMapId: maps[0].id,
    mapLoaded: true,
    mapSyncing: false,
    syncProgress: 100,
    lastSyncedAt: "2026-06-26 09:15:08",
    points,
    selectedPointIds: [points[0].id],
    plans: buildDevicePlans(device),
  };
}

const deviceConfigs = reactive<Record<string, DeviceConfigState>>(
  Object.fromEntries(
    devices.value.map((device) => [device.id, buildDeviceConfig(device)]),
  ),
);

const filteredDevices = computed(() => {
  const search = keyword.value.trim().toLowerCase();

  return devices.value.filter((device) => {
    const matchKeyword =
      !search ||
      [device.id, device.name, device.gimbalSerial, device.project]
        .join(" ")
        .toLowerCase()
        .includes(search);

    return matchKeyword;
  });
});

const totalPages = computed(() =>
  Math.max(1, Math.ceil(filteredDevices.value.length / pageSize)),
);

const pagedDevices = computed(() => {
  const start = (currentPage.value - 1) * pageSize;
  return filteredDevices.value.slice(start, start + pageSize);
});

const visiblePages = computed(() => {
  const pages: number[] = [];
  const start = Math.max(1, currentPage.value - 2);
  const end = Math.min(totalPages.value, start + 4);

  for (let page = start; page <= end; page += 1) {
    pages.push(page);
  }

  return pages;
});

const currentConfiguredDevice = computed(
  () =>
    devices.value.find((device) => device.id === configuringDeviceId.value) ??
    null,
);

const currentConfig = computed(() =>
  configuringDeviceId.value
    ? (deviceConfigs[configuringDeviceId.value] ?? null)
    : null,
);

const activeMap = computed(
  () =>
    currentConfig.value?.maps.find(
      (map) => map.id === currentConfig.value?.activeMapId,
    ) ?? null,
);

const selectedPoints = computed(() => {
  if (!currentConfig.value) {
    return [];
  }

  return currentConfig.value.points.filter((point) =>
    currentConfig.value?.selectedPointIds.includes(point.id),
  );
});

const singleSelectedPoint = computed(() =>
  selectedPoints.value.length === 1 ? selectedPoints.value[0] : null,
);

const selectionRectStyle = computed(() => {
  const left = Math.min(selectionBox.startX, selectionBox.endX);
  const top = Math.min(selectionBox.startY, selectionBox.endY);
  const width = Math.abs(selectionBox.endX - selectionBox.startX);
  const height = Math.abs(selectionBox.endY - selectionBox.startY);

  return {
    left: `${left}%`,
    top: `${top}%`,
    width: `${width}%`,
    height: `${height}%`,
  };
});

function resetForm() {
  form.id = "";
  form.name = "";
  form.battery = "";
  form.status = "空闲";
  form.project = projectOptions[0];
  form.gimbalSerial = "";
  form.createdAt = "";
  formError.value = "";
}

function resetPlanForm() {
  editingPlanId.value = null;
  planForm.name = "";
  planForm.mode = "定时";
  planForm.time = "02:00";
  planForm.cycle = "每日";
  planForm.routeRange = "全线路";
  planForm.enabled = true;
}

function openCreateDialog() {
  editingId.value = null;
  resetForm();
  dialogVisible.value = true;
}

function openEditDialog(device: DeviceRecord) {
  editingId.value = device.id;
  form.id = device.id;
  form.name = device.name;
  form.battery = device.battery;
  form.status = device.status;
  form.project = device.project;
  form.gimbalSerial = device.gimbalSerial;
  form.createdAt = device.createdAt;
  formError.value = "";
  dialogVisible.value = true;
}

function openConfigDialog(device: DeviceRecord) {
  const config = deviceConfigs[device.id] ?? buildDeviceConfig(device);

  deviceConfigs[device.id] = config;
  configuringDeviceId.value = device.id;
  configDialogVisible.value = true;
  mapEditorMode.value = "select";
  activeConfigSideTab.value = "point";

  if (!config.selectedPointIds.length && config.points.length) {
    config.selectedPointIds = [config.points[0].id];
  }

  syncBatchForm();
  resetPlanForm();
}

function closeDialog() {
  dialogVisible.value = false;
  formError.value = "";
}

function closeConfigDialog() {
  configDialogVisible.value = false;
  configuringDeviceId.value = null;
  clearSelectionBox();
  stopMapSyncTimer();
}

function normalizeBattery(value: string) {
  const digits = value.replace(/[^\d]/g, "");
  if (!digits) {
    return "";
  }

  const numericValue = Math.min(100, Math.max(0, Number(digits)));
  return `${numericValue}%`;
}

function buildNextId() {
  const maxId = devices.value.reduce((maxValue, device) => {
    const numericPart = Number(device.id.replace(/\D/g, ""));
    return Number.isNaN(numericPart)
      ? maxValue
      : Math.max(maxValue, numericPart);
  }, 0);

  return `ROBOT-${String(maxId + 1).padStart(3, "0")}`;
}

function saveDevice() {
  const normalizedBattery = normalizeBattery(form.battery);

  if (!form.name.trim()) {
    formError.value = "请填写设备名称";
    return;
  }

  if (!normalizedBattery) {
    formError.value = "请填写当前电量";
    return;
  }

  if (!form.gimbalSerial.trim()) {
    formError.value = "请填写云台序列号";
    return;
  }

  const payload: DeviceRecord = {
    id: editingId.value ?? buildNextId(),
    name: form.name.trim(),
    battery: normalizedBattery,
    status: form.status,
    project: form.project,
    gimbalSerial: form.gimbalSerial.trim(),
    createdAt: form.createdAt || nowString(),
  };

  if (editingId.value) {
    devices.value = devices.value.map((device) =>
      device.id === editingId.value ? payload : device,
    );
  } else {
    devices.value = [payload, ...devices.value];
    deviceConfigs[payload.id] = buildDeviceConfig(payload);
  }

  closeDialog();
  currentPage.value = 1;
}

function removeDevice(id: string) {
  if (!window.confirm("确认删除该设备吗？")) {
    return;
  }

  devices.value = devices.value.filter((device) => device.id !== id);
  delete deviceConfigs[id];

  if (configuringDeviceId.value === id) {
    closeConfigDialog();
  }

  if (currentPage.value > totalPages.value) {
    currentPage.value = totalPages.value;
  }
}

function setPage(page: number) {
  currentPage.value = page;
}

function handleFilterChange() {
  currentPage.value = 1;
}

function getStatusClass(status: DeviceStatus) {
  const classMap: Record<DeviceStatus, string> = {
    空闲: "status-idle",
    离线: "status-offline",
    充电中: "status-charging",
  };

  return classMap[status];
}

function getPointTypeClass(type: PatrolPointType) {
  const classMap: Record<PatrolPointType, string> = {
    常规巡航: "type-normal",
    热成像: "type-thermal",
    泵房复核: "type-pump",
  };

  return classMap[type];
}

function syncBatchForm() {
  if (selectedPoints.value.length < 2) {
    batchForm.routeSpeed = "";
    batchForm.stopDuration = "";
    batchForm.liftHeight = "";
    batchForm.gimbalPreset = "";
    return;
  }

  const fields: Array<keyof BatchFormState> = [
    "routeSpeed",
    "stopDuration",
    "liftHeight",
    "gimbalPreset",
  ];

  fields.forEach((field) => {
    const values = selectedPoints.value.map((point) => point[field]);
    batchForm[field] = values.every((value) => value === values[0])
      ? values[0]
      : "";
  });
}

function updateSelection(nextIds: string[]) {
  if (!currentConfig.value) {
    return;
  }

  currentConfig.value.selectedPointIds = nextIds;
  syncBatchForm();
}

function selectMap(mapId: string) {
  if (!currentConfig.value) {
    return;
  }

  currentConfig.value.activeMapId = mapId;
}

function stopMapSyncTimer() {
  if (syncTimer !== null) {
    window.clearInterval(syncTimer);
    syncTimer = null;
  }
}

function syncMapFromDevice() {
  if (!currentConfig.value || currentConfig.value.mapSyncing) {
    return;
  }

  stopMapSyncTimer();
  currentConfig.value.mapSyncing = true;
  currentConfig.value.syncProgress = 0;

  syncTimer = window.setInterval(() => {
    if (!currentConfig.value) {
      stopMapSyncTimer();
      return;
    }

    const nextProgress = Math.min(100, currentConfig.value.syncProgress + 12);
    currentConfig.value.syncProgress = nextProgress;

    if (nextProgress >= 100) {
      currentConfig.value.mapSyncing = false;
      currentConfig.value.mapLoaded = true;
      currentConfig.value.lastSyncedAt = nowString();

      const targetMap = currentConfig.value.maps.find(
        (map) => map.id === currentConfig.value?.activeMapId,
      );

      if (targetMap) {
        targetMap.updatedAt = currentConfig.value.lastSyncedAt;
        targetMap.version = `v${(Number(targetMap.version.replace(/[^\d.]/g, "")) + 0.1).toFixed(1)}`;
        targetMap.pointCloudCount += 36;
      }

      stopMapSyncTimer();
    }
  }, 160);
}

function getRelativePosition(event: MouseEvent) {
  const rect = mapStageRef.value?.getBoundingClientRect();
  if (!rect) {
    return null;
  }

  const x = ((event.clientX - rect.left) / rect.width) * 100;
  const y = ((event.clientY - rect.top) / rect.height) * 100;

  return {
    x: Math.min(96, Math.max(4, Number(x.toFixed(2)))),
    y: Math.min(92, Math.max(8, Number(y.toFixed(2)))),
  };
}

function clearSelectionBox() {
  selectionBox.active = false;
  selectionBox.moved = false;
  selectionBox.startX = 0;
  selectionBox.startY = 0;
  selectionBox.endX = 0;
  selectionBox.endY = 0;
}

function handleMapPointerDown(event: MouseEvent) {
  if (!currentConfig.value?.mapLoaded || mapEditorMode.value !== "select") {
    return;
  }

  const position = getRelativePosition(event);
  if (!position) {
    return;
  }

  selectionBox.active = true;
  selectionBox.moved = false;
  selectionBox.startX = position.x;
  selectionBox.startY = position.y;
  selectionBox.endX = position.x;
  selectionBox.endY = position.y;
}

function handleMapPointerMove(event: MouseEvent) {
  if (!selectionBox.active) {
    return;
  }

  const position = getRelativePosition(event);
  if (!position) {
    return;
  }

  selectionBox.endX = position.x;
  selectionBox.endY = position.y;

  if (
    Math.abs(selectionBox.endX - selectionBox.startX) > 0.8 ||
    Math.abs(selectionBox.endY - selectionBox.startY) > 0.8
  ) {
    selectionBox.moved = true;
  }
}

function handleMapPointerUp(event: MouseEvent) {
  if (!selectionBox.active || !currentConfig.value) {
    return;
  }

  if (selectionBox.moved) {
    const left = Math.min(selectionBox.startX, selectionBox.endX);
    const right = Math.max(selectionBox.startX, selectionBox.endX);
    const top = Math.min(selectionBox.startY, selectionBox.endY);
    const bottom = Math.max(selectionBox.startY, selectionBox.endY);

    const hits = currentConfig.value.points
      .filter(
        (point) =>
          point.x >= left &&
          point.x <= right &&
          point.y >= top &&
          point.y <= bottom,
      )
      .map((point) => point.id);

    const merged = event.shiftKey
      ? Array.from(new Set([...currentConfig.value.selectedPointIds, ...hits]))
      : hits;

    updateSelection(merged);
    suppressStageClick.value = true;
  }

  clearSelectionBox();
}

function buildPointTypeFromMode(mode: EditorMode): PatrolPointType {
  if (mode === "thermal") {
    return "热成像";
  }

  if (mode === "pump") {
    return "泵房复核";
  }

  return "常规巡航";
}

function buildPointName(type: PatrolPointType) {
  const prefixMap: Record<PatrolPointType, string> = {
    常规巡航: "常规巡航点",
    热成像: "热成像点",
    泵房复核: "泵房复核点",
  };

  return `${prefixMap[type]} ${String((currentConfig.value?.points.length ?? 0) + 1).padStart(2, "0")}`;
}

function buildNextPointId() {
  if (!currentConfig.value) {
    return `${currentConfiguredDevice.value?.id ?? "ROBOT"}-P01`;
  }

  const maxIndex = currentConfig.value.points.reduce((maxValue, point) => {
    const match = point.id.match(/P(\d+)$/);
    return Math.max(maxValue, match ? Number(match[1]) : 0);
  }, 0);

  return `${currentConfiguredDevice.value?.id ?? "ROBOT"}-P${String(maxIndex + 1).padStart(2, "0")}`;
}

function buildNextPlanId() {
  if (!currentConfig.value) {
    return `${currentConfiguredDevice.value?.id ?? "ROBOT"}-TASK01`;
  }

  const maxIndex = currentConfig.value.plans.reduce((maxValue, plan) => {
    const match = plan.id.match(/TASK(\d+)$/);
    return Math.max(maxValue, match ? Number(match[1]) : 0);
  }, 0);

  return `${currentConfiguredDevice.value?.id ?? "ROBOT"}-TASK${String(maxIndex + 1).padStart(2, "0")}`;
}

function handleMapStageClick(event: MouseEvent) {
  if (suppressStageClick.value) {
    suppressStageClick.value = false;
    return;
  }

  if (!currentConfig.value?.mapLoaded) {
    return;
  }

  if (mapEditorMode.value === "select") {
    updateSelection([]);
    return;
  }

  const position = getRelativePosition(event);
  if (!position) {
    return;
  }

  const type = buildPointTypeFromMode(mapEditorMode.value);
  const point: PatrolPoint = {
    id: buildNextPointId(),
    name: buildPointName(type),
    type,
    x: position.x,
    y: position.y,
    routeSpeed: "0.30",
    stopDuration: "10",
    liftHeight: "1500",
    gimbalPreset: "P-00",
    note: "新建点位，待补充巡检说明",
  };

  currentConfig.value.points.push(point);
  updateSelection([point.id]);
}

function handlePointClick(pointId: string, event: MouseEvent) {
  if (!currentConfig.value) {
    return;
  }

  if (event.shiftKey || event.ctrlKey || event.metaKey) {
    const selected = currentConfig.value.selectedPointIds.includes(pointId)
      ? currentConfig.value.selectedPointIds.filter((id) => id !== pointId)
      : [...currentConfig.value.selectedPointIds, pointId];

    updateSelection(selected);
    return;
  }

  updateSelection([pointId]);
}

function clearPointSelection() {
  updateSelection([]);
}

function removeSelectedPoints() {
  if (!currentConfig.value || !currentConfig.value.selectedPointIds.length) {
    return;
  }

  currentConfig.value.points = currentConfig.value.points.filter(
    (point) => !currentConfig.value?.selectedPointIds.includes(point.id),
  );
  updateSelection([]);
}

function applyBatchConfig() {
  if (!currentConfig.value || selectedPoints.value.length < 2) {
    return;
  }

  currentConfig.value.points = currentConfig.value.points.map((point) => {
    if (!currentConfig.value?.selectedPointIds.includes(point.id)) {
      return point;
    }

    return {
      ...point,
      routeSpeed: batchForm.routeSpeed || point.routeSpeed,
      stopDuration: batchForm.stopDuration || point.stopDuration,
      liftHeight: batchForm.liftHeight || point.liftHeight,
      gimbalPreset: batchForm.gimbalPreset || point.gimbalPreset,
    };
  });

  syncBatchForm();
}

function loadPlan(plan: PatrolTask) {
  editingPlanId.value = plan.id;
  planForm.name = plan.name;
  planForm.mode = plan.mode;
  planForm.time = plan.time;
  planForm.cycle = plan.cycle;
  planForm.routeRange = plan.routeRange;
  planForm.enabled = plan.enabled;
}

function savePlan() {
  if (!currentConfig.value || !planForm.name.trim()) {
    return;
  }

  const payload: PatrolTask = {
    id: editingPlanId.value ?? buildNextPlanId(),
    name: planForm.name.trim(),
    mode: planForm.mode,
    time: planForm.time.trim() || "--:--",
    cycle: planForm.cycle.trim() || "手动触发",
    routeRange: planForm.routeRange.trim() || "未指定路线",
    enabled: planForm.enabled,
    updatedAt: nowString(),
  };

  if (editingPlanId.value) {
    currentConfig.value.plans = currentConfig.value.plans.map((plan) =>
      plan.id === editingPlanId.value ? payload : plan,
    );
  } else {
    currentConfig.value.plans = [payload, ...currentConfig.value.plans];
  }

  resetPlanForm();
}

function removePlan(planId: string) {
  if (!currentConfig.value) {
    return;
  }

  currentConfig.value.plans = currentConfig.value.plans.filter(
    (plan) => plan.id !== planId,
  );

  if (editingPlanId.value === planId) {
    resetPlanForm();
  }
}

onBeforeUnmount(() => {
  stopMapSyncTimer();
});
</script>

<template>
  <div class="page-shell management-page">
    <div class="page-grid management-layout">
      <section class="page-card management-table-card">
        <div class="management-toolbar">
          <div class="toolbar-left">
            <button
              class="page-action-btn primary"
              type="button"
              @click="openCreateDialog"
            >
              新增设备
            </button>
            <input
              v-model="keyword"
              class="toolbar-input"
              placeholder="搜索设备编号 / 名称 / 云台序列号"
              type="text"
              @input="handleFilterChange"
            />
          </div>
          <div class="toolbar-right">
            共 {{ filteredDevices.length }} 台设备
          </div>
        </div>

        <div class="table-shell">
          <table class="device-table">
            <thead>
              <tr>
                <th>设备编号</th>
                <th>设备名称</th>
                <th>所属项目</th>
                <th>当前电量</th>
                <th>当前状态</th>
                <th>云台序列号</th>
                <th>创建时间</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="device in pagedDevices" :key="device.id">
                <td>{{ device.id }}</td>
                <td>{{ device.name }}</td>
                <td>{{ device.project }}</td>
                <td>{{ device.battery }}</td>
                <td>
                  <span
                    :class="['table-status', getStatusClass(device.status)]"
                  >
                    {{ device.status }}
                  </span>
                </td>
                <td>{{ device.gimbalSerial }}</td>
                <td>{{ device.createdAt }}</td>
                <td>
                  <div class="table-actions">
                    <button
                      class="config-action"
                      type="button"
                      @click="openConfigDialog(device)"
                    >
                      配置
                    </button>
                    <button type="button" @click="openEditDialog(device)">
                      编辑
                    </button>
                    <button
                      class="danger"
                      type="button"
                      @click="removeDevice(device.id)"
                    >
                      删除
                    </button>
                  </div>
                </td>
              </tr>
              <tr v-if="!pagedDevices.length">
                <td class="table-empty" colspan="8">
                  当前筛选条件下暂无设备数据
                </td>
              </tr>
            </tbody>
          </table>
        </div>

        <div class="pagination-bar">
          <button
            class="pagination-btn"
            :disabled="currentPage === 1"
            type="button"
            @click="setPage(currentPage - 1)"
          >
            上一页
          </button>
          <button
            v-for="page in visiblePages"
            :key="page"
            :class="['pagination-btn', { active: currentPage === page }]"
            type="button"
            @click="setPage(page)"
          >
            {{ page }}
          </button>
          <button
            class="pagination-btn"
            :disabled="currentPage === totalPages"
            type="button"
            @click="setPage(currentPage + 1)"
          >
            下一页
          </button>
        </div>
      </section>
    </div>

    <div
      v-if="dialogVisible"
      class="crud-dialog-mask"
      @click.self="closeDialog"
    >
      <div class="crud-dialog">
        <div class="crud-dialog-header">
          <div class="crud-dialog-title">
            {{ editingId ? "编辑设备" : "新增设备" }}
          </div>
          <button class="dialog-close" type="button" @click="closeDialog">
            ×
          </button>
        </div>
        <div class="crud-form-grid">
          <label class="crud-field">
            <span>设备名称</span>
            <input v-model="form.name" type="text" />
          </label>
          <label class="crud-field">
            <span>当前电量</span>
            <input v-model="form.battery" placeholder="例如 82%" type="text" />
          </label>
          <label class="crud-field">
            <span>当前状态</span>
            <select v-model="form.status">
              <option
                v-for="status in statusOptions"
                :key="status"
                :value="status"
              >
                {{ status }}
              </option>
            </select>
          </label>
          <label class="crud-field">
            <span>所属项目</span>
            <select v-model="form.project">
              <option
                v-for="project in projectOptions"
                :key="project"
                :value="project"
              >
                {{ project }}
              </option>
            </select>
          </label>
          <label class="crud-field">
            <span>云台序列号</span>
            <input v-model="form.gimbalSerial" type="text" />
          </label>
          <label class="crud-field">
            <span>创建时间</span>
            <input
              v-model="form.createdAt"
              placeholder="留空则自动生成"
              type="text"
            />
          </label>
        </div>
        <div v-if="formError" class="crud-form-error">{{ formError }}</div>
        <div class="crud-dialog-footer">
          <button
            class="page-action-btn ghost"
            type="button"
            @click="closeDialog"
          >
            取消
          </button>
          <button
            class="page-action-btn primary"
            type="button"
            @click="saveDevice"
          >
            保存
          </button>
        </div>
      </div>
    </div>

    <div
      v-if="configDialogVisible && currentConfiguredDevice && currentConfig"
      class="config-dialog-mask"
      @click.self="closeConfigDialog"
    >
      <div class="config-dialog">
        <div class="config-dialog-header">
          <div class="config-header-main">
            <div class="config-title-row">
              <h3 class="config-dialog-title">机器人配套配置</h3>
              <span class="config-device-chip">
                {{ currentConfiguredDevice.name }} /
                {{ currentConfiguredDevice.id }}
              </span>
            </div>
            <div class="config-header-meta">
              <span>所属项目：{{ currentConfiguredDevice.project }}</span>
              <span>地图最近同步：{{ currentConfig.lastSyncedAt }}</span>
              <span
                >云台序列号：{{ currentConfiguredDevice.gimbalSerial }}</span
              >
            </div>
          </div>
          <button class="dialog-close" type="button" @click="closeConfigDialog">
            ×
          </button>
        </div>

        <div class="config-dialog-body">
          <section class="config-map-panel">
            <div class="panel-head">
              <div>
                <div class="panel-title">地图编辑</div>
                <div class="panel-subtitle">
                  从机器人本地地图加载环境点云，完成巡航点标注、框选和参数编辑
                </div>
              </div>
              <div class="panel-actions">
                <button
                  class="page-action-btn primary"
                  type="button"
                  @click="syncMapFromDevice"
                >
                  {{
                    currentConfig.mapSyncing
                      ? "地图同步中..."
                      : "从本地设备拉取地图"
                  }}
                </button>
              </div>
            </div>

            <div class="editor-toolbar">
              <button
                v-for="mode in editorModes"
                :key="mode.id"
                :class="[
                  'editor-tool-btn',
                  { active: mapEditorMode === mode.id },
                ]"
                type="button"
                @click="mapEditorMode = mode.id"
              >
                {{ mode.label }}
              </button>
              <div class="editor-tip">
                提示：点击画布新增点位；拖拽框选点位；`Shift` 可追加多选
              </div>
            </div>

            <div class="config-map-stage" ref="mapStageRef">
              <svg
                class="config-map-svg"
                viewBox="0 0 100 100"
                preserveAspectRatio="none"
              >
                <defs>
                  <linearGradient
                    id="routeGlow"
                    x1="0%"
                    y1="0%"
                    x2="100%"
                    y2="0%"
                  >
                    <stop
                      offset="0%"
                      stop-color="#3cc7ff"
                      stop-opacity="0.12"
                    />
                    <stop
                      offset="48%"
                      stop-color="#8ff3ff"
                      stop-opacity="0.9"
                    />
                    <stop
                      offset="100%"
                      stop-color="#3cc7ff"
                      stop-opacity="0.12"
                    />
                  </linearGradient>
                </defs>
                <g class="map-grid">
                  <line
                    v-for="line in 9"
                    :key="`h-${line}`"
                    x1="0"
                    :y1="line * 10"
                    x2="100"
                    :y2="line * 10"
                  />
                  <line
                    v-for="line in 9"
                    :key="`v-${line}`"
                    :x1="line * 10"
                    y1="0"
                    :x2="line * 10"
                    y2="100"
                  />
                </g>
                <path
                  class="map-tunnel-outline"
                  d="M 6 72 C 16 68, 28 50, 40 47 S 62 62, 76 52 S 90 30, 96 28"
                />
                <path
                  class="map-route-line"
                  d="M 8 70 C 16 64, 26 46, 40 44 S 62 58, 74 50 S 88 35, 94 30"
                />
                <g class="point-cloud-layer">
                  <circle
                    v-for="index in 48"
                    :key="`cloud-${index}`"
                    :cx="8 + ((index * 1.9) % 86)"
                    :cy="18 + ((index * 7.3) % 48)"
                    :r="index % 3 === 0 ? 0.45 : 0.3"
                  />
                </g>
              </svg>

              <div
                class="map-hit-layer"
                @click="handleMapStageClick"
                @mousedown="handleMapPointerDown"
                @mousemove="handleMapPointerMove"
                @mouseup="handleMapPointerUp"
                @mouseleave="handleMapPointerUp"
              ></div>

              <button
                v-for="point in currentConfig.points"
                :key="point.id"
                :class="[
                  'map-point',
                  getPointTypeClass(point.type),
                  {
                    selected: currentConfig.selectedPointIds.includes(point.id),
                  },
                ]"
                :style="{ left: `${point.x}%`, top: `${point.y}%` }"
                type="button"
                @click.stop="handlePointClick(point.id, $event)"
              >
                <span class="map-point-core"></span>
                <span class="map-point-label">{{ point.name }}</span>
              </button>

              <div
                v-if="selectionBox.active"
                class="selection-rect"
                :style="selectionRectStyle"
              ></div>

              <div class="stage-overlay top-left">
                {{ activeMap?.name }} / {{ activeMap?.version }}
              </div>
              <div class="stage-overlay top-right">
                {{
                  currentConfig.mapLoaded
                    ? "地图已加载，可编辑"
                    : "等待同步地图"
                }}
              </div>
            </div>
          </section>

          <section class="config-side-panel">
            <div class="side-tabs">
              <button
                :class="[
                  'side-tab-btn',
                  { active: activeConfigSideTab === 'point' },
                ]"
                type="button"
                @click="activeConfigSideTab = 'point'"
              >
                点位参数配置
              </button>
              <button
                :class="[
                  'side-tab-btn',
                  { active: activeConfigSideTab === 'plan' },
                ]"
                type="button"
                @click="activeConfigSideTab = 'plan'"
              >
                巡检计划配置
              </button>
            </div>

            <div
              v-if="activeConfigSideTab === 'point'"
              class="config-side-card"
            >
              <div class="panel-title">点位参数配置</div>
              <template v-if="singleSelectedPoint">
                <div class="point-tag-row">
                  <span
                    :class="[
                      'point-type-tag',
                      getPointTypeClass(singleSelectedPoint.type),
                    ]"
                  >
                    {{ singleSelectedPoint.type }}
                  </span>
                  <span class="point-id-tag">{{ singleSelectedPoint.id }}</span>
                </div>
                <div class="config-form-grid">
                  <label class="config-field wide">
                    <span>点位名称</span>
                    <input v-model="singleSelectedPoint.name" type="text" />
                  </label>
                  <label class="config-field">
                    <span>点位类型</span>
                    <select v-model="singleSelectedPoint.type">
                      <option
                        v-for="type in pointTypeOptions"
                        :key="type"
                        :value="type"
                      >
                        {{ type }}
                      </option>
                    </select>
                  </label>
                  <label class="config-field">
                    <span>巡航速度 (m/s)</span>
                    <input
                      v-model="singleSelectedPoint.routeSpeed"
                      type="text"
                    />
                  </label>
                  <label class="config-field">
                    <span>停留时长 (s)</span>
                    <input
                      v-model="singleSelectedPoint.stopDuration"
                      type="text"
                    />
                  </label>
                  <label class="config-field">
                    <span>升降柱高度 (mm)</span>
                    <input
                      v-model="singleSelectedPoint.liftHeight"
                      type="text"
                    />
                  </label>
                  <label class="config-field">
                    <span>云台预置点</span>
                    <input
                      v-model="singleSelectedPoint.gimbalPreset"
                      type="text"
                    />
                  </label>
                  <label class="config-field wide">
                    <span>巡检说明</span>
                    <textarea
                      v-model="singleSelectedPoint.note"
                      rows="3"
                    ></textarea>
                  </label>
                </div>
              </template>
              <div v-else class="config-empty">
                选择单个巡航点位后，可在此配置巡航线参数、升降柱高度和云台预置点。
              </div>
            </div>

            <div
              v-if="activeConfigSideTab === 'point'"
              class="config-side-card"
            >
              <div class="panel-title">批量参数统一配置</div>
              <template v-if="selectedPoints.length > 1">
                <div class="batch-tip">
                  已选中
                  {{ selectedPoints.length }} 个巡航点位，留空字段将保留原值。
                </div>
                <div class="config-form-grid compact">
                  <label class="config-field">
                    <span>巡航速度 (m/s)</span>
                    <input
                      v-model="batchForm.routeSpeed"
                      placeholder="统一速度"
                      type="text"
                    />
                  </label>
                  <label class="config-field">
                    <span>停留时长 (s)</span>
                    <input
                      v-model="batchForm.stopDuration"
                      placeholder="统一停留"
                      type="text"
                    />
                  </label>
                  <label class="config-field">
                    <span>升降柱高度 (mm)</span>
                    <input
                      v-model="batchForm.liftHeight"
                      placeholder="统一高度"
                      type="text"
                    />
                  </label>
                  <label class="config-field">
                    <span>云台预置点</span>
                    <input
                      v-model="batchForm.gimbalPreset"
                      placeholder="统一预置点"
                      type="text"
                    />
                  </label>
                </div>
                <button
                  class="page-action-btn primary batch-apply-btn"
                  type="button"
                  @click="applyBatchConfig"
                >
                  应用到选中点位
                </button>
              </template>
              <div v-else class="config-empty">
                使用框选或 `Shift + 点击`
                选中多个点位后，可在这里一次性下发统一参数。
              </div>
            </div>

            <div
              v-if="activeConfigSideTab === 'plan'"
              class="config-side-card plan-card"
            >
              <div class="panel-head inline">
                <div>
                  <div class="panel-title">巡检计划配置</div>
                  <div class="panel-subtitle">
                    支持创建、调整定时与周期巡检任务
                  </div>
                </div>
                <button
                  class="page-action-btn ghost"
                  type="button"
                  @click="resetPlanForm"
                >
                  新建计划
                </button>
              </div>

              <div class="config-form-grid compact">
                <label class="config-field wide">
                  <span>任务名称</span>
                  <input
                    v-model="planForm.name"
                    placeholder="例如：夜间全线路巡检"
                    type="text"
                  />
                </label>
                <label class="config-field">
                  <span>任务模式</span>
                  <select v-model="planForm.mode">
                    <option
                      v-for="mode in patrolModeOptions"
                      :key="mode"
                      :value="mode"
                    >
                      {{ mode }}
                    </option>
                  </select>
                </label>
                <label class="config-field">
                  <span>执行时间</span>
                  <input
                    v-model="planForm.time"
                    placeholder="02:00"
                    type="text"
                  />
                </label>
                <label class="config-field">
                  <span>执行周期</span>
                  <input
                    v-model="planForm.cycle"
                    placeholder="每日 / 每2小时"
                    type="text"
                  />
                </label>
                <label class="config-field">
                  <span>巡检范围</span>
                  <input
                    v-model="planForm.routeRange"
                    placeholder="全线路 / 泵房区"
                    type="text"
                  />
                </label>
                <label class="config-field switch-field">
                  <span>是否启用</span>
                  <button
                    :class="['plan-switch', { active: planForm.enabled }]"
                    type="button"
                    @click="planForm.enabled = !planForm.enabled"
                  >
                    {{ planForm.enabled ? "已启用" : "已停用" }}
                  </button>
                </label>
              </div>

              <div class="plan-editor-actions">
                <button
                  class="page-action-btn primary"
                  type="button"
                  @click="savePlan"
                >
                  {{ editingPlanId ? "更新计划" : "保存计划" }}
                </button>
                <button
                  class="page-action-btn ghost"
                  type="button"
                  @click="resetPlanForm"
                >
                  重置
                </button>
              </div>

              <div class="plan-list-shell">
                <div
                  v-for="plan in currentConfig.plans"
                  :key="plan.id"
                  class="plan-list-item"
                >
                  <div class="plan-item-main">
                    <div class="plan-item-title">
                      <span>{{ plan.name }}</span>
                      <span
                        :class="['plan-status-chip', { active: plan.enabled }]"
                      >
                        {{ plan.enabled ? "启用中" : "已停用" }}
                      </span>
                    </div>
                    <div class="plan-item-meta">
                      <span>{{ plan.mode }}</span>
                      <span>{{ plan.time }}</span>
                      <span>{{ plan.cycle }}</span>
                      <span>{{ plan.routeRange }}</span>
                    </div>
                    <div class="plan-item-updated">
                      最近调整：{{ plan.updatedAt }}
                    </div>
                  </div>
                  <div class="plan-item-actions">
                    <button type="button" @click="loadPlan(plan)">编辑</button>
                    <button
                      class="danger"
                      type="button"
                      @click="removePlan(plan.id)"
                    >
                      删除
                    </button>
                  </div>
                </div>
              </div>
            </div>
          </section>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped lang="less">
.config-action {
  color: #8cf7ff;
  border-color: rgba(124, 230, 255, 0.36) !important;
  background: linear-gradient(
    90deg,
    rgba(18, 82, 142, 0.9),
    rgba(20, 132, 186, 0.74)
  ) !important;
}

.config-dialog-mask {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 26px;
  background: rgba(2, 10, 22, 0.82);
  backdrop-filter: blur(8px);
}

.config-dialog {
  width: min(1760px, calc(100vw - 52px));
  height: min(980px, calc(100vh - 52px));
  display: flex;
  flex-direction: column;
  padding: 22px 24px 24px;
  background:
    radial-gradient(
      circle at top left,
      rgba(20, 112, 185, 0.18),
      transparent 28%
    ),
    linear-gradient(
      180deg,
      rgba(8, 31, 62, 0.98) 0%,
      rgba(4, 15, 34, 0.98) 100%
    );
  border: 1px solid rgba(89, 208, 255, 0.24);
  box-shadow:
    0 28px 72px rgba(0, 0, 0, 0.46),
    inset 0 0 0 1px rgba(83, 193, 255, 0.08);
  overflow: hidden;
}

.config-dialog-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
}

.config-header-main {
  min-width: 0;
}

.config-title-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.config-dialog-title {
  margin: 0;
  color: #f2fbff;
  font-size: 24px;
  font-weight: 700;
  letter-spacing: 0.6px;
}

.config-device-chip {
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 12px;
  border: 1px solid rgba(109, 224, 255, 0.24);
  background: rgba(8, 42, 78, 0.62);
  color: #9af6ff;
  font-size: 12px;
  font-weight: 700;
}

.config-header-meta {
  margin-top: 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 22px;
  color: rgba(179, 227, 247, 0.76);
  font-size: 12px;
}

.config-dialog-body {
  margin-top: 18px;
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) minmax(360px, 0.92fr);
  gap: 16px;
}

.config-map-panel,
.config-side-card {
  background: linear-gradient(
    180deg,
    rgba(7, 28, 56, 0.82),
    rgba(4, 16, 34, 0.88)
  );
  border: 1px solid rgba(82, 197, 255, 0.14);
  box-shadow: inset 0 0 18px rgba(54, 170, 245, 0.04);
}

.config-map-panel {
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 18px;
}

.config-side-panel {
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow: auto;
  padding-right: 2px;
}

.side-tabs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.side-tab-btn {
  height: 40px;
  border: 1px solid rgba(87, 198, 255, 0.18);
  background: rgba(8, 31, 62, 0.72);
  color: rgba(192, 235, 250, 0.78);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition:
    border-color 0.2s ease,
    background 0.2s ease,
    box-shadow 0.2s ease;
}

.side-tab-btn.active {
  color: #eefcff;
  border-color: rgba(102, 235, 255, 0.7);
  background: linear-gradient(
    90deg,
    rgba(24, 102, 180, 0.88),
    rgba(35, 158, 215, 0.88)
  );
  box-shadow: 0 0 14px rgba(52, 177, 255, 0.18);
}

.config-side-card {
  padding: 16px;
}

.panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}

.panel-head.inline {
  align-items: center;
}

.panel-title {
  color: #f0fbff;
  font-size: 18px;
  font-weight: 700;
}

.panel-subtitle {
  margin-top: 6px;
  color: rgba(170, 224, 246, 0.72);
  font-size: 12px;
  line-height: 1.5;
}

.panel-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 10px;
}

.editor-toolbar {
  margin-top: 14px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.editor-tool-btn {
  height: 34px;
  padding: 0 14px;
  border: 1px solid rgba(87, 198, 255, 0.18);
  background: rgba(8, 31, 62, 0.72);
  color: rgba(192, 235, 250, 0.78);
  font-size: 12px;
  cursor: pointer;
}

.editor-tool-btn.active {
  color: #eefcff;
  border-color: rgba(102, 235, 255, 0.7);
  background: linear-gradient(
    90deg,
    rgba(24, 102, 180, 0.88),
    rgba(35, 158, 215, 0.88)
  );
}

.editor-tip {
  margin-left: auto;
  color: rgba(161, 218, 243, 0.72);
  font-size: 12px;
}

.config-map-stage {
  position: relative;
  margin-top: 14px;
  flex: 1;
  min-height: 420px;
  overflow: hidden;
  border: 1px solid rgba(87, 198, 255, 0.18);
  background:
    radial-gradient(
      circle at 18% 20%,
      rgba(77, 176, 229, 0.18),
      transparent 28%
    ),
    radial-gradient(
      circle at 82% 78%,
      rgba(42, 115, 178, 0.18),
      transparent 26%
    ),
    linear-gradient(135deg, rgba(5, 18, 38, 0.98), rgba(6, 28, 56, 0.96));
}

.config-map-svg,
.map-hit-layer {
  position: absolute;
  inset: 0;
}

.map-hit-layer {
  z-index: 2;
}

.config-map-svg {
  width: 100%;
  height: 100%;
}

.map-grid line {
  stroke: rgba(97, 214, 255, 0.08);
  stroke-width: 0.15;
}

.map-tunnel-outline {
  fill: none;
  stroke: rgba(78, 192, 255, 0.22);
  stroke-width: 8;
  stroke-linecap: round;
}

.map-route-line {
  fill: none;
  stroke: url(#routeGlow);
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-dasharray: 1.5 1.2;
}

.point-cloud-layer circle {
  fill: rgba(146, 236, 255, 0.42);
}

.map-point {
  position: absolute;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0;
  border: 0;
  background: transparent;
  transform: translate(-50%, -50%);
  cursor: pointer;
}

.map-point-core {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid rgba(231, 250, 255, 0.88);
  box-shadow: 0 0 18px currentColor;
}

.map-point-label {
  padding: 4px 8px;
  background: rgba(4, 19, 39, 0.78);
  border: 1px solid rgba(91, 208, 255, 0.18);
  color: #eafcff;
  font-size: 12px;
  white-space: nowrap;
}

.map-point.selected .map-point-core,
.map-point.selected .map-point-label {
  box-shadow: 0 0 18px rgba(112, 226, 255, 0.26);
}

.map-point.selected .map-point-label {
  border-color: rgba(112, 226, 255, 0.58);
  background: rgba(10, 48, 88, 0.88);
}

.type-normal {
  color: #66d7ff;
}

.type-thermal {
  color: #ffb26e;
}

.type-pump {
  color: #9af7c8;
}

.selection-rect {
  position: absolute;
  z-index: 4;
  border: 1px dashed rgba(121, 234, 255, 0.92);
  background: rgba(92, 220, 255, 0.1);
}

.stage-overlay {
  position: absolute;
  z-index: 5;
  padding: 5px 10px;
  border: 1px solid rgba(91, 208, 255, 0.18);
  background: rgba(5, 20, 38, 0.72);
  color: rgba(212, 246, 255, 0.88);
  font-size: 11px;
}

.stage-overlay.top-left {
  top: 14px;
  left: 14px;
}

.stage-overlay.top-right {
  top: 14px;
  right: 14px;
}

.stage-overlay.bottom-left {
  left: 14px;
  bottom: 14px;
}

.stage-overlay.bottom-right {
  right: 14px;
  bottom: 14px;
}

.point-tag-row {
  margin-top: 12px;
  display: flex;
  gap: 8px;
}

.point-type-tag,
.point-id-tag {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 10px;
  border: 1px solid rgba(91, 208, 255, 0.18);
  font-size: 12px;
}

.point-type-tag.type-normal {
  background: rgba(39, 149, 207, 0.18);
}

.point-type-tag.type-thermal {
  background: rgba(185, 108, 37, 0.18);
}

.point-type-tag.type-pump {
  background: rgba(38, 156, 112, 0.18);
}

.point-id-tag {
  color: rgba(170, 224, 246, 0.76);
}

.config-form-grid {
  margin-top: 14px;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.config-form-grid.compact {
  margin-top: 12px;
}

.config-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
  color: rgba(173, 224, 246, 0.78);
  font-size: 12px;
}

.config-field.wide {
  grid-column: 1 / -1;
}

.config-field input,
.config-field select,
.config-field textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid rgba(85, 198, 255, 0.18);
  background: rgba(8, 31, 62, 0.72);
  color: #eefcff;
  font-size: 13px;
  box-sizing: border-box;
  outline: none;
}

.config-field textarea {
  resize: vertical;
  min-height: 88px;
}

.config-empty {
  margin-top: 12px;
  padding: 16px;
  border: 1px dashed rgba(87, 198, 255, 0.16);
  background: rgba(7, 24, 47, 0.58);
  color: rgba(164, 218, 243, 0.72);
  font-size: 12px;
  line-height: 1.7;
}

.batch-tip {
  margin-top: 12px;
  color: rgba(165, 221, 245, 0.76);
  font-size: 12px;
}

.batch-apply-btn {
  margin-top: 12px;
  width: 100%;
}

.switch-field {
  justify-content: flex-end;
}

.plan-switch {
  height: 38px;
  border: 1px solid rgba(87, 198, 255, 0.18);
  background: rgba(8, 31, 62, 0.72);
  color: rgba(192, 235, 250, 0.78);
  cursor: pointer;
}

.plan-switch.active {
  color: #eefcff;
  border-color: rgba(102, 235, 255, 0.7);
  background: linear-gradient(
    90deg,
    rgba(24, 102, 180, 0.88),
    rgba(35, 158, 215, 0.88)
  );
}

.plan-editor-actions {
  margin-top: 12px;
  display: flex;
  gap: 10px;
}

.plan-list-shell {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 300px;
  overflow: auto;
}

.plan-list-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid rgba(84, 197, 255, 0.12);
  background: rgba(8, 31, 62, 0.62);
}

.plan-item-main {
  min-width: 0;
}

.plan-item-title {
  display: flex;
  align-items: center;
  gap: 10px;
  color: #f2fbff;
  font-size: 14px;
  font-weight: 700;
}

.plan-status-chip {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 8px;
  border: 1px solid rgba(255, 148, 105, 0.2);
  background: rgba(126, 31, 20, 0.22);
  color: #ffd0cc;
  font-size: 11px;
}

.plan-status-chip.active {
  border-color: rgba(110, 255, 197, 0.2);
  background: rgba(29, 138, 102, 0.18);
  color: #9cf7dc;
}

.plan-item-meta,
.plan-item-updated {
  margin-top: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 12px;
  color: rgba(171, 223, 246, 0.76);
  font-size: 12px;
}

.plan-item-actions {
  display: flex;
  gap: 8px;
}

.plan-item-actions button {
  height: 30px;
  padding: 0 10px;
  border: 1px solid rgba(87, 198, 255, 0.2);
  background: rgba(8, 31, 62, 0.72);
  color: #eafcff;
  font-size: 12px;
  cursor: pointer;
}

.plan-item-actions .danger {
  border-color: rgba(255, 126, 112, 0.2);
  color: #ffd0cc;
  background: rgba(126, 31, 20, 0.22);
}

@media (max-width: 1480px) {
  .config-dialog-body {
    grid-template-columns: 1fr;
  }

  .config-side-panel {
    overflow: visible;
  }

  .config-dialog {
    height: auto;
    max-height: calc(100vh - 52px);
    overflow: auto;
  }
}
</style>
