using NavisworksTransport.Core.AliasTree;
using NavisworksTransport.Core.Config;
using NavisworksTransport.UI.WPF.ViewModels;
using NavisworksTransport.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Autodesk.Navisworks.Api.DocumentParts;
using NavisApplication = Autodesk.Navisworks.Api.Application;

namespace NavisworksTransport.UI.WPF.Views
{
    /// <summary>
    /// 别名导航树面板 — 核心 UI 逻辑
    /// </summary>
    public partial class AliasTreeControl : UserControl
    {
        // ============================================================
        // 状态
        // ============================================================

        private AliasDataStore _dataStore;
        private string _copiedAliasTreeJson;

        /// <summary>当控件检测到模型就绪但数据存储未连接时触发</summary>
        internal event Action OnDatabaseNeeded;
        /// <summary>保存别名前触发（确保数据库已创建）</summary>
        internal event Action BeforeSaveAlias;
        private Dictionary<string, AliasNodeViewModel> _nodeMap = new Dictionary<string, AliasNodeViewModel>();
        /// <summary>记录 ExpandAndSelect 按需加载但无别名的深层节点，下次选择时清理</summary>
        private HashSet<string> _onDemandNodeKeys = new HashSet<string>();
        private bool _isInternalSelection = false;
        private Autodesk.Navisworks.Api.ModelItem _currentSelectionItem;
        private bool _built = false;

        /// <summary>完整展示的最大层级深度（默认2层）</summary>
        public int MaxFullDisplayDepth { get; set; } = 2;

        // ============================================================
        // 生命周期
        // ============================================================

        public AliasTreeControl()
        {
            InitializeComponent();

            // 从配置文件读取初始值
            MaxFullDisplayDepth = ConfigManager.Instance.Current.AliasTree?.MaxFullDisplayDepth ?? 2;

            // 订阅配置变更，实时生效
            ConfigManager.Instance.CategoryConfigurationChanged += OnAliasTreeConfigChanged;

            // 将 AliasTreeView 的 items 源指向根节点集合
            var rootNodes = new System.Collections.ObjectModel.ObservableCollection<AliasNodeViewModel>();
            AliasTreeView.ItemsSource = rootNodes;

            // 文档切换事件
            NavisApplication.ActiveDocumentChanged += (s, e) => { _built = false; RebuildTree(); };
            NavisApplication.ActiveDocumentChanging += (s, e) => { _built = false; };

            // 模型加载事件：面板启动时文档已存在但模型未加载时触发
            var doc = NavisApplication.ActiveDocument;
            if (doc != null)
                doc.Models.CollectionChanged += (s, e) =>
                {
                    _built = false;
                    RebuildTree();
                    OnDatabaseNeeded?.Invoke();
                };

            HookSelectionEvents();

            // 立即构建：面板打开时如果文档已存在，不走事件也要构建
            RebuildTree();
        }

        /// <summary>
        /// 获取当前数据存储（null 表示未连接）
        /// </summary>
        public AliasDataStore GetDataStore() => _dataStore;

        /// <summary>
        /// 设置数据存储（由外部在 PathDatabase 就绪后调用）
        /// </summary>
        public void SetDataStore(AliasDataStore store)
        {
            _dataStore = store;
            LogManager.Info($"[别名树] SetDataStore: _built={_built}, aliasCount={store?.Count}");
            if (!_built)
                RebuildTree();
            else
                RefreshAliasesFromStore();

            // 无论树是否已构建，确保已存别名的深层节点路径被加载
            var aliasMap = _dataStore?.LoadAll();
            if (aliasMap != null && aliasMap.Count > 0)
                EnsureAliasedPathsVisible(aliasMap);

            // EnsureAliasedPathsVisible 可能创建了新节点，需要再刷一次别名到这些节点上
            if (_built)
                RefreshAliasesFromStore();
        }

        private void RefreshAliasesFromStore()
        {
            if (_dataStore == null) return;
            try
            {
                var aliasMap = _dataStore.LoadAll();
                LogManager.Info($"[别名树] RefreshAliases: loaded={aliasMap.Count}, nodeMap={_nodeMap.Count}");
                int matched = 0;
                foreach (var kvp in aliasMap)
                {
                    int sepIdx = kvp.Key.IndexOf("||", StringComparison.Ordinal);
                    string lookupKey = sepIdx >= 0 ? kvp.Key.Substring(sepIdx + 2) : kvp.Key;
                    if (_nodeMap.TryGetValue(lookupKey, out var node))
                    {
                        try
                        {
                            node.Alias = kvp.Value;
                            matched++;
                        }
                        catch (Exception ex)
                        {
                            LogManager.Error($"[别名树] RefreshAliases set error: key={kvp.Key}, {ex.Message}");
                        }
                    }
                }
                LogManager.Info($"[别名树] RefreshAliases: matched={matched}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] RefreshAliases 异常: {ex.Message}");
            }
        }

        /// <summary>
        /// 清理资源
        /// </summary>
        public void Cleanup()
        {
            ConfigManager.Instance.CategoryConfigurationChanged -= OnAliasTreeConfigChanged;
            UnhookSelectionEvents();
            _dataStore = null;
            _nodeMap.Clear();
        }

        private void OnAliasTreeConfigChanged(object sender, Tuple<ConfigCategory, ConfigurationChangedEventArgs> args)
        {
            var config = ConfigManager.Instance.Current;
            int newDepth = config.AliasTree?.MaxFullDisplayDepth ?? 2;
            if (MaxFullDisplayDepth != newDepth)
            {
                MaxFullDisplayDepth = newDepth;
                LogManager.Info($"[别名树] 配置变更: MaxFullDisplayDepth={newDepth}");
                _built = false;
                RebuildTree();
            }
        }

        // ============================================================
        // 选择事件（双向联动）
        // ============================================================

        private bool _selectionHooked = false;

        private void HookSelectionEvents()
        {
            if (_selectionHooked) return;
            try
            {
                var doc = NavisApplication.ActiveDocument;
                if (doc != null)
                {
                    doc.CurrentSelection.Changed += OnNavisSelectionChanged;
                    _selectionHooked = true;
                }
            }
            catch (Exception ex) { LogManager.Debug($"[别名树] HookSelection事件异常: {ex.Message}"); }
        }

