<template>
  <div class="editor-layout" :style="{ height }">
    <!-- 中间流程图区域 -->
    <div ref="flowHostRef" class="flow-container" :class="{ 'snapline-disabled': !snaplineEnabled }">
      <div class="flow-controls" :class="{ 'flow-controls--collapsed': flowControlsCollapsed }">
        <div class="control-row control-header">
          <button class="control-button" type="button" @click="flowControlsCollapsed = !flowControlsCollapsed">
            {{ flowControlsCollapsed ? `显示画布控制${groupRuleWarnings.length ? `(${groupRuleWarnings.length})` : ''}` : '收起画布控制' }}
          </button>
        </div>
        <template v-if="!flowControlsCollapsed">
        <div class="control-row toggles">
          <label class="control-toggle">
            <input type="checkbox" v-model="selectionEnabled" />
            <span>框选</span>
          </label>
          <label class="control-toggle">
            <input type="checkbox" v-model="snapGridEnabled" />
            <span>吸附网格</span>
          </label>
          <label class="control-toggle">
            <input type="checkbox" v-model="snaplineEnabled" />
            <span>对齐线</span>
          </label>
          <span class="control-hint">已选 {{ selectedCount }}</span>
          <button class="control-button" type="button" @click="showAllNodes">显示全部</button>
        </div>
        <div class="control-row">
          <div class="control-label">对齐</div>
          <div class="control-buttons">
            <button
              v-for="btn in alignmentButtons"
              :key="btn.key"
              class="control-button"
              type="button"
              :disabled="selectedCount < 2"
              @click="() => alignSelected(btn.key)"
            >
              {{ btn.label }}
            </button>
          </div>
        </div>
        <div class="control-row">
          <div class="control-label">分布</div>
          <div class="control-buttons">
            <button
              v-for="btn in distributeButtons"
              :key="btn.key"
              class="control-button"
              type="button"
              :disabled="selectedCount < 3"
              @click="() => distributeSelected(btn.key)"
            >
              {{ btn.label }}
            </button>
          </div>
        </div>
        <div v-if="groupRuleWarnings.length" class="control-row rule-row">
          <div class="control-label">规则检查</div>
          <div class="rule-list">
            <div v-for="(warning, index) in groupRuleWarnings" :key="`${warning.groupId}-${warning.code}-${index}`" class="rule-item">
              [{{ warning.groupId }}] {{ warning.message }}
            </div>
          </div>
        </div>
        </template>
      </div>
      <div class="container" ref="containerRef" :style="{ height: '100%' }"></div>
    </div>
    <!-- 右侧属性面板 -->
    <PropertyPanel :height="height" :node="selectedNode" :lf="lf" />
  </div>
</template>

<script setup lang="ts">
import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
import LogicFlow, { EventType } from '@logicflow/core';
import type { Position, NodeData, EdgeData, BaseNodeModel, GraphModel, GraphData } from '@logicflow/core';
import '@logicflow/core/lib/style/index.css';
import { Menu, Label, Snapshot, SelectionSelect, MiniMap, Control } from '@logicflow/extension';
import '@logicflow/extension/lib/style/index.css';
import '@logicflow/core/es/index.css';
import '@logicflow/extension/es/index.css';
import { translateEdgeData, translateNodeData } from '@logicflow/core/es/keyboard/shortcut';

import { register } from '@logicflow/vue-node-registry';
import PropertySelectNode from './nodes/yys/PropertySelectNode.vue';
import ImageNode from './nodes/common/ImageNode.vue';
import AssetSelectorNode from './nodes/common/AssetSelectorNode.vue';
import TextNode from './nodes/common/TextNode.vue';
import TextNodeModel from './nodes/common/TextNodeModel';
import VectorNode from './nodes/common/VectorNode.vue';
import VectorNodeModel from './nodes/common/VectorNodeModel';
import PropertyPanel from './PropertyPanel.vue';
import { useGlobalMessage } from '@/ts/useGlobalMessage';
import { setLogicFlowInstance, destroyLogicFlowInstance } from '@/ts/useLogicFlow';
import { normalizePropertiesWithStyle, normalizeNodeStyle, styleEquals } from '@/ts/nodeStyle';
import { useCanvasSettings } from '@/ts/useCanvasSettings';
import { validateGraphGroupRules, type GroupRuleWarning } from '@/utils/groupRules';
import { subscribeSharedGroupRulesConfig } from '@/utils/groupRulesConfigSource';

type AlignType = 'left' | 'right' | 'top' | 'bottom' | 'hcenter' | 'vcenter';
type DistributeType = 'horizontal' | 'vertical';

const MOVE_STEP = 2;
const MOVE_STEP_LARGE = 10;
const COPY_TRANSLATION = 40;

const props = withDefaults(defineProps<{
  height?: string;
  enableLabel?: boolean;
}>(), {
  enableLabel: true
});

