<template>
  <div class="binding-page">
    <ModelPlaceholder ref="modelRef" @modelLoaded="onModelLoaded" />

    <aside class="binding-card">
      <div class="binding-label">{{ modeLabel }}</div>
      <div class="binding-title">{{ partName }}</div>
      <div class="binding-count">已选择 {{ selectedComponents.length }} 个模型构件</div>
      <div class="binding-divider"></div>
      <button class="tree-toggle" type="button">构件树 <span>▴</span></button>
      <div class="tree-head"><span>快速选择模型构件</span><span>已选 {{ selectedComponents.length }} 组</span></div>
      <input v-model.trim="treeQuery" class="tree-search" type="search" placeholder="搜索构件" autocomplete="off" />
      <div class="tree-list">
        <button
          v-for="row in visibleRows"
          :key="row.key"
          class="tree-row"
          :class="{ 'is-group': !row.leaf, 'is-selected': row.selected }"
          type="button"
          :style="{ paddingLeft: `${12 + row.level * 14}px` }"
          @click="toggleRowSelection(row)"
        >
          <span class="tree-caret" @click.stop="onTreeCaretClick(row)">{{ row.leaf ? (row.selected ? '✓' : '') : row.open ? '▾' : '▸' }}</span>
          <span class="tree-name">{{ row.name }}</span>
        </button>
        <div v-if="!visibleRows.length" class="tree-empty">暂无构件树数据，请等待模型加载完成</div>
      </div>
    </aside>

    <div v-if="isUnbind && boundComponents.length" class="binding-tip">已高亮当前工程部位已绑定构件，可直接取消选择后确定解绑</div>

    <div class="binding-actions">
      <button class="btn-cancel" type="button" @click="cancel">取消</button>
      <button class="btn-confirm" type="button" @click="confirm">{{ isUnbind ? '确定解绑' : '确定绑定' }}</button>
    </div>

    <div v-if="toastText" class="toast" :class="'toast-' + toastType">{{ toastText }}</div>
  </div>
</template>