        private void UnhookSelectionEvents()
        {
            try
            {
                var doc = NavisApplication.ActiveDocument;
                if (doc != null)
                {
                    doc.CurrentSelection.Changed -= OnNavisSelectionChanged;
                }
            }
            catch (Exception ex) { LogManager.Debug($"[别名树] UnhookSelection异常: {ex.Message}"); }
            _selectionHooked = false;
        }

        /// <summary>
        /// 别名树选中 → 同步到 Navisworks 选择
        /// </summary>
        private void OnTreeViewSelected(AliasNodeViewModel node)
        {
            // 由 NW 选择触发的 ExpandAndSelect 过程中的被动选中事件，不反向同步
            if (_isInternalSelection) return;

            LogManager.Debug($"[别名树] OnTreeViewSelected: {node.OriginalName}");

            _isInternalSelection = true;
            try
            {
                _currentSelectionItem = node.ModelItem;
                TbCurrentNode.Text = $"当前: {node.OriginalName}";
                var doc = NavisApplication.ActiveDocument;
                if (doc != null && doc.CurrentSelection != null && node.ModelItem != null)
                {
                    var coll = new Autodesk.Navisworks.Api.ModelItemCollection { node.ModelItem };
                    try
                    {
                        doc.CurrentSelection.Clear();
                        doc.CurrentSelection.CopyFrom(coll);
                    }
                    catch (NullReferenceException)
                    {
                        // NW API 内部 NRE（文档过渡状态），忽略
                    }
                    catch (Exception ex)
                    {
                        LogManager.Error($"[别名树] NW选择异常: {ex.GetType().Name}");
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] 选中同步异常: {ex.Message}");
            }
            finally
            {
                _isInternalSelection = false;
            }
        }

        private void StartInlineEdit(AliasNodeViewModel node, TreeViewItem tvi)
        {
            // 直接用，不需要 tvi
            StartInlineEdit(node);
        }

        private void StartInlineEdit(AliasNodeViewModel node)
        {
            ClearAllEditing();
            node.IsEditing = true;
        }

        /// <summary>
        /// Navisworks 选择变化 → 同步到别名树高亮
        /// </summary>
        private void OnNavisSelectionChanged(object sender, EventArgs e)
        {
            if (_isInternalSelection) return;

            try
            {
                var doc = NavisApplication.ActiveDocument;
                if (doc == null) return;

                var selected = doc.CurrentSelection?.SelectedItems;
                if (selected.Count == 1)
                {
                    var item = selected.First();
                    _currentSelectionItem = item;
                    TbCurrentNode.Text = $"当前: {item.DisplayName}";

                    LogManager.Debug($"[别名树] NW选中: {item.DisplayName}");

                    ExpandAndSelect(item);
                }
                else
                {
                    _currentSelectionItem = null;
                    TbCurrentNode.Text = selected.Count > 1 ? $"当前: ({selected.Count} 个选中)" : "当前: 无";
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] 选择变化处理异常: {ex.Message}");
            }
        }

        // ============================================================
        // 树构建
        // ============================================================

        private void RebuildTree()
        {
            if (_built) return;
            _built = true;

            try
            {
                var rootNodes = AliasTreeView.ItemsSource as System.Collections.ObjectModel.ObservableCollection<AliasNodeViewModel>;
                if (rootNodes == null) return;

                rootNodes.Clear();
                _nodeMap.Clear();

                var doc = NavisApplication.ActiveDocument;
                if (doc == null || doc.Models == null) return;

                // 全量加载别名到内存
                Dictionary<string, string> aliasMap = null;
                Dictionary<string, string> displayPathToAlias = null;
                if (_dataStore != null)
                {
                    aliasMap = _dataStore.LoadAll();
                    displayPathToAlias = BuildDisplayPathAliasMap(aliasMap);
                }

                // 遍历所有 root items
                foreach (var model in doc.Models)
                {
                    var rootItem = model.RootItem;
                    if (rootItem != null)
                    {
                        var node = BuildNode(rootItem, aliasMap, displayPathToAlias, 0, "", 0);
                        if (node != null)
                            rootNodes.Add(node);
                    }
                }

                // 重新挂载选择事件（可能文档刚切换）
                HookSelectionEvents();

                // 对已有别名的深层节点，强制加载其路径使其可见
                if (aliasMap != null && aliasMap.Count > 0)
                    EnsureAliasedPathsVisible(aliasMap);

                LogManager.Info($"[别名树] 树构建完成，共 {_nodeMap.Count} 个节点");
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] 构建失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 递归构建节点树（主线程调用）。
        /// siblingIndex 用于同名兄弟去重，0 表示第一个（不追加后缀）。
        /// </summary>
        private AliasNodeViewModel BuildNode(Autodesk.Navisworks.Api.ModelItem modelItem,
                                               Dictionary<string, string> aliasMap,
                                               Dictionary<string, string> displayPathToAlias,
                                               int depth, string parentDisplayPath, int siblingIndex)
        {
            if (modelItem == null) return null;

            string displayName = !string.IsNullOrEmpty(modelItem.DisplayName) ? modelItem.DisplayName : "几何对象";
            // 父节点 key 可能是完整格式 "IndexPath||DisplayPath"，也可能是 ".../name#N"
            // 只取纯 DisplayPath 部分（去掉 || 前缀和结尾的 #N 后缀）
            string parentPath = parentDisplayPath;
            int sepIdx = parentPath.IndexOf("||", StringComparison.Ordinal);
            if (sepIdx >= 0) parentPath = parentPath.Substring(sepIdx + 2);
            string displayPath = string.IsNullOrEmpty(parentPath)
                ? displayName
                : parentPath + "/" + displayName;
            // 同名兄弟去重：第二个起追加 #索引
            string displayPathKey = siblingIndex > 0 ? displayPath + "#" + siblingIndex : displayPath;

            // 别名匹配：精确匹配 displayPathKey
            string alias = null;
            displayPathToAlias?.TryGetValue(displayPathKey, out alias);

            var node = new AliasNodeViewModel
            {
                NodeKey = displayPathKey,
                ModelItem = modelItem,
                OriginalName = displayName,
                Alias = alias,
                ChildCount = modelItem.Children.Count(),
                Depth = depth
            };

            _nodeMap[displayPathKey] = node;

            // 深度限制：超过阈值且无别名 → 不展开子节点，不加占位
            bool hasChildren = node.ChildCount > 0;
            if (hasChildren && depth >= MaxFullDisplayDepth && string.IsNullOrEmpty(alias))
                hasChildren = false;

            if (hasChildren)
            {
                node.Children.Add(new AliasNodeViewModel
                {
                    DisplayName = "加载中...",
                    OriginalName = "加载中..."
                });
            }

            return node;
        }

        private void LazyLoadChildren(AliasNodeViewModel parentNode, Dictionary<string, string> aliasMap,
                                       Dictionary<string, string> displayPathToAlias)
        {
            // 检查是否只有占位节点
            if (parentNode.Children.Count != 1 || parentNode.Children[0].OriginalName != "加载中...")
                return;

            parentNode.Children.Clear();

            try
            {
                var item = parentNode.ModelItem;
                if (item == null) return;

                // 同名去重：统计每个名字已出现的次数
                var nameCount = new Dictionary<string, int>();
                foreach (var childItem in item.Children)
                {
                    string name = !string.IsNullOrEmpty(childItem.DisplayName) ? childItem.DisplayName : "几何对象";
                    nameCount.TryGetValue(name, out int count);
                    int siblingIdx = count;
                    nameCount[name] = count + 1;

                    var childNode = BuildNode(childItem, aliasMap, displayPathToAlias,
                        parentNode.Depth + 1, parentNode.NodeKey, siblingIdx);
                    if (childNode != null)
                    {
                        childNode.Parent = parentNode;
                        parentNode.Children.Add(childNode);
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] 懒加载子节点失败: {ex.Message}");
            }
        }

        // ============================================================
        /// <summary>
        /// 从 aliasMap 构建 DisplayPath → alias 索引（去掉 IndexPath|| 前缀）
        /// </summary>
        private static Dictionary<string, string> BuildDisplayPathAliasMap(Dictionary<string, string> aliasMap)
        {
            if (aliasMap == null || aliasMap.Count == 0) return null;
            var map = new Dictionary<string, string>();
            foreach (var kvp in aliasMap)
            {
                // key 格式: "IndexPath||DisplayPath" → 取 DisplayPath 部分
                int sepIdx = kvp.Key.IndexOf("||", StringComparison.Ordinal);
                string displayPath = sepIdx >= 0 ? kvp.Key.Substring(sepIdx + 2) : kvp.Key;
                map[displayPath] = kvp.Value;
            }
            return map;
        }
        // ============================================================

        /// <summary>
        /// 确保节点有完整 NodeKey（含 IndexPath）。首次保存别名时调用 CreatePathId 升级 key。
        /// </summary>
        private void EnsureFullNodeKey(AliasNodeViewModel node)
        {
            if (node == null || node.ModelItem == null) return;
            // 已是完整格式
            if (node.NodeKey != null && node.NodeKey.Contains("||")) return;

            try
            {
                var identity = AliasNodeIdentity.FromModelItem(node.ModelItem);
                node.NodeKey = identity.ToKey();
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] EnsureFullNodeKey 失败: {ex.Message}");
            }
        }

        private Autodesk.Navisworks.Api.ModelItem FindModelItemByNodeKey(string key)
        {
            if (string.IsNullOrEmpty(key)) return null;

            // 判断 key 格式：含 "||" 为完整格式 → ResolvePathId；否则为 DisplayPath → _nodeMap 查找
            int sepIdx = key.IndexOf("||", StringComparison.Ordinal);
            if (sepIdx >= 0)
            {
                if (!AliasNodeIdentity.TryParseKey(key, out var identity) || string.IsNullOrEmpty(identity.IndexPath))
                    return null;
                return ResolveByIndexPath(identity.IndexPath);
            }

            // 简化格式：DisplayPath → 查 _nodeMap
            if (_nodeMap.TryGetValue(key, out var node) && node.ModelItem != null)
                return node.ModelItem;

            return null;
        }

        private Autodesk.Navisworks.Api.ModelItem ResolveByIndexPath(string indexPath)
        {
            try
            {
                var doc = NavisApplication.ActiveDocument;
                if (doc == null || doc.Models == null) return null;

                int colonIdx = indexPath.IndexOf(':');
                if (colonIdx < 0) return null;
                if (!int.TryParse(indexPath.Substring(0, colonIdx), out int modelIndex))
                    return null;
                string pathId = indexPath.Substring(colonIdx + 1);

                return doc.Models.ResolvePathId(new ModelItemPathId { ModelIndex = modelIndex, PathId = pathId });
            }
            catch
            {
                return null;
            }
        }

        // ============================================================
        // 展开并高亮指定 ModelItem
        // 策略：利用 ModelItem.Parent 链逐层匹配，不依赖 IndexPath。
        // ============================================================

        private void ExpandAndSelect(Autodesk.Navisworks.Api.ModelItem item)
        {
            if (item == null) return;

            LogManager.Info($"[别名树] ExpandAndSelect: target={item.DisplayName}");

            // 整个 ExpandAndSelect 期间阻止树→NW 反向联动
            _isInternalSelection = true;
            try
            {
                ExpandAndSelectInternal(item);
            }
            finally
            {
                _isInternalSelection = false;
            }
        }

        private void ExpandAndSelectInternal(Autodesk.Navisworks.Api.ModelItem item)
        {
            // 0. 清理上次按需加载的无别名深层节点
            CleanupOnDemandNodes();

            // 1. 沿 Parent 链构建祖先列表（根 → 目标）
            var chain = new System.Collections.Generic.List<Autodesk.Navisworks.Api.ModelItem>();
            var current = item;
            while (current != null)
            {
                chain.Insert(0, current);
                current = current.Parent;
            }

            // 2. 在 rootNodes 中找匹配的根节点
            var rootNodes = AliasTreeView.ItemsSource
                as System.Collections.ObjectModel.ObservableCollection<AliasNodeViewModel>;
            if (rootNodes == null || rootNodes.Count == 0) return;

            AliasNodeViewModel treeNode = null;
            foreach (var rn in rootNodes)
            {
                if (rn.ModelItem != null && rn.ModelItem.Equals(chain[0]))
                {
                    treeNode = rn;
                    break;
                }
            }
            if (treeNode == null)
            {
                LogManager.Warning($"[别名树] 根节点未找到: {chain[0].DisplayName}");
                return;
            }

            // 3. 目标就是根节点
            if (chain.Count == 1)
            {
                SelectTreeNode(treeNode);
                return;
            }

            // 4. 沿链逐层向下，匹配/加载子节点
            for (int i = 1; i < chain.Count; i++)
            {
                var targetModelItem = chain[i];

                // 展开当前节点
                ExpandTreeNode(treeNode);

                // 加载子节点（有占位 → 全量懒加载；否则只找目标那一个）
                var aliasMap = _dataStore?.LoadAll();
                var displayAlias = BuildDisplayPathAliasMap(aliasMap);
                if (treeNode.Children.Count == 1 && treeNode.Children[0].OriginalName == "加载中...")
                {
                    LazyLoadChildren(treeNode, aliasMap, displayAlias);
                }

                // 在已加载的子节点中查找匹配
                AliasNodeViewModel child = null;
                foreach (var c in treeNode.Children)
                {
                    if (c.OriginalName != "加载中..." && c.ModelItem != null && c.ModelItem.Equals(targetModelItem))
                    {
                        child = c;
                        break;
                    }
                }

                // 没找到（深度超限节点）→ 直接用 ModelItem 创建
                if (child == null)
                {
                    child = BuildNode(targetModelItem, aliasMap, displayAlias,
                        treeNode.Depth + 1, treeNode.NodeKey, 0);
                    if (child != null)
                    {
                        child.Parent = treeNode;
                        treeNode.Children.Add(child);
                        // 超限且无别名 → 下次选择时清理
                        if (child.Depth >= MaxFullDisplayDepth && !child.HasAlias)
                            _onDemandNodeKeys.Add(child.NodeKey);
                    }
                }

                if (child == null)
                {
                    LogManager.Warning($"[别名树] 无法定位子节点: {targetModelItem.DisplayName}");
                    return;
                }

                treeNode = child;
            }

            // 5. 选中最终节点
            SelectTreeNode(treeNode);
        }

        /// <summary>
        /// 展开树节点（程序化，不触发 Expanded 事件）
        /// </summary>
        private void ExpandTreeNode(AliasNodeViewModel node)
        {
            var tvi = GetTreeViewItemForNode(node);
            if (tvi != null)
            {
                _isProgrammaticExpand = true;
                tvi.IsExpanded = true;
                _isProgrammaticExpand = false;
                tvi.UpdateLayout();
            }
        }

        /// <summary>
        /// 选中并滚动到指定树节点
        /// </summary>
        private void SelectTreeNode(AliasNodeViewModel node)
        {
            var parent = node.Parent;
            if (parent != null)
            {
                var parentTvi = GetTreeViewItemForNode(parent);
                if (parentTvi != null)
                {
                    parentTvi.BringIntoView();
                    parentTvi.UpdateLayout();

                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        AliasTreeView.UpdateLayout();
                        var childTvi = parentTvi.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem;
                        if (childTvi != null)
                        {
                            _isProgrammaticSelect = true;
                            childTvi.IsSelected = true;
                            _isProgrammaticSelect = false;
                            childTvi.BringIntoView();
                        }
                    }), System.Windows.Threading.DispatcherPriority.Background);
                    return;
                }
            }