const flowHostRef = ref<HTMLElement | null>(null);
const containerRef = ref<HTMLElement | null>(null);
const lf = ref<LogicFlow | null>(null);
const selectedCount = ref(0);
const { selectionEnabled, snapGridEnabled, snaplineEnabled } = useCanvasSettings();
const alignmentButtons: { key: AlignType; label: string }[] = [
  { key: 'left', label: '左对齐' },
  { key: 'right', label: '右对齐' },
  { key: 'top', label: '上对齐' },
  { key: 'bottom', label: '下对齐' },
  { key: 'hcenter', label: '水平居中' },
  { key: 'vcenter', label: '垂直居中' }
];
const distributeButtons: { key: DistributeType; label: string }[] = [
  { key: 'horizontal', label: '水平等距' },
  { key: 'vertical', label: '垂直等距' }
];
const { showMessage } = useGlobalMessage();

// 当前选中节点
const selectedNode = ref<any>(null);
const copyBuffer = ref<GraphData | null>(null);
const groupRuleWarnings = ref<GroupRuleWarning[]>([]);
const flowControlsCollapsed = ref(true);
let nextPasteDistance = COPY_TRANSLATION;
let containerResizeObserver: ResizeObserver | null = null;
let groupRuleValidationTimer: ReturnType<typeof setTimeout> | null = null;
let unsubscribeSharedGroupRules: (() => void) | null = null;

const resolveResizeHost = () => {
  const container = containerRef.value;
  if (!container) return null;
  return flowHostRef.value ?? (container.parentElement as HTMLElement | null) ?? container;
};

const resizeCanvas = () => {
  const lfInstance = lf.value as any;
  const resizeHost = resolveResizeHost();
  if (!lfInstance || !resizeHost || typeof lfInstance.resize !== 'function') {
    return;
  }
  const width = resizeHost.clientWidth;
  const height = resizeHost.clientHeight;
  if (width > 0 && height > 0) {
    lfInstance.resize(width, height);
    return;
  }
};

const handleWindowResize = () => {
  resizeCanvas();
};

function isInputLike(event?: KeyboardEvent) {
  const target = event?.target as HTMLElement | null;
  if (!target) return false;
  const tag = target.tagName?.toLowerCase();
  return ['input', 'textarea', 'select', 'option'].includes(tag) || target.isContentEditable;
}

function shouldSkipShortcut(event?: KeyboardEvent) {
  const lfInstance = lf.value as any;
  if (!lfInstance) return true;
  if (lfInstance.keyboard?.disabled) return true;
  if (lfInstance.graphModel?.textEditElement) return true;
  if (isInputLike(event)) return true;
  return false;
}

const queueCanvasResize = () => {
  resizeCanvas();
  if (typeof window === 'undefined') return;
  window.requestAnimationFrame(() => resizeCanvas());
  setTimeout(() => resizeCanvas(), 120);
};

function ensureMeta(meta?: Record<string, any>) {
  const next: Record<string, any> = meta ? { ...meta } : {};
  if (next.visible == null) next.visible = true;
  if (next.locked == null) next.locked = false;
  return next;
}

function applyMetaToModel(model: BaseNodeModel, metaInput?: Record<string, any>) {
  const lfInstance = lf.value;
  const meta = ensureMeta(metaInput ?? (model.getProperties?.() as any)?.meta ?? (model as any)?.properties?.meta);
  model.visible = meta.visible !== false;
  model.draggable = !meta.locked;
  model.setHittable?.(!meta.locked);
  model.setHitable?.(!meta.locked);
  model.setIsShowAnchor?.(!meta.locked);
  model.setRotatable?.(!meta.locked);
  model.setResizable?.(!meta.locked);

  if (lfInstance) {
    const connectedEdges = lfInstance.getNodeEdges(model.id);
    connectedEdges.forEach((edgeModel) => {
      edgeModel.visible = meta.visible !== false;
    });
  }
}

function applyStyleToModel(model: BaseNodeModel, styleInput?: Record<string, any>) {
  // 只有当 style 中明确指定了 width/height 时才更新 model
  // 避免用默认值覆盖用户的手动缩放操作
  if (styleInput?.width != null && model.width !== styleInput.width) {
    model.width = styleInput.width;
  }
  if (styleInput?.height != null && model.height !== styleInput.height) {
    model.height = styleInput.height;
  }
}

function normalizeNodeModel(model: BaseNodeModel) {
  const lfInstance = lf.value;
  if (!lfInstance) return;

  const props = (model.getProperties?.() as any) ?? (model as any)?.properties ?? {};
  const incomingMeta = ensureMeta(props.meta);

  // 优先使用 model 的实际尺寸（用户可能刚刚手动缩放了节点）
  const normalized = normalizePropertiesWithStyle(
    { ...props, meta: incomingMeta },
    { width: model.width, height: model.height }
  );

  const currentMeta = ensureMeta((model as any)?.properties?.meta);
  const metaChanged =
    currentMeta.visible !== incomingMeta.visible ||
    currentMeta.locked !== incomingMeta.locked ||
    currentMeta.groupId !== incomingMeta.groupId;

  // 只检查 style 的其他属性变化，不检查 width/height
  // 因为 width/height 应该由 LogicFlow 的缩放控制，而不是由 properties 控制
  const styleChanged = !styleEquals(props.style, normalized.style);

  if (metaChanged || styleChanged) {
    // 同步 style 到 properties，但保持 width/height 与 model 一致
    lfInstance.setProperties(model.id, {
      ...normalized,
      width: model.width,
      height: model.height
    });
  }
  applyMetaToModel(model, normalized.meta);
  // 不再调用 applyStyleToModel，因为 width/height 应该由 LogicFlow 控制
}