<script setup>
import { computed, nextTick, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import ModelPlaceholder from "../../components/model-placeholder/index.vue";
import { progressApi } from "../../service/api/progress.js";
import { useModelCenterStore } from "../../stores/modelCenter.js";

const route = useRoute();
const router = useRouter();
const modelCenterStore = useModelCenterStore();
const modelRef = ref(null);
const constructTreeData = ref([]);
const expanded = ref(new Set());
const treeQuery = ref("");
const selectedComponents = ref([]);
const boundComponents = ref([]);
const toastText = ref("");
const toastType = ref("success");
let toastTimer = null;

const partId = computed(() => String(route.query.partId || ""));
const partName = computed(() => String(route.query.partName || partId.value || "--"));
const isUnbind = computed(() => route.query.mode === "unbind");
const modeLabel = computed(() => (isUnbind.value ? "解绑构件" : "绑定构件"));
const modelId = computed(() => {
  const id = String(route.query.modelId || modelCenterStore.activeId || "").trim();
  return /^\d+$/.test(id) ? id : undefined;
});
const partPayload = computed(() => [{
  id: partId.value,
  createDate: String(route.query.createDate || ""),
  is_leaf: route.query.isLeaf === "true",
  isLeaf: route.query.isLeaf === "true",
}]);

const visibleRows = computed(() => {
  const rows = [];
  const query = treeQuery.value.toLowerCase();
  const selectedCodes = new Set(selectedComponents.value.map((item) => String(item.code)));
  const matches = (node) => !query || String(node?.name || "").toLowerCase().includes(query) || childrenOf(node).some(matches);
  const walk = (node, level, parentKey, inheritedUrl, index) => {
    if (!matches(node)) return;
    const key = `${parentKey}/${String(node?.name || "node")}-${String(node?.id || node?.url || index)}`;
    const children = childrenOf(node);
    const ids = idsDeep(node);
    const leaf = !children.length;
    const url = String(node?.url || inheritedUrl || "").trim();
    const open = query ? true : expanded.value.has(key);
    const selected = ids.length > 0 && ids.every((id) => selectedCodes.has(String(id)));
    rows.push({ key, name: String(node?.name || node?.id || "未命名构件"), level, leaf, open, selected, ids, url });
    if (!leaf && open) children.forEach((child, childIndex) => walk(child, level + 1, key, url, childIndex));
  };
  constructTreeData.value.forEach((node, index) => walk(node, 0, `root-${index}`, node?.url, index));
  return rows;
});

onMounted(async () => {
  if (!partId.value) {
    showToast("缺少工程部位参数", "error");
    return;
  }
  await modelCenterStore.loadModels().catch((error) => {
    console.error("加载模型列表失败:", error);
  });
  await loadBoundComponents();
  await nextTick();
  refreshConstructTreeData();
  setTimeout(refreshConstructTreeData, 1200);
});

function onModelLoaded(treeData) {
  applyConstructTreeData(treeData);
  highlightBoundComponents();
}

function refreshConstructTreeData() {
  applyConstructTreeData(modelRef.value?.getConstructTreeData?.());
}

function applyConstructTreeData(raw) {
  constructTreeData.value = Array.isArray(raw?.level) ? raw.level : [];
  if (constructTreeData.value.length && expanded.value.size === 0) {
    const next = new Set();
    constructTreeData.value.forEach((node, index) => collectExpandedKeys(node, 0, `root-${index}`, index, next));
    expanded.value = next;
  }
}

function collectExpandedKeys(node, level, parentKey, index, target) {
  const key = `${parentKey}/${String(node?.name || "node")}-${String(node?.id || node?.url || index)}`;
  const children = childrenOf(node);
  if (!children.length) return;
  target.add(key);
  children.forEach((child, childIndex) => collectExpandedKeys(child, level + 1, key, childIndex, target));
}

async function loadBoundComponents() {
  try {
    const data = await progressApi.getByPartId(partId.value, modelId.value);
    boundComponents.value = (data || []).map(toBindingCodeItem).filter(Boolean);
    selectedComponents.value = boundComponents.value.map((item) => ({ ...item }));
    highlightBoundComponents();
  } catch (error) {
    console.error("获取已绑定构件失败:", error);
    showToast("获取已绑定构件失败", "error");
  }
}

function childrenOf(node) {
  return Array.isArray(node?.children) ? node.children : [];
}

function idsOf(node) {
  const ids = [];
  if (Array.isArray(node?.ids)) ids.push(...node.ids);
  if (node?.id != null) ids.push(node.id);
  return Array.from(new Set(ids.map((id) => String(id)).filter(Boolean)));
}

function idsDeep(node) {
  const ids = new Set(idsOf(node));
  childrenOf(node).forEach((child) => idsDeep(child).forEach((id) => ids.add(id)));
  return Array.from(ids);
}

function toggleExpand(row) {
  if (!row || row.leaf) return;
  const next = new Set(expanded.value);
  if (next.has(row.key)) next.delete(row.key);
  else next.add(row.key);
  expanded.value = next;
}

function onTreeCaretClick(row) {
  if (row?.leaf) {
    toggleRowSelection(row);
    return;
  }
  toggleExpand(row);
}

function toggleRowSelection(row) {
  if (!row?.ids?.length) {
    toggleExpand(row);
    return;
  }
  const selectedCodes = new Set(selectedComponents.value.map((item) => String(item.code)));
  const allSelected = row.ids.every((id) => selectedCodes.has(String(id)));
  if (allSelected) {
    selectedComponents.value = selectedComponents.value.filter((item) => !row.ids.includes(String(item.code)));
    return;
  }
  const next = [...selectedComponents.value];
  row.ids.forEach((id) => {
    if (selectedCodes.has(String(id))) return;
    next.push({ code: String(id), data: { url: row.url, id: String(id) } });
  });
  selectedComponents.value = next;
  highlightRow(row);
}

function highlightRow(row) {
  const ids = row.ids.map((id) => Number(id)).filter((id) => Number.isFinite(id));
  if (row.url && ids.length) modelRef.value?.highlightModels?.([{ url: row.url, ids }]);
}

function highlightBoundComponents() {
  const grouped = new Map();
  selectedComponents.value.forEach((item) => {
    const url = String(item?.data?.url || "").trim();
    const id = Number(item?.code);
    if (!url || !Number.isFinite(id)) return;
    if (!grouped.has(url)) grouped.set(url, []);
    grouped.get(url).push(id);
  });
  const refs = Array.from(grouped.entries()).map(([url, ids]) => ({ url, ids }));
  if (refs.length) modelRef.value?.highlightModels?.(refs);
}

async function confirm() {
  if (!selectedComponents.value.length && !isUnbind.value) {
    showToast("请先选择需要绑定的模型构件", "error");
    return;
  }
  try {
    const selectedCodes = new Set(selectedComponents.value.map((item) => String(item.code)));
    const codeIds = isUnbind.value
      ? boundComponents.value.filter((item) => !selectedCodes.has(String(item.code)))
      : selectedComponents.value;
    if (isUnbind.value && !codeIds.length) {
      showToast("请取消选择需要解绑的构件", "error");
      return;
    }
    const payload = { partIds: partPayload.value, codeIds, modelId: modelId.value };
    const res = isUnbind.value
      ? await progressApi.deleteBatch(payload)
      : await progressApi.saveBatch(payload);
    if (res?.code === 200) {
      showToast(isUnbind.value ? "解绑成功" : "绑定成功", "success");
      setTimeout(() => router.push("/progress"), 450);
    } else {
      showToast(res?.message || "操作失败", "error");
    }
  } catch (error) {
    console.error("保存绑定关系失败:", error);
    showToast("操作失败", "error");
  }
}

function cancel() {
  router.push("/progress");
}

function toBindingCodeItem(item) {
  const code = String(item?.codeId ?? item?.code ?? item?.id ?? "").trim();
  if (!code) return null;
  return { code, data: resolveComponentCodeDataPayload(item, code) };
}

function resolveComponentCodeDataPayload(item, code) {
  const rawCodeData = String(item?.codeData || "").trim();
  if (rawCodeData) {
    const parsed = parseCodeData(rawCodeData);
    if (parsed) return parsed;
  }
  const rawData = item?.data;
  if (rawData && typeof rawData === "object") return { ...rawData, id: rawData.id ?? code };
  return { id: code };
}

function parseCodeData(raw) {
  try {
    if (raw.startsWith("{")) {
      const obj = {};
      raw.replace(/{([\s\S]+)}/, "$1").split(", ").forEach((entry) => {
        const [key, value] = entry.split("=");
        if (key) obj[key] = value;
      });
      return obj;
    }
  } catch (error) {
    return null;
  }
  return null;
}

function showToast(text, type = "success") {
  toastText.value = text;
  toastType.value = type;
  if (toastTimer) clearTimeout(toastTimer);
  toastTimer = setTimeout(() => { toastText.value = ""; }, 2000);
}
</script>

<style scoped>
.binding-page { position: relative; min-height: 100vh; overflow: hidden; background: #8f9aa5; }
.binding-card { position: absolute; left: 24px; top: 28px; width: 300px; max-height: calc(100vh - 112px); overflow: hidden; border-radius: 12px; background: rgba(26, 38, 46, 0.96); box-shadow: 0 24px 60px rgba(0,0,0,.28); padding: 14px; color: rgba(244,250,252,.96); z-index: 20; }
.binding-label { font-size: 13px; font-weight: 900; }
.binding-title { margin-top: 8px; font-size: 18px; font-weight: 900; }
.binding-count { margin-top: 8px; font-size: 12px; font-weight: 800; color: rgba(224, 235, 240, 0.75); }
.binding-divider { height: 1px; margin: 14px 0 10px; background: rgba(255,255,255,.12); }
.tree-toggle { width: 100%; height: 38px; border-radius: 8px; border: 1px solid rgba(255,255,255,.12); background: rgba(255,255,255,.10); color: rgba(244,250,252,.95); display: flex; align-items: center; justify-content: space-between; padding: 0 12px; font-size: 13px; font-weight: 900; }
.tree-head { display: flex; align-items: center; justify-content: space-between; margin-top: 12px; font-size: 12px; font-weight: 900; color: rgba(234,244,248,.84); }
.tree-search { width: 100%; height: 34px; margin-top: 12px; border-radius: 7px; border: 1px solid rgba(255,255,255,.14); background: rgba(255,255,255,.08); color: rgba(245,250,252,.94); padding: 0 10px; outline: none; }
.tree-list { margin-top: 14px; max-height: min(560px, calc(100vh - 330px)); overflow: auto; border-radius: 10px; border: 1px solid rgba(255,255,255,.08); padding: 8px; }
.tree-row { width: 100%; min-height: 34px; border: 0; border-radius: 7px; background: transparent; color: rgba(231,241,246,.86); display: grid; grid-template-columns: 20px minmax(0, 1fr); align-items: center; gap: 6px; text-align: left; cursor: pointer; }
.tree-row:hover { background: rgba(255,255,255,.07); }
.tree-row.is-group { color: rgba(210,226,233,.78); font-weight: 900; }
.tree-row.is-selected { color: rgba(245,255,253,.98); }
.tree-caret { width: 16px; height: 16px; border-radius: 4px; border: 1px solid rgba(203,226,236,.36); display: grid; place-items: center; color: rgba(83,214,206,.95); font-weight: 900; }
.tree-row.is-group .tree-caret { border: 0; }
.tree-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tree-empty { padding: 22px 8px; text-align: center; color: rgba(221,235,241,.58); font-size: 12px; }
.binding-tip { position: absolute; left: 50%; top: 120px; transform: translateX(-50%); z-index: 21; border-radius: 10px; background: rgba(245,250,255,.96); color: rgba(48,65,96,.9); padding: 10px 16px; font-size: 14px; font-weight: 800; box-shadow: 0 18px 44px rgba(30,42,60,.18); }
.binding-actions { position: absolute; right: 88px; bottom: 18px; z-index: 1002; display: flex; gap: 10px; padding: 10px; border-radius: 10px; background: rgba(18,31,39,.95); box-shadow: 0 16px 40px rgba(0,0,0,.28); }
.btn-cancel, .btn-confirm { min-width: 128px; height: 46px; border-radius: 8px; font-size: 14px; font-weight: 900; cursor: pointer; }
.btn-cancel { border: 0; background: rgba(255,255,255,.94); color: rgba(46,58,74,.82); }
.btn-confirm { border: 1px solid rgba(83,214,206,.34); background: linear-gradient(180deg, rgba(38,190,178,.95), rgba(21,149,142,.95)); color: white; }
.toast { position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%); z-index: 1000; padding: 10px 16px; border-radius: 10px; background: rgba(18,31,39,.94); color: white; font-weight: 900; }
.toast-error { background: rgba(154, 36, 44, 0.94); }

:deep(.homeViewWrapper) {
  display: none !important;
}
</style>