            // 无父节点（根节点）直接选中
            var tvi = GetTreeViewItemForNode(node);
            if (tvi != null)
            {
                _isProgrammaticSelect = true;
                tvi.IsSelected = true;
                _isProgrammaticSelect = false;
                tvi.BringIntoView();
            }
        }

        /// <summary>
        /// 清理上次按需加载的、无别名的深层节点。
        /// ExpandAndSelect 每次调用前执行，确保前一次超限无别名节点被移除。
        /// </summary>
        private void CleanupOnDemandNodes()
        {
            if (_onDemandNodeKeys.Count == 0) return;

            foreach (var key in _onDemandNodeKeys)
            {
                if (_nodeMap.TryGetValue(key, out var node))
                {
                    node.Parent?.Children.Remove(node);
                    _nodeMap.Remove(key);
                }
            }
            _onDemandNodeKeys.Clear();
        }

        /// <summary>
        /// 确保所有已存别名的节点路径在树中可见。
        /// 在 RebuildTree 后调用，对深层别名节点逐级加载其祖先。
        /// </summary>
        private void EnsureAliasedPathsVisible(Dictionary<string, string> aliasMap)
        {
            LogManager.Info($"[别名树] EnsureAliasedPathsVisible: 共 {aliasMap.Count} 条别名");
            foreach (var kvp in aliasMap)
            {
                LogManager.Debug($"[别名树]   alias: key={kvp.Key} -> \"{kvp.Value}\"");
            }

            int loaded = 0;
            foreach (var kvp in aliasMap)
            {
                try
                {
                    LogManager.Debug($"[别名树] EnsureAliased: key={kvp.Key}, alias={kvp.Value}");
                    var item = FindModelItemByNodeKey(kvp.Key);
                    if (item == null)
                    {
                        LogManager.Debug($"[别名树] EnsureAliased: FindModelItemByNodeKey null, key={kvp.Key}");
                        continue;
                    }
                    if (EnsurePathLoaded(item))
                        loaded++;
                }
                catch (Exception ex)
                {
                    LogManager.Error($"[别名树] 加载别名路径失败: {kvp.Key}, {ex.Message}");
                }
            }
            if (loaded > 0)
                LogManager.Info($"[别名树] 已确保 {loaded} 个别名节点路径可见");
        }