function normalizeAllNodes() {
  const lfInstance = lf.value;
  if (!lfInstance) return;
  lfInstance.graphModel?.nodes.forEach((model: BaseNodeModel) => normalizeNodeModel(model));

  // 清除新节点标记
  const allNodes = lfInstance.graphModel?.nodes || [];
  allNodes.forEach(node => {
    delete (node as any)._isNewNode;
  });
}

function updateNodeMeta(model: BaseNodeModel, updater: (meta: Record<string, any>) => Record<string, any>) {
  const lfInstance = lf.value;
  if (!lfInstance) return;
  const props = (model.getProperties?.() as any) ?? (model as any)?.properties ?? {};
  const nextMeta = updater(ensureMeta(props.meta));
  lfInstance.setProperties(model.id, { ...props, meta: nextMeta });
  applyMetaToModel(model, nextMeta);
}

function getSelectedNodeModelsFiltered(options?: { includeHidden?: boolean; includeLocked?: boolean }) {
  const includeHidden = options?.includeHidden ?? false;
  const includeLocked = options?.includeLocked ?? false;
  const graphModel = lf.value?.graphModel;
  if (!graphModel) return [];
  return graphModel.selectNodes.filter((model: BaseNodeModel) => {
    const meta = ensureMeta((model.getProperties?.() as any)?.meta ?? (model as any)?.properties?.meta);
    if (!includeHidden && meta.visible === false) return false;
    if (!includeLocked && meta.locked) return false;
    return true;
  });
}

function getSelectedNodeModels() {
  const graphModel = lf.value?.graphModel;
  if (!graphModel) return [];
  return [...graphModel.selectNodes];
}

function collectGroupNodeIds(models: BaseNodeModel[]) {
  const graphModel = lf.value?.graphModel;
  if (!graphModel) return [];
  const ids = new Set<string>();
  models.forEach((model) => {
    const meta = ensureMeta((model.getProperties?.() as any)?.meta ?? (model as any)?.properties?.meta);
    if (meta.locked) return;
    if (meta.groupId) {
      graphModel.nodes.forEach((node) => {
        const peerMeta = ensureMeta((node.getProperties?.() as any)?.meta ?? (node as any)?.properties?.meta);
        if (peerMeta.groupId === meta.groupId && !peerMeta.locked) {
          ids.add(node.id);
        }
      });
    } else {
      ids.add(model.id);
    }
  });
  return Array.from(ids);
}

function moveSelectedNodes(deltaX: number, deltaY: number) {
  const graphModel = lf.value?.graphModel;
  if (!graphModel) return;
  const targets = collectGroupNodeIds(getSelectedNodeModelsFiltered());
  if (!targets.length) return;
  graphModel.moveNodes(targets, deltaX, deltaY);
}

// ========== 图层命令 ==========
function bringToFront(nodeId?: string) {
  const lfInstance = lf.value;
  if (!lfInstance) return;
  const targetId = nodeId || selectedNode.value?.id;
  if (!targetId) return;

  // 诊断日志：查看所有节点的 zIndex
  const allNodes = lfInstance.graphModel.nodes;
  console.log('[置于顶层] 目标节点ID:', targetId);
  console.log('[置于顶层] 所有节点的 zIndex:', allNodes.map(n => ({ id: n.id, zIndex: n.zIndex })));

  lfInstance.setElementZIndex(targetId, 'top');

  // 操作后再次查看
  console.log('[置于顶层] 操作后所有节点的 zIndex:', allNodes.map(n => ({ id: n.id, zIndex: n.zIndex })));
}

function sendToBack(nodeId?: string) {
  const lfInstance = lf.value;
  if (!lfInstance) return;
  const targetId = nodeId || selectedNode.value?.id;
  if (!targetId) return;

  const currentNode = lfInstance.getNodeModelById(targetId);
  if (!currentNode) return;

  const allNodes = lfInstance.graphModel.nodes;
  console.log('[置于底层] 目标节点ID:', targetId);
  console.log('[置于底层] 所有节点的 zIndex:', allNodes.map(n => ({ id: n.id, zIndex: n.zIndex })));

  // 修复：找到所有节点中最小的 zIndex，然后设置为比它更小
  const allZIndexes = allNodes.map(n => n.zIndex).filter(z => z !== undefined);
  const minZIndex = allZIndexes.length > 0 ? Math.min(...allZIndexes) : 1;
  const newZIndex = minZIndex - 1;

  currentNode.setZIndex(newZIndex);

  // 操作后再次查看
  console.log('[置于底层] 操作后所有节点的 zIndex:', allNodes.map(n => ({ id: n.id, zIndex: n.zIndex })));
}

function bringForward(nodeId?: string) {
  const lfInstance = lf.value;
  if (!lfInstance) return;
  const targetId = nodeId || selectedNode.value?.id;
  if (!targetId) return;

  const currentNode = lfInstance.getNodeModelById(targetId);
  if (!currentNode) return;t

  const currentZIndex = currentNode.zIndex;
  currentNode.setZIndex(currentZIndex + 1);
}

function sendBackward(nodeId?: string) {
  const lfInstance = lf.value;
  if (!lfInstance) return;
  const targetId = nodeId || selectedNode.value?.id;
  if (!targetId) return;

  const currentNode = lfInstance.getNodeModelById(targetId);
  if (!currentNode) return;

  const currentZIndex = currentNode.zIndex;
  currentNode.setZIndex(currentZIndex - 1);
}

// ========== 删除操作 ==========
function deleteSelectedElements(event?: KeyboardEvent) {
  if (shouldSkipShortcut(event)) return true;
  const lfInstance = lf.value;
  if (!lfInstance) return true;

  const { edges } = lfInstance.getSelectElements(true);
  const nodes = getSelectedNodeModelsFiltered({ includeHidden: false, includeLocked: true });
  const lockedNodes = nodes.filter((node) => ensureMeta((node as any).properties?.meta).locked);
  edges.forEach((edge) => edge.id && lfInstance.deleteEdge(edge.id));
  nodes
    .filter((node) => {
      const meta = ensureMeta((node as any).properties?.meta);
      return !meta.locked && meta.visible !== false;
    })
    .forEach((node) => node.id && lfInstance.deleteNode(node.id));

  if (lockedNodes.length) {
    showMessage('warning', '部分节点已锁定，未删除');
  }
  updateSelectedCount();
  selectedNode.value = null;
  return false;
}

function deleteNode(nodeId: string) {
  const lfInstance = lf.value;
  if (!lfInstance) return;
  const node = lfInstance.getNodeModelById(nodeId);
  if (!node) return;

  const meta = ensureMeta((node as any).properties?.meta);
  if (meta.locked) {
    showMessage('warning', '节点已锁定，无法删除');
    return;
  }

  lfInstance.deleteNode(nodeId);
}

function toggleLockSelected(event?: KeyboardEvent) {
  if (shouldSkipShortcut(event)) return true;
  const models = getSelectedNodeModels();
  if (!models.length) {
    showMessage('info', '请选择节点后再执行锁定/解锁');
    return true;
  }
  const hasUnlocked = models.some((model) => !ensureMeta((model.getProperties?.() as any)?.meta).locked);
  models.forEach((model) => {
    updateNodeMeta(model, (meta) => ({ ...meta, locked: hasUnlocked ? true : false }));
  });
  return false;
}

function toggleVisibilitySelected(event?: KeyboardEvent) {
  if (shouldSkipShortcut(event)) return true;
  const models = getSelectedNodeModelsFiltered({ includeLocked: true });
  if (!models.length) {
    showMessage('info', '请选择节点后再执行显示/隐藏');
    return true;
  }
  const hasVisible = models.some((model) => ensureMeta((model.getProperties?.() as any)?.meta).visible !== false);
  models.forEach((model) => {
    updateNodeMeta(model, (meta) => ({ ...meta, visible: !hasVisible ? true : false }));
  });
  if (hasVisible) {
    selectedNode.value = null;
  }
  updateSelectedCount();
  return false;
}

function showAllNodes() {
  const lfInstance = lf.value;
  if (!lfInstance) return;
  let changed = 0;
  lfInstance.graphModel?.nodes.forEach((model: BaseNodeModel) => {
    const props = (model.getProperties?.() as any) ?? (model as any)?.properties ?? {};
    const meta = ensureMeta(props.meta);
    if (meta.visible === false) {
      meta.visible = true;
      lfInstance.setProperties(model.id, { ...props, meta });
      applyMetaToModel(model, meta);
      changed += 1;
    }
  });
  if (changed > 0) {
    showMessage('success', `已显示 ${changed} 个节点`);
  } else {
    showMessage('info', '没有隐藏的节点');
  }
  updateSelectedCount();
}

function groupSelectedNodes(event?: KeyboardEvent) {
  if (shouldSkipShortcut(event)) return true;
  const models = getSelectedNodeModelsFiltered();
  if (models.length < 2) {
    showMessage('warning', '请选择至少两个未锁定的节点进行分组');
    return true;
  }
  const groupId = `group_${Date.now().toString(36)}`;
  models.forEach((model) => {
    updateNodeMeta(model, (meta) => ({ ...meta, groupId }));
  });
  scheduleGroupRuleValidation();
  return false;
}

function ungroupSelectedNodes(event?: KeyboardEvent) {
  if (shouldSkipShortcut(event)) return true;
  const models = getSelectedNodeModels();
  if (!models.length) {
    showMessage('info', '请选择节点后再执行解组');
    return true;
  }
  models.forEach((model) => {
    updateNodeMeta(model, (meta) => {
      const nextMeta = { ...meta };
      delete nextMeta.groupId;
      return nextMeta;
    });
  });
  scheduleGroupRuleValidation();
  return false;
}