        /// <summary>
        /// 沿 ModelItem 父链逐层加载节点到树中（不选中）。
        /// 逻辑与 ExpandAndSelect 相同，只是最终不选中。
        /// </summary>
        private bool EnsurePathLoaded(Autodesk.Navisworks.Api.ModelItem item)
        {
            // 1. 构建祖先链
            var chain = new System.Collections.Generic.List<Autodesk.Navisworks.Api.ModelItem>();
            var current = item;
            while (current != null)
            {
                chain.Insert(0, current);
                current = current.Parent;
            }

            var chainStr = string.Join(" -> ", chain.ConvertAll(c => c.DisplayName ?? "?"));
            LogManager.Debug($"[别名树] EnsurePathLoaded: chain=[{chainStr}], depth={chain.Count - 1}");

            // 2. 匹配根节点
            var rootNodes = AliasTreeView.ItemsSource
                as System.Collections.ObjectModel.ObservableCollection<AliasNodeViewModel>;
            if (rootNodes == null) return false;

            AliasNodeViewModel treeNode = null;
            foreach (var rn in rootNodes)
            {
                if (rn.ModelItem != null && rn.ModelItem.Equals(chain[0]))
                {
                    treeNode = rn;
                    break;
                }
            }
            if (treeNode == null) return false;
            if (chain.Count == 1) return true;

            // 3. 逐层加载
            var aliasMap = _dataStore?.LoadAll();
            var displayAlias = BuildDisplayPathAliasMap(aliasMap);
            for (int i = 1; i < chain.Count; i++)
            {
                var targetModelItem = chain[i];
                ExpandTreeNode(treeNode);

                if (treeNode.Children.Count == 1 && treeNode.Children[0].OriginalName == "加载中...")
                    LazyLoadChildren(treeNode, aliasMap, displayAlias);

                AliasNodeViewModel child = null;
                foreach (var c in treeNode.Children)
                {
                    if (c.OriginalName != "加载中..." && c.ModelItem != null && c.ModelItem.Equals(targetModelItem))
                    {
                        child = c;
                        break;
                    }
                }

                if (child == null)
                {
                    int siblingIdx = NavisworksTransport.Core.AliasTree.AliasNodeIdentity.ComputeSiblingIndex(targetModelItem);
                    child = BuildNode(targetModelItem, aliasMap, displayAlias,
                        treeNode.Depth + 1, treeNode.NodeKey, siblingIdx);
                    if (child != null)
                    {
                        child.Parent = treeNode;
                        treeNode.Children.Add(child);
                    }
                }

                if (child == null)
                {
                    LogManager.Debug($"[别名树] EnsurePathLoaded: child null at L{i}");
                    return false;
                }
                treeNode = child;
            }

            return true;
        }