function handleArrowMove(direction: 'left' | 'right' | 'up' | 'down', event?: KeyboardEvent) {
  if (shouldSkipShortcut(event)) return true;
  const step = (event?.shiftKey ? MOVE_STEP_LARGE : MOVE_STEP) * (direction === 'left' || direction === 'up' ? -1 : 1);
  if (direction === 'left' || direction === 'right') {
    moveSelectedNodes(step, 0);
  } else {
    moveSelectedNodes(0, step);
  }
  return false;
}

function remapGroupIds(nodes: GraphData['nodes']) {
  const map = new Map<string, string>();
  const seed = Date.now().toString(36);
  nodes.forEach((node, index) => {
    const meta = ensureMeta((node as any).properties?.meta);
    if (meta.groupId) {
      if (!map.has(meta.groupId)) {
        map.set(meta.groupId, `group_${seed}_${index}`);
      }
      meta.groupId = map.get(meta.groupId);
    }
    (node as any).properties = { ...(node as any).properties, meta };
  });
}

function handleCopy(event?: KeyboardEvent) {
  if (shouldSkipShortcut(event)) return true;
  const lfInstance = lf.value;
  if (!lfInstance) return true;
  const elements = lfInstance.getSelectElements(false);
  if (!elements.nodes.length && !elements.edges.length) {
    copyBuffer.value = null;
    return true;
  }
  const nodes = elements.nodes.map((node) => translateNodeData(JSON.parse(JSON.stringify(node)), COPY_TRANSLATION));
  const edges = elements.edges.map((edge) => translateEdgeData(JSON.parse(JSON.stringify(edge)), COPY_TRANSLATION));
  remapGroupIds(nodes);
  copyBuffer.value = { nodes, edges };
  nextPasteDistance = COPY_TRANSLATION;
  return false;
}

function handlePaste(event?: KeyboardEvent) {
  if (shouldSkipShortcut(event)) return true;
  const lfInstance = lf.value;
  if (!lfInstance || !copyBuffer.value) return true;

  lfInstance.clearSelectElements();
  const added = lfInstance.addElements(copyBuffer.value, nextPasteDistance);
  if (added) {
    added.nodes.forEach((model) => {
      normalizeNodeModel(model);
      lfInstance.selectElementById(model.id, true);
    });
    added.edges.forEach((edge) => lfInstance.selectElementById(edge.id, true));
    copyBuffer.value.nodes.forEach((node) => translateNodeData(node, COPY_TRANSLATION));
    copyBuffer.value.edges.forEach((edge) => translateEdgeData(edge, COPY_TRANSLATION));
    nextPasteDistance += COPY_TRANSLATION;
    updateSelectedCount(lfInstance.graphModel);
  }
  return false;
}

function handleNodeDrag(args: { data: NodeData; deltaX: number; deltaY: number }) {
  const { data, deltaX, deltaY } = args;
  if (!deltaX && !deltaY) return;
  const graphModel = lf.value?.graphModel;
  if (!graphModel) return;
  const model = graphModel.getNodeModelById(data.id);
  if (!model) return;
  const targets = collectGroupNodeIds([model]).filter((id) => id !== model.id);
  if (!targets.length) return;
  graphModel.moveNodes(targets, deltaX, deltaY);
}

function updateSelectedCount(model?: GraphModel) {
  const lfInstance = lf.value;
  const graphModel = model ?? lfInstance?.graphModel;
  selectedCount.value = graphModel?.selectNodes.length ?? 0;
}

function refreshGroupRuleWarnings() {
  const lfInstance = lf.value;
  if (!lfInstance) {
    groupRuleWarnings.value = [];
    return;
  }
  const graphData = lfInstance.getGraphRawData() as GraphData;
  groupRuleWarnings.value = validateGraphGroupRules(graphData);
}

function scheduleGroupRuleValidation(delay = 120) {
  if (groupRuleValidationTimer) {
    clearTimeout(groupRuleValidationTimer);
  }
  groupRuleValidationTimer = setTimeout(() => {
    refreshGroupRuleWarnings();
  }, delay);
}

function applySelectionSelect(enabled: boolean) {
  const lfInstance = lf.value as any;
  if (!lfInstance) return;
  if (enabled) {
    lfInstance.openSelectionSelect?.();
  } else {
    lfInstance.closeSelectionSelect?.();
  }
}

function applySnapGrid(enabled: boolean) {
  const lfInstance = lf.value;
  if (!lfInstance) return;
  lfInstance.updateEditConfig({ snapGrid: enabled });
}

function getSelectedRects() {
  const lfInstance = lf.value;
  if (!lfInstance) return [];
  const actionable = getSelectedNodeModelsFiltered();
  return actionable.map((model: BaseNodeModel) => {
    const bounds = model.getBounds();
    const width = bounds.maxX - bounds.minX;
    const height = bounds.maxY - bounds.minY;
    return {
      model,
      bounds,
      width,
      height,
      centerX: (bounds.maxX + bounds.minX) / 2,
      centerY: (bounds.maxY + bounds.minY) / 2
    };
  });
}

function alignSelected(direction: AlignType) {
  const rects = getSelectedRects();
  if (rects.length < 2) {
    showMessage('warning', '请选择至少两个节点再执行对齐');
    return;
  }

  const minX = Math.min(...rects.map((item) => item.bounds.minX));
  const maxX = Math.max(...rects.map((item) => item.bounds.maxX));
  const minY = Math.min(...rects.map((item) => item.bounds.minY));
  const maxY = Math.max(...rects.map((item) => item.bounds.maxY));
  const centerX = (minX + maxX) / 2;
  const centerY = (minY + maxY) / 2;

  rects.forEach(({ model, width, height }) => {
    let targetX = model.x;
    let targetY = model.y;

    switch (direction) {
      case 'left':
        targetX = minX + width / 2;
        break;
      case 'right':
        targetX = maxX - width / 2;
        break;
      case 'hcenter':
        targetX = centerX;
        break;
      case 'top':
        targetY = minY + height / 2;
        break;
      case 'bottom':
        targetY = maxY - height / 2;
        break;
      case 'vcenter':
        targetY = centerY;
        break;
    }

    model.moveTo(targetX, targetY);
  });
}

function distributeSelected(type: DistributeType) {
  const rects = getSelectedRects();
  if (rects.length < 3) {
    showMessage('warning', '请选择至少三个节点再执行分布');
    return;
  }

  const sorted = [...rects].sort((a, b) =>
    type === 'horizontal' ? a.bounds.minX - b.bounds.minX : a.bounds.minY - b.bounds.minY
  );
  const first = sorted[0];
  const last = sorted[sorted.length - 1];

  if (type === 'horizontal') {
    const totalWidth = sorted.reduce((sum, item) => sum + item.width, 0);
    const gap = (last.bounds.maxX - first.bounds.minX - totalWidth) / (sorted.length - 1);
    let cursor = first.bounds.minX + first.width;

    for (let i = 1; i < sorted.length - 1; i += 1) {
      cursor += gap;
      const item = sorted[i];
      item.model.moveTo(cursor + item.width / 2, item.centerY);
      cursor += item.width;
    }
  } else {
    const totalHeight = sorted.reduce((sum, item) => sum + item.height, 0);
    const gap = (last.bounds.maxY - first.bounds.minY - totalHeight) / (sorted.length - 1);
    let cursor = first.bounds.minY + first.height;

    for (let i = 1; i < sorted.length - 1; i += 1) {
      cursor += gap;
      const item = sorted[i];
      item.model.moveTo(item.centerX, cursor + item.height / 2);
      cursor += item.height;
    }
  }
}

// 注册自定义节点
function registerNodes(lfInstance: LogicFlow) {
  register({ type: 'propertySelect', component: PropertySelectNode }, lfInstance);

  register({ type: 'imageNode', component: ImageNode }, lfInstance);
  register({ type: 'assetSelector', component: AssetSelectorNode }, lfInstance);
  register({ type: 'textNode', component: TextNode, model: TextNodeModel }, lfInstance);
  register({ type: 'vectorNode', component: VectorNode, model: VectorNodeModel }, lfInstance);
}