        /// <summary>
        /// 从 TreeView 根开始，沿 parent 链找 ViewModel 对应的 TreeViewItem。
        /// 虚拟化下可能返回 null。
        /// </summary>
        private TreeViewItem GetTreeViewItemForNode(AliasNodeViewModel node)
        {
            if (node == null) return null;

            // 收集从根到目标的路径
            var path = new List<AliasNodeViewModel>();
            var current = node;
            while (current != null)
            {
                path.Insert(0, current);
                current = current.Parent;
            }

            // 从 TreeView 根开始找
            ItemsControl container = AliasTreeView;
            foreach (var item in path)
            {
                if (item == null) break;
                AliasTreeView.UpdateLayout();
                container.UpdateLayout();
                var tvi = container.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
                if (tvi == null) return null;
                container = tvi;
            }

            return container as TreeViewItem;
        }

        // ============================================================
        // 事件处理：工具栏
        // ============================================================

        private void OnAliasInputKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                BeforeSaveAlias?.Invoke();
                if (_currentSelectionItem == null || _dataStore == null) return;
                string input = TxtAliasInput.Text?.Trim();
                if (string.IsNullOrEmpty(input)) return;
                if (input == _currentSelectionItem?.DisplayName)
                    input = null;

                var identity = AliasNodeIdentity.FromModelItem(_currentSelectionItem);
                LogManager.Info($"[别名树] 保存别名: {input}, NodeKey={identity.ToKey()}");
                _dataStore.SetAlias(identity, input);
                TxtAliasInput.Clear();
                RefreshNodeDisplay(identity.ToKey());
            }
        }

        private void OnAutoGenClick(object sender, RoutedEventArgs e)
        {
            // 阶段 3 实现
            SetStatus("自动生成尚未实现");
        }

        private void OnExportClick(object sender, RoutedEventArgs e)
        {
            if (_dataStore == null) { SetStatus("数据库未连接"); return; }

            var dlg = new Microsoft.Win32.SaveFileDialog
            {
                Filter = "JSON 文件|*.alias.json",
                Title = "导出别名映射"
            };
            if (dlg.ShowDialog() != true) return;

            try
            {
                var allAliases = _dataStore.LoadAll();
                var list = new List<object>();
                foreach (var kvp in allAliases)
                {
                    list.Add(new { key = kvp.Key, alias = kvp.Value });
                }

                var docName = NavisApplication.ActiveDocument?.FileName ?? "unknown";
                var data = new
                {
                    version = 1,
                    exportedAt = DateTime.Now.ToString("s"),
                    documentName = System.IO.Path.GetFileName(docName),
                    aliases = list
                };

                var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
                var json = serializer.Serialize(data);
                System.IO.File.WriteAllText(dlg.FileName, json, System.Text.Encoding.UTF8);
                SetStatus($"已导出 {list.Count} 条别名");
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] 导出失败: {ex.Message}");
            }
        }

        private void OnImportClick(object sender, RoutedEventArgs e)
        {
            if (_dataStore == null) { SetStatus("数据库未连接"); return; }

            var dlg = new Microsoft.Win32.OpenFileDialog
            {
                Filter = "JSON 文件|*.json",
                Title = "导入别名映射"
            };
            if (dlg.ShowDialog() != true) return;

            try
            {
                string json = System.IO.File.ReadAllText(dlg.FileName, System.Text.Encoding.UTF8);
                var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
                var data = serializer.Deserialize<Dictionary<string, object>>(json);

                var aliasesArr = data["aliases"] as System.Collections.ArrayList;
                if (aliasesArr == null) { SetStatus("无效的别名文件"); return; }

                var entries = new Dictionary<string, string>();
                foreach (Dictionary<string, object> item in aliasesArr)
                {
                    string key = item["key"] as string;
                    string alias = item["alias"] as string;
                    if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(alias))
                        entries[key] = alias;
                }

                _dataStore.ImportBatch(entries);
                RefreshAliasesFromStore();
                SetStatus($"已导入 {entries.Count} 条别名");
                LogManager.Info($"[别名树] 导入完成: {entries.Count} 条");
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] 导入失败: {ex.Message}");
            }
        }

        private void OnClearAllClick(object sender, RoutedEventArgs e)
        {
            if (_dataStore == null) return;

            var result = System.Windows.MessageBox.Show(
                "确定要清除全部别名吗？此操作不可撤销。",
                "清除全部别名",
                MessageBoxButton.YesNo,
                MessageBoxImage.Warning);

            if (result == MessageBoxResult.Yes)
            {
                _dataStore.ClearAll();
                RebuildTree();
                SetStatus("已清除全部别名");
            }
        }

        private void OnRefreshClick(object sender, RoutedEventArgs e)
        {
            _built = false;
            RebuildTree();
            SetStatus("树已刷新");
        }

        // ============================================================
        // 事件处理：内联编辑
        // ============================================================

        private void OnEditClick(object sender, RoutedEventArgs e)
        {
            if (!(sender is Button btn) || !(btn.Tag is AliasNodeViewModel node))
                return;

            // 清除所有编辑状态
            ClearAllEditing();
            node.IsEditing = true;

            Dispatcher.BeginInvoke(new Action(() =>
            {
                var container = AliasTreeView.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem;
                if (container == null) return;
                var txt = FindVisualChild<TextBox>(container);
                if (txt != null)
                {
                    txt.Focus();
                    txt.SelectAll();
                }
            }), System.Windows.Threading.DispatcherPriority.Render);
        }

        private void OnAliasTextBoxKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                CommitEdit(sender as TextBox);
            }
            else if (e.Key == Key.Escape)
            {
                CancelEdit(sender as TextBox);
            }
        }

        private void OnEditTextBoxVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (sender is TextBox tb && tb.IsVisible)
            {
                Dispatcher.BeginInvoke(new Action(() =>
                {
                    tb.Focus();
                    tb.SelectAll();
                }), System.Windows.Threading.DispatcherPriority.Loaded);
            }
        }

        private void OnAliasTextBoxLostFocus(object sender, RoutedEventArgs e)
        {
            CommitEdit(sender as TextBox);
        }

        private void CommitEdit(TextBox textBox)
        {
            if (textBox?.DataContext is AliasNodeViewModel node)
            {
                string newAlias = textBox.Text?.Trim();
                if (string.IsNullOrEmpty(newAlias) || newAlias == node.OriginalName)
                    newAlias = null;
                BeforeSaveAlias?.Invoke();
                if (_dataStore == null) return;

                LogManager.Info($"[别名树] 内联编辑: {newAlias}, NodeKey={node.NodeKey}");
                try
                {
                    EnsureFullNodeKey(node);
                    AliasNodeIdentity.TryParseKey(node.NodeKey, out var identity);
                    _dataStore.SetAlias(identity, newAlias);
                }
                catch (Exception ex)
                {
                    LogManager.Error($"[别名树] SetAlias 异常: {ex.Message}");
                    return;
                }
                node.Alias = newAlias;
                node.IsEditing = false;

                ApplyAliasHighlight(node.NodeKey, node.HasAlias);
            }
        }

        private void CancelEdit(TextBox textBox)
        {
            if (textBox?.DataContext is AliasNodeViewModel node)
            {
                node.RefreshDisplay();
                node.IsEditing = false;
            }
        }

        private void ClearAllEditing()
        {
            foreach (var kvp in _nodeMap)
            {
                kvp.Value.IsEditing = false;
            }
        }

        // ============================================================
        // 事件处理：右键菜单
        // ============================================================

        private AliasNodeViewModel GetSelectedNode()
        {
            return AliasTreeView.SelectedItem as AliasNodeViewModel;
        }

        private void OnCtxEditClick(object sender, RoutedEventArgs e)
        {
            var node = GetSelectedNode();
            LogManager.Info($"[别名树] OnCtxEditClick: node={node?.OriginalName ?? "null"}");
            if (node == null) return;

            // 从可视化树中查找 TreeViewItem，不依赖 ContainerFromItem
            var tvi = FindTreeViewItemByDataContext(AliasTreeView, node);
            LogManager.Info($"[别名树] OnCtxEditClick: tvi found={tvi != null}");
            if (tvi != null)
                StartInlineEdit(node, tvi);
        }

        private static TreeViewItem FindTreeViewItemByDataContext(ItemsControl parent, object dataContext)
        {
            // 先检查直接子项
            for (int i = 0; i < parent.Items.Count; i++)
            {
                var item = parent.Items[i];
                if (item == dataContext)
                {
                    return parent.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
                }
                if (item is AliasNodeViewModel vm)
                {
                    // 递归搜索子项（先获取容器，再搜它的子项）
                    var tvi = parent.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
                    if (tvi != null && tvi.Items.Count > 0)
                    {
                        var found = FindTreeViewItemByDataContext(tvi, dataContext);
                        if (found != null) return found;
                    }
                }
            }
            return null;
        }

        private void OnCtxBatchEditClick(object sender, RoutedEventArgs e)
        {
            var node = GetSelectedNode();
            if (node == null) return;

            // 阶段 3 实现批量对话框
            SetStatus($"批量编辑 {node.NodeKey} 的子节点 — 尚未实现");
        }

        private void OnCtxClearClick(object sender, RoutedEventArgs e)
        {
            var node = GetSelectedNode();
            if (node == null || _dataStore == null) return;

            EnsureFullNodeKey(node);
            AliasNodeIdentity.TryParseKey(node.NodeKey, out var identity);
            _dataStore.DeleteAlias(identity);
            node.Alias = null;
            ApplyAliasHighlight(node.NodeKey, false);
        }


        private void OnCtxCopyNameClick(object sender, RoutedEventArgs e)
        {
            var node = GetSelectedNode();
            if (node == null) return;
            try
            {
                Clipboard.SetText(node.OriginalName);
            }
            catch (Exception ex) { LogManager.Debug($"[别名树] Clipboard异常: {ex.Message}"); }
        }

        // ============================================================
        // 子树别名导出/导入 / 复制/粘贴
        // ============================================================

        /// <summary>
        /// 解析带 #N 后缀的段名 ("Room101#1" → "Room101", 1)
        /// </summary>
        private static void ParseSuffixedSegment(string segment, out string baseName, out int index)
        {
            int hashIdx = segment.LastIndexOf('#');
            if (hashIdx > 0 && int.TryParse(segment.Substring(hashIdx + 1), out index))
            {
                baseName = segment.Substring(0, hashIdx);
            }
            else
            {
                baseName = segment;
                index = 0;
            }
        }

        /// <summary>
        /// 收集源节点子树下所有别名，返回相对路径→别名 字典（不含源节点自身）
        /// </summary>
        private Dictionary<string, string> CollectSubtreeAliases(AliasNodeIdentity sourceIdentity)
        {
            var result = new Dictionary<string, string>();
            var allAliases = _dataStore.LoadAll();
            if (allAliases == null || allAliases.Count == 0) return result;

            string prefix = sourceIdentity.DisplayPath + "/";

            foreach (var kvp in allAliases)
            {
                if (!AliasNodeIdentity.TryParseKey(kvp.Key, out var id)) continue;

                if (id.DisplayPath.StartsWith(prefix, StringComparison.Ordinal))
                {
                    string relativePath = id.DisplayPath.Substring(prefix.Length);
                    result[relativePath] = kvp.Value;
                }
            }

            return result;
        }

        /// <summary>
        /// 将相对路径→别名 字典应用到目标节点子树。
        /// 逐段 Children 寻径，任一缺失则返回错误信息取消全部。
        /// </summary>
        private string TryApplySubtreeAliases(AliasNodeViewModel targetNode, Dictionary<string, string> relativeAliases)
        {
            if (relativeAliases == null || relativeAliases.Count == 0) return "没有可导入的别名数据";

            EnsureFullNodeKey(targetNode);
            if (!AliasNodeIdentity.TryParseKey(targetNode.NodeKey, out var targetIdentity))
                return "无法解析目标节点标识";

            var targetItem = FindModelItemByNodeKey(targetNode.NodeKey);
            if (targetItem == null) return "无法定位目标模型节点";

            var batch = new Dictionary<string, string>();

            foreach (var kvp in relativeAliases)
            {
                Autodesk.Navisworks.Api.ModelItem current = targetItem;
                string[] segments = kvp.Key.Split('/');
                bool found = true;

                foreach (var segment in segments)
                {
                    ParseSuffixedSegment(segment, out var baseName, out var index);
                    int matchCount = 0;
                    Autodesk.Navisworks.Api.ModelItem matched = null;

                    foreach (var child in current.Children)
                    {
                        if (string.Equals(child.DisplayName, baseName, StringComparison.Ordinal))
                        {
                            if (matchCount == index) { matched = child; break; }
                            matchCount++;
                        }
                    }

                    if (matched == null) { found = false; break; }
                    current = matched;
                }

                if (!found) return $"目标子树缺少路径：{kvp.Key}，已取消";

                var descIdentity = AliasNodeIdentity.FromModelItem(current);
                batch[descIdentity.ToKey()] = kvp.Value;
            }

            if (batch.Count > 0)
            {
                _dataStore.ImportBatch(batch);
                RefreshAliasesFromStore();
                SetStatus($"已导入 {batch.Count} 条别名到目标子树");
            }

            return null;
        }

        private void OnCtxExportSubtreeClick(object sender, RoutedEventArgs e)
        {
            var node = GetSelectedNode();
            if (node == null || _dataStore == null) return;

            EnsureFullNodeKey(node);
            if (!AliasNodeIdentity.TryParseKey(node.NodeKey, out var identity))
            { SetStatus("无法解析节点标识"); return; }

            var entries = CollectSubtreeAliases(identity);
            if (entries.Count == 0) { SetStatus("该节点下没有别名数据"); return; }

            var defaultName = string.Join("_", identity.DisplayPath.Split(System.IO.Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries)) + ".alias-subtree.json";
            var dlg = new Microsoft.Win32.SaveFileDialog
            {
                Filter = "JSON 文件|*.alias-subtree.json",
                Title = "导出子树别名",
                FileName = defaultName
            };
            if (dlg.ShowDialog() != true) return;

            try
            {
                var data = new
                {
                    version = 1,
                    type = "alias_subtree",
                    exportedAt = DateTime.Now.ToString("s"),
                    sourceDisplayPath = identity.DisplayPath,
                    aliases = entries
                };

                var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
                File.WriteAllText(dlg.FileName, serializer.Serialize(data), System.Text.Encoding.UTF8);
                SetStatus($"已导出 {entries.Count} 条子树别名");
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] 子树导出失败: {ex.Message}");
                SetStatus("导出失败，见日志");
            }
        }

        private void OnCtxImportSubtreeClick(object sender, RoutedEventArgs e)
        {
            var node = GetSelectedNode();
            if (node == null || _dataStore == null) return;

            var dlg = new Microsoft.Win32.OpenFileDialog
            {
                Filter = "JSON 文件|*.alias-subtree.json;*.json",
                Title = "导入子树别名"
            };
            if (dlg.ShowDialog() != true) return;

            try
            {
                var json = File.ReadAllText(dlg.FileName, System.Text.Encoding.UTF8);
                var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
                var data = serializer.Deserialize<Dictionary<string, object>>(json);

                if (!(data.TryGetValue("type", out var typeObj) && (typeObj as string) == "alias_subtree"))
                { SetStatus("无效的子树别名文件"); return; }

                if (!(data.TryGetValue("aliases", out var aliasesObj) && aliasesObj is Dictionary<string, object> rawAliases))
                { SetStatus("别名数据格式无效"); return; }

                var entries = new Dictionary<string, string>();
                foreach (var kvp in rawAliases)
                {
                    var alias = kvp.Value as string;
                    if (!string.IsNullOrEmpty(kvp.Key) && !string.IsNullOrEmpty(alias))
                        entries[kvp.Key] = alias;
                }

                if (entries.Count == 0) { SetStatus("文件不包含有效别名"); return; }

                var error = TryApplySubtreeAliases(node, entries);
                if (error != null) SetStatus(error);
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] 子树导入失败: {ex.Message}");
                SetStatus("导入失败，见日志");
            }
        }

        private void OnCtxCopyAliasTreeClick(object sender, RoutedEventArgs e)
        {
            var node = GetSelectedNode();
            if (node == null || _dataStore == null) return;

            EnsureFullNodeKey(node);
            if (!AliasNodeIdentity.TryParseKey(node.NodeKey, out var identity))
            { SetStatus("无法解析节点标识"); return; }

            var entries = CollectSubtreeAliases(identity);
            if (entries.Count == 0) { SetStatus("该节点下没有别名数据"); return; }

            var data = new
            {
                version = 1,
                type = "alias_subtree",
                exportedAt = DateTime.Now.ToString("s"),
                sourceDisplayPath = identity.DisplayPath,
                aliases = entries
            };

            var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
            _copiedAliasTreeJson = serializer.Serialize(data);
            SetStatus($"已复制 {entries.Count} 条别名树");
        }

        private void OnCtxPasteAliasTreeClick(object sender, RoutedEventArgs e)
        {
            var node = GetSelectedNode();
            if (node == null || _dataStore == null) return;

            if (string.IsNullOrEmpty(_copiedAliasTreeJson))
            { SetStatus("没有已复制的别名树数据"); return; }

            try
            {
                var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
                var data = serializer.Deserialize<Dictionary<string, object>>(_copiedAliasTreeJson);

                if (!(data.TryGetValue("type", out var typeObj) && (typeObj as string) == "alias_subtree"))
                { SetStatus("内存数据无效"); return; }

                if (!(data.TryGetValue("aliases", out var aliasesObj) && aliasesObj is Dictionary<string, object> rawAliases))
                { SetStatus("别名数据格式无效"); return; }

                var entries = new Dictionary<string, string>();
                foreach (var kvp in rawAliases)
                {
                    var alias = kvp.Value as string;
                    if (!string.IsNullOrEmpty(kvp.Key) && !string.IsNullOrEmpty(alias))
                        entries[kvp.Key] = alias;
                }

                if (entries.Count == 0) { SetStatus("别名数据为空"); return; }

                var error = TryApplySubtreeAliases(node, entries);
                if (error != null) SetStatus(error);
            }
            catch (Exception ex)
            {
                LogManager.Debug($"[别名树] 粘贴别名树异常: {ex.Message}");
                SetStatus("别名树数据格式无效");
            }
        }

        // ============================================================
        // 3D 高亮
        // ============================================================

        private void ApplyAliasHighlight(string nodeKey, bool highlight)
        {
            var item = FindModelItemByNodeKey(nodeKey);
            if (item == null) return;

            var coll = new Autodesk.Navisworks.Api.ModelItemCollection { item };

            if (highlight)
            {
                ModelHighlightHelper.HighlightItems("AliasHighlight", coll);
            }
            else
            {
                ModelHighlightHelper.ClearCategory("AliasHighlight");
            }
        }

        // ============================================================
        // 辅助
        // ============================================================

        private void RefreshNodeDisplay(string key)
        {
            int sepIdx = key.IndexOf("||", StringComparison.Ordinal);
            string lookupKey = sepIdx >= 0 ? key.Substring(sepIdx + 2) : key;
            if (_nodeMap.TryGetValue(lookupKey, out var node))
            {
                if (_dataStore != null)
                {
                    AliasNodeIdentity.TryParseKey(key, out var identity);
                    node.Alias = _dataStore.GetAlias(identity);
                }
                node.RefreshDisplay();
            }
        }

        private void SetStatus(string message)
        {
            TbStatus.Text = message;
            StatusBarItem.Visibility = Visibility.Visible;
            // 3 秒后自动隐藏
            var timer = new System.Windows.Threading.DispatcherTimer
            {
                Interval = TimeSpan.FromSeconds(3)
            };
            timer.Tick += (s, e2) =>
            {
                timer.Stop();
                StatusBarItem.Visibility = Visibility.Collapsed;
            };
            timer.Start();
        }

        private static T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
            {
                var child = VisualTreeHelper.GetChild(parent, i);
                if (child is T t) return t;
                var result = FindVisualChild<T>(child);
                if (result != null) return result;
            }
            return null;
        }

        // ============================================================
        // TreeView 子节点懒加载
        // ============================================================

        private void OnTreeViewItemExpanded(object sender, RoutedEventArgs e)
        {
            if (_isProgrammaticExpand) return;
            if (!(e.OriginalSource is TreeViewItem tvi) || !(tvi.DataContext is AliasNodeViewModel node))
                return;
            var aliasMap = _dataStore?.LoadAll();
            var displayAlias = BuildDisplayPathAliasMap(aliasMap);
            LazyLoadChildren(node, aliasMap, displayAlias);
        }

        private bool _isProgrammaticSelect;
        private bool _isProgrammaticExpand;

        /// <summary>
        /// 树节点被选中时：如果 500ms 内再次选中同一节点 → 进入内联编辑
        /// </summary>
        /// <summary>
        /// 右键前先选中节点，确保右键菜单操作的是正确节点
        /// </summary>
        private void OnTreeViewItemRightClick(object sender, MouseButtonEventArgs e)
        {
            try
            {
                var tvi = FindAncestor(e.OriginalSource as DependencyObject);
                if (tvi != null)
                {
                    tvi.IsSelected = true;
                    tvi.Focus();
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] RCK-异常: {ex.GetType().Name}");
            }
        }

        private static TreeViewItem FindAncestor(DependencyObject current)
        {
            try
            {
                while (current != null)
                {
                    if (current is TreeViewItem tvi) return tvi;
                    current = VisualTreeHelper.GetParent(current);
                }
            }
            catch (Exception ex) { LogManager.Warning($"[别名树] FindAncestor异常: {ex.GetType().Name}"); }
            return null;
        }

        /// <summary>
        /// 左键点击已选中节点 → 进入内联编辑
        /// </summary>
        private void OnTreeViewItemLeftClick(object sender, MouseButtonEventArgs e)
        {
            try
            {
                // 从点击元素的 DataContext 获取节点，不遍历可视化树
                if (e.OriginalSource is FrameworkElement fe && fe.DataContext is AliasNodeViewModel node && !node.IsOrphan)
                {
                    var selNode = GetSelectedNode();
                    if (node == selNode)
                        StartInlineEdit(node);
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"[别名树] LCK-异常: {ex.GetType().Name}");
            }
        }

        private void OnTreeViewItemSelected(object sender, RoutedEventArgs e)
        {
            if (e.OriginalSource is TreeViewItem tvi && tvi.DataContext is AliasNodeViewModel node && !node.IsOrphan)
            {
                if (!_isProgrammaticSelect)
                    OnTreeViewSelected(node);
            }
        }

        /// <summary>
        /// 选中状态下点击 → 进入内联编辑（模仿 DataGrid 行为）
        /// </summary>
        /// <summary>
        /// F2 键 → 进入内联编辑
        /// </summary>
        private void UserControl_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.F2)
            {
                EnterEditSelectedNode();
                e.Handled = true;
            }
        }

        /// <summary>
        /// 双击 → 进入内联编辑
        /// </summary>
        private void OnTreeViewDoubleClick(object sender, MouseButtonEventArgs e)
        {
            EnterEditSelectedNode();
        }

        private void EnterEditSelectedNode()
        {
            var node = GetSelectedNode();
            if (node != null)
            {
                var tvi = AliasTreeView.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem;
                if (tvi != null) StartInlineEdit(node, tvi);
            }
        }
    }
}