// 初始化 LogicFlow
onMounted(() => {
  lf.value = new LogicFlow({
    container: containerRef.value,
    grid: { type: 'dot', size: 10 },
    allowResize: true,
    allowRotate: true,
    overlapMode: -1,
    snapline: snaplineEnabled.value,
    keyboard: {
      enabled: true
    },
    style: {
      text: {
        color: '#333333',
        fontSize: 14,
        background: {
          fill: '#ffffff',
          stroke: '#dcdfe6',
          strokeWidth: 1,
          radius: 4
        }
      },
      nodeText: {
        color: '#333333',
        fontSize: 14
      }
    },
    plugins: [
      Menu,
      ...(props.enableLabel ? [Label] : []),
      Snapshot,
      SelectionSelect,
      MiniMap,
      Control
    ],
    pluginsOptions: {
      label: {
        isMultiple: false, // 每个节点只允许一个 label
        // 不设置全局 labelWidth，让每个节点自己控制
        // textOverflowMode -> 'ellipsis' | 'wrap' | 'clip' | 'nowrap' | 'default'
        textOverflowMode: 'wrap',
      },
      miniMap: {
        isShowHeader: false,
        isShowCloseIcon: true,
        width: 200,
        height: 140,
        rightPosition: 16,
        bottomPosition: 16
      }
    },
  });

  const lfInstance = lf.value;
  if (!lfInstance) return;

  lfInstance.keyboard.off(['cmd + c', 'ctrl + c']);
  lfInstance.keyboard.off(['cmd + v', 'ctrl + v']);
  lfInstance.keyboard.off(['backspace']);

  const bindShortcut = (keys: string | string[], handler: (event?: KeyboardEvent) => boolean | void) => {
    lfInstance.keyboard.on(keys, (event: KeyboardEvent) => handler(event));
  };

  bindShortcut(['del', 'backspace'], deleteSelectedElements);
  bindShortcut(['left'], (event) => handleArrowMove('left', event));
  bindShortcut(['right'], (event) => handleArrowMove('right', event));
  bindShortcut(['up'], (event) => handleArrowMove('up', event));
  bindShortcut(['down'], (event) => handleArrowMove('down', event));
  bindShortcut(['cmd + c', 'ctrl + c'], handleCopy);
  bindShortcut(['cmd + v', 'ctrl + v'], handlePaste);
  bindShortcut(['cmd + g', 'ctrl + g'], groupSelectedNodes);
  bindShortcut(['cmd + u', 'ctrl + u'], ungroupSelectedNodes);
  bindShortcut(['cmd + l', 'ctrl + l'], toggleLockSelected);
  bindShortcut(['cmd + shift + h', 'ctrl + shift + h'], toggleVisibilitySelected);

  lfInstance.extension.menu.addMenuConfig({
    nodeMenu: [
      {
        text: '置于顶层',
        callback(node: NodeData) {
          bringToFront(node.id);
        }
      },
      {
        text: '上移一层',
        callback(node: NodeData) {
          bringForward(node.id);
        }
      },
      {
        text: '下移一层',
        callback(node: NodeData) {
          sendBackward(node.id);
        }
      },
      {
        text: '置于底层',
        callback(node: NodeData) {
          sendToBack(node.id);
        }
      },
      {
        text: '---' // 分隔线
      },
      {
        text: '复制 (Ctrl+C)',
        callback() {
          handleCopy();
        }
      },
      {
        text: '粘贴 (Ctrl+V)',
        callback() {
          handlePaste();
        }
      },
      {
        text: '---' // 分隔线
      },
      {
        text: '组合 (Ctrl+G)',
        callback() {
          groupSelectedNodes();
        }
      },
      {
        text: '解组 (Ctrl+U)',
        callback() {
          ungroupSelectedNodes();
        }
      },
      {
        text: '---' // 分隔线
      },
      {
        text: '锁定/解锁 (Ctrl+L)',
        callback() {
          toggleLockSelected();
        }
      },
      {
        text: '显示/隐藏 (Ctrl+Shift+H)',
        callback() {
          toggleVisibilitySelected();
        }
      },
      {
        text: '---' // 分隔线
      },
      {
        text: '删除节点 (Del)',
        callback(node: NodeData) {
          deleteNode(node.id);
        }
      }
    ],
    edgeMenu: [
      {
        text: '删除边',
        callback(edge: EdgeData) {
          lfInstance.deleteEdge(edge.id);
        }
      }
    ],
    graphMenu: [
      {
        text: '添加节点',
        callback(data: Position) {
          lfInstance.addNode({
            type: 'rect',
            x: data.x,
            y: data.y
          });
        }
      },
      {
        text: '粘贴 (Ctrl+V)',
        callback(data: Position) {
          handlePaste();
        }
      }
    ]
  });

  // 配置多选时的右键菜单（选区菜单）
  lfInstance.extension.menu.setMenuByType({
    type: 'lf:defaultSelectionMenu',
    menu: [
      {
        text: '复制 (Ctrl+C)',
        callback() {
          handleCopy();
        }
      },
      {
        text: '粘贴 (Ctrl+V)',
        callback() {
          handlePaste();
        }
      },
      {
        text: '---' // 分隔线
      },
      {
        text: '组合 (Ctrl+G)',
        callback() {
          groupSelectedNodes();
        }
      },
      {
        text: '解组 (Ctrl+U)',
        callback() {
          ungroupSelectedNodes();
        }
      },
      {
        text: '---' // 分隔线
      },
      {
        text: '锁定/解锁 (Ctrl+L)',
        callback() {
          toggleLockSelected();
        }
      },
      {
        text: '显示/隐藏 (Ctrl+Shift+H)',
        callback() {
          toggleVisibilitySelected();
        }
      },
      {
        text: '---' // 分隔线
      },
      {
        text: '删除选中 (Del)',
        callback() {
          deleteSelectedElements();
        }
      }
    ]
  });

  registerNodes(lfInstance);
  setLogicFlowInstance(lfInstance);

  // 监听所有可能的节点添加事件
  lfInstance.on(EventType.NODE_ADD, ({ data }) => {
    const model = lfInstance.getNodeModelById(data.id);
    if (model) {
      normalizeNodeModel(model);
      // 设置新节点的 zIndex 为 1000
      model.setZIndex(1000);
      // 标记这个节点是新创建的，避免被 normalizeAllNodes 重置
      (model as any)._isNewNode = true;
    }
    scheduleGroupRuleValidation();
  });

  // 监听 DND 添加节点事件
  lfInstance.on('node:dnd-add', ({ data }) => {
    const model = lfInstance.getNodeModelById(data.id);
    if (model) {
      // 设置新节点的 zIndex 为 1000
      model.setZIndex(1000);
      // 标记这个节点是新创建的
      (model as any)._isNewNode = true;
    }
    scheduleGroupRuleValidation();
  });

  lfInstance.on(EventType.GRAPH_RENDERED, () => {
    normalizeAllNodes();
    scheduleGroupRuleValidation(0);
  });

  // 监听节点点击事件，更新选中节点
  lfInstance.on(EventType.NODE_CLICK, ({ data }) => {
    selectedNode.value = data;
  });

  // 监听空白点击事件，取消选中
  lfInstance.on(EventType.BLANK_CLICK, () => {
    selectedNode.value = null;
    updateSelectedCount();
  });

  // 节点属性改变，如果当前节点是选中节点，则同步更新 selectedNode
  lfInstance.on(EventType.NODE_PROPERTIES_CHANGE, (data) => {
    const nodeId = data.id;
    if (selectedNode.value && nodeId === selectedNode.value.id) {
      if (data.properties) {
        selectedNode.value = {
          ...selectedNode.value,
          properties: data.properties
        };
      }
    }
    const model = lfInstance.getNodeModelById(nodeId);
    if (model) normalizeNodeModel(model);
    scheduleGroupRuleValidation();
  });

  lfInstance.on(EventType.NODE_DELETE, () => {
    scheduleGroupRuleValidation();
  });
  lfInstance.on(EventType.EDGE_ADD, () => {
    scheduleGroupRuleValidation();
  });
  lfInstance.on(EventType.EDGE_DELETE, () => {
    scheduleGroupRuleValidation();
  });

  lfInstance.on('selection:selected', () => updateSelectedCount());
  lfInstance.on('selection:drop', () => updateSelectedCount());

  nextTick(() => {
    queueCanvasResize();
  });
  if (typeof ResizeObserver !== 'undefined') {
    containerResizeObserver = new ResizeObserver(() => {
      resizeCanvas();
    });
    if (flowHostRef.value) {
      containerResizeObserver.observe(flowHostRef.value);
    }
    if (containerRef.value && containerRef.value !== flowHostRef.value) {
      containerResizeObserver.observe(containerRef.value);
    }
  }
  window.addEventListener('resize', handleWindowResize);
  unsubscribeSharedGroupRules = subscribeSharedGroupRulesConfig(() => {
    scheduleGroupRuleValidation(0);
  });
});

watch(selectionEnabled, (enabled) => {
  applySelectionSelect(enabled);
});

watch(snapGridEnabled, (enabled) => {
  applySnapGrid(enabled);
});

watch(snaplineEnabled, (enabled) => {
  const lfInstance = lf.value as any;
  if (!lfInstance) return;
  if (!enabled) {
    lfInstance.snaplineModel?.clearSnapline?.();
  }
});

watch(
  () => props.height,
  () => {
    nextTick(() => {
      resizeCanvas();
    });
  }
);

defineExpose({
  resizeCanvas
});

// 销毁 LogicFlow
onBeforeUnmount(() => {
  window.removeEventListener('resize', handleWindowResize);
  containerResizeObserver?.disconnect();
  containerResizeObserver = null;
  if (groupRuleValidationTimer) {
    clearTimeout(groupRuleValidationTimer);
    groupRuleValidationTimer = null;
  }
  unsubscribeSharedGroupRules?.();
  unsubscribeSharedGroupRules = null;
  lf.value?.destroy();
  lf.value = null;
  destroyLogicFlowInstance();
});




</script>

<style scoped>
.editor-layout {
  display: flex;
  height: 100%;
  width: 100%;
  min-width: 0;
  flex: 1;
}
.flow-container {
  flex: 1;
  min-width: 0;
  min-height: 0;
  position: relative;
  overflow: hidden;
}
.container {
  width: 100%;
  min-height: 0;
  background: #fff;
  height: 100%;
}
.flow-controls {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 10;
  background: rgba(255, 255, 255, 0.95);
  border: 1px solid #ebeef5;
  border-radius: 8px;
  padding: 10px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
  max-width: 460px;
  font-size: 12px;
}
.flow-controls--collapsed {
  padding: 6px;
  max-width: 220px;
}
.control-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 6px;
}
.control-header {
  margin-bottom: 0;
}
.control-row:last-child {
  margin-bottom: 0;
}
.rule-row {
  align-items: flex-start;
}
.rule-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-width: 360px;
}
.rule-item {
  color: #9a3412;
  background: #fff7ed;
  border: 1px solid #fed7aa;
  border-radius: 6px;
  padding: 4px 6px;
  line-height: 1.4;
}
.control-label {
  font-weight: 600;
  color: #303133;
}
.control-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.control-button {
  border: 1px solid #dcdfe6;
  background: #fff;
  border-radius: 4px;
  padding: 4px 8px;
  cursor: pointer;
  font-size: 12px;
  color: #303133;
}
.control-button:hover:enabled {
  background: #f5f7fa;
}
.control-button:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}
.control-toggle {
  display: flex;
  align-items: center;
  gap: 4px;
  color: #606266;
}
.control-hint {
  color: #909399;
}
.context-menu {
  position: fixed;
  background: white;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  padding: 5px 0;
  z-index: 9999;
  min-width: 120px;
  user-select: none;
}
.menu-item {
  padding: 8px 16px;
  cursor: pointer;
  font-size: 14px;
  color: #606266;
  white-space: nowrap;
}
.menu-item:hover {
  background-color: #f5f7fa;
  color: #409eff;
}

.snapline-disabled .lf-snapline {
  display: none;
}
</style>
