import os

from imgui_bundle import imgui
from direct.showbase.ShowBaseGlobal import globalClock
from direct.task.TaskManagerGlobal import taskMgr
from panda3d.core import WindowProperties


class AppActions:
    """Project, input, messaging, edit actions, and import workflows."""

    def __init__(self, app):
        self.app = app

    def __getattr__(self, name):
        return getattr(self.app, name)

    def __setattr__(self, name, value):
        if name == "app" or name in self.__dict__ or hasattr(type(self), name):
            object.__setattr__(self, name, value)
        else:
            setattr(self.app, name, value)

    def _resolve_cut_copy_node(self, node):
        """Resolve selection to a stable scene root for copy/cut/paste."""
        if not node or node.isEmpty():
            return node

        current = node
        while current and not current.isEmpty():
            if current.getName() == "render":
                break
            if current.hasTag("tree_item_type") or current.hasTag("is_model_root") or current.hasTag("is_scene_element"):
                return current
            current = current.getParent()

        return node


    def _toggle_hot_reload(self):
        """切换热重载状态"""
        if hasattr(self, 'script_manager') and self.script_manager:
            try:
                current_state = getattr(self.script_manager, 'hot_reload_enabled', False)
                self.script_manager.hot_reload_enabled = not current_state
                
                new_state = "启用" if not current_state else "禁用"
                self.add_success_message(f"热重载已{new_state}")
                print(f"[脚本系统] 热重载已{new_state}")
            except Exception as e:
                self.add_error_message(f"切换热重载失败: {str(e)}")
                print(f"[脚本系统] 切换热重载失败: {e}")
    

    def _create_new_script(self):
        """创建新脚本"""
        if not hasattr(self, '_new_script_name') or not self._new_script_name.strip():
            self.add_error_message("请输入脚本名称")
            return
        
        script_name = self._new_script_name.strip()
        if not script_name.endswith('.py'):
            script_name += '.py'
        
        # 确定模板类型
        template_map = {
            0: "basic",
            1: "movement", 
            2: "rotation",
            3: "scale",
            4: "animation"
        }
        template_type = template_map.get(getattr(self, '_selected_template', 0), "basic")
        
        try:
            if hasattr(self, 'script_manager') and self.script_manager:
                result = self.script_manager.create_script_file(script_name, template_type)
                if result:
                    self.add_success_message(f"脚本 {script_name} 创建成功")
                    print(f"[脚本系统] 创建脚本成功: {script_name}")
                    # 刷新脚本列表
                    self._refresh_scripts_list()
                else:
                    self.add_error_message(f"脚本 {script_name} 创建失败")
            else:
                self.add_error_message("脚本管理器未初始化")
        except Exception as e:
            self.add_error_message(f"创建脚本失败: {str(e)}")
            print(f"[脚本系统] 创建脚本失败: {e}")
    

    def _refresh_scripts_list(self):
        """刷新脚本列表"""
        try:
            if hasattr(self, 'script_manager') and self.script_manager:
                # 这里可以添加缓存逻辑，避免频繁刷新
                available_scripts = self.script_manager.get_available_scripts()
                print(f"[脚本系统] 刷新脚本列表: {len(available_scripts)} 个脚本")
                self.add_success_message(f"脚本列表已刷新，共 {len(available_scripts)} 个脚本")
            else:
                self.add_error_message("脚本管理器未初始化")
        except Exception as e:
            self.add_error_message(f"刷新脚本列表失败: {str(e)}")
            print(f"[脚本系统] 刷新脚本列表失败: {e}")
    

    def _reload_all_scripts(self):
        """重载所有脚本"""
        try:
            if hasattr(self, 'script_manager') and self.script_manager:
                # 获取所有可用脚本并逐个重载
                available_scripts = self.script_manager.get_available_scripts()
                success_count = 0
                
                for script_name in available_scripts:
                    if self.script_manager.reload_script(script_name):
                        success_count += 1
                
                self.add_success_message(f"重载完成: {success_count}/{len(available_scripts)} 个脚本成功")
                print(f"[脚本系统] 重载脚本: {success_count}/{len(available_scripts)} 成功")
            else:
                self.add_error_message("脚本管理器未初始化")
        except Exception as e:
            self.add_error_message(f"重载脚本失败: {str(e)}")
            print(f"[脚本系统] 重载脚本失败: {e}")
    

    def _on_script_selected(self, script_name):
        """处理脚本选择事件"""
        print(f"[脚本系统] 选择脚本: {script_name}")
        self.add_info_message(f"已选择脚本: {script_name}")
    

    def _edit_script(self, script_name):
        """编辑脚本"""
        try:
            if hasattr(self, 'script_manager') and self.script_manager:
                # 获取脚本信息
                script_info = self.script_manager.get_script_info(script_name)
                if script_info and script_info.get("file"):
                    script_path = script_info["file"]
                    
                    # 打开系统默认编辑器
                    import subprocess
                    import platform
                    
                    system = platform.system()
                    try:
                        if system == "Windows":
                            subprocess.run(['notepad', script_path])
                        elif system == "Darwin":  # macOS
                            subprocess.run(['open', script_path])
                        else:  # Linux
                            subprocess.run(['xdg-open', script_path])
                        
                        self.add_success_message(f"已打开脚本编辑器: {script_name}")
                        print(f"[脚本系统] 编辑脚本: {script_path}")
                    except Exception as e:
                        self.add_error_message(f"打开编辑器失败: {str(e)}")
                else:
                    self.add_error_message(f"找不到脚本文件: {script_name}")
            else:
                self.add_error_message("脚本管理器未初始化")
        except Exception as e:
            self.add_error_message(f"编辑脚本失败: {str(e)}")
            print(f"[脚本系统] 编辑脚本失败: {e}")
    

    def _mount_script_to_selected(self, script_name):
        """挂载脚本到选中对象"""
        selected_node = None
        if hasattr(self, 'selection') and self.selection and hasattr(self.selection, 'selectedNode'):
            selected_node = self.selection.selectedNode
        
        if not selected_node or selected_node.isEmpty():
            self.add_error_message("请先选择一个对象")
            return
        
        try:
            if hasattr(self, 'script_manager') and self.script_manager:
                script_component = self.script_manager.add_script_to_object(selected_node, script_name)
                if script_component:
                    self.add_success_message(f"脚本 {script_name} 已挂载到 {selected_node.getName()}")
                    print(f"[脚本系统] 挂载脚本: {script_name} -> {selected_node.getName()}")
                else:
                    self.add_error_message(f"挂载脚本 {script_name} 失败")
            else:
                self.add_error_message("脚本管理器未初始化")
        except Exception as e:
            self.add_error_message(f"挂载脚本失败: {str(e)}")
            print(f"[脚本系统] 挂载脚本失败: {e}")
    

    def _unmount_script_from_selected(self, script_name):
        """从选中对象卸载脚本"""
        selected_node = None
        if hasattr(self, 'selection') and self.selection and hasattr(self.selection, 'selectedNode'):
            selected_node = self.selection.selectedNode
        
        if not selected_node or selected_node.isEmpty():
            self.add_error_message("请先选择一个对象")
            return
        
        try:
            if hasattr(self, 'script_manager') and self.script_manager:
                result = self.script_manager.remove_script_from_object(selected_node, script_name)
                if result:
                    self.add_success_message(f"脚本 {script_name} 已从 {selected_node.getName()} 卸载")
                    print(f"[脚本系统] 卸载脚本: {script_name} <- {selected_node.getName()}")
                else:
                    self.add_error_message(f"卸载脚本 {script_name} 失败")
            else:
                self.add_error_message("脚本管理器未初始化")
        except Exception as e:
            self.add_error_message(f"卸载脚本失败: {str(e)}")
            print(f"[脚本系统] 卸载脚本失败: {e}")
    
    # ==================== 菜单处理函数 ====================
    

    def _on_new_project(self):
        """处理新建项目菜单项"""
        self.add_info_message("打开新建项目对话框")
        self.show_new_project_dialog = True
    

    def _on_open_project(self):
        """处理打开项目菜单项"""
        self.add_info_message("打开项目对话框")
        self.show_open_project_dialog = True
    

    def _on_save_project(self):
        """处理保存项目菜单项"""
        if hasattr(self, 'project_manager') and self.project_manager:
            try:
                # 检查是否有当前项目路径
                if not self.project_manager.current_project_path:
                    self.add_warning_message("没有当前项目路径，请先创建或打开项目")
                    self.show_save_as_dialog = True
                    return
                
                # 直接调用保存逻辑，避免Qt依赖
                if self._save_project_impl():
                    self.add_success_message("项目保存成功")
                else:
                    self.add_error_message("项目保存失败")
            except Exception as e:
                self.add_error_message(f"项目保存失败: {e}")
        else:
            self.add_error_message("项目管理器未初始化")
    

    def _on_save_as_project(self):
        """处理另存为项目菜单项"""
        self.add_info_message("另存为项目（功能待实现）")
        # TODO: 实现另存为对话框
        # self.show_save_as_dialog = True
    

    def _on_exit(self):
        """处理退出菜单项"""
        self.add_info_message("退出应用程序")
        self.userExit()
    
    # ==================== 键盘事件处理函数 ====================
    

    def _on_ctrl_pressed(self):
        """Ctrl键按下"""
        self.ctrl_pressed = True
    

    def _on_ctrl_released(self):
        """Ctrl键释放"""
        self.ctrl_pressed = False
    

    def _on_alt_pressed(self):
        """Alt键按下"""
        self.alt_pressed = True
    

    def _on_alt_released(self):
        """Alt键释放"""
        self.alt_pressed = False
    

    def _on_n_pressed(self):
        """N键按下 - 检查Ctrl+N组合键"""
        if self.ctrl_pressed:
            self._on_new_project()
    

    def _on_o_pressed(self):
        """O键按下 - 检查Ctrl+O组合键"""
        if self.ctrl_pressed:
            self._on_open_project()
    
    
    

    def _on_f4_pressed(self):
        """F4键按下 - 检查Alt+F4组合键"""
        if self.alt_pressed:
            self._on_exit()
    
    # 移除了单独的按键处理方法，现在直接使用组合键事件
    

    def _on_delete_pressed(self):
        """Delete键按下 - 删除选中节点"""
        self._on_delete()

    def _on_escape_pressed(self):
        """Escape键按下 - 取消所有选区"""
        # 1. 取消 LUI 组件选择
        if hasattr(self, 'lui_manager'):
            if self.lui_manager.selected_index >= 0:
                self.lui_manager.selected_index = -1
                print("✓ 已取消LUI组件选中")
                
        # 2. 取消 3D 场景选择
        if hasattr(self, 'selection') and self.selection:
            if self.selection.selectedNode:
                self.selection.selectedNode = None
                self.selection.clearSelectionBox()
                self.selection.clearGizmo()
                print("✓ 已取消场景节点选中")
    

    def _on_wheel_up(self):
        """滚轮向上滚动 - 相机前进"""
        try:
            if not self.camera_control_enabled:
                return
                
            # 检查鼠标是否在ImGui窗口上
            if self._is_mouse_over_imgui():
                return
                
            # 沿相机前向向量移动
            forward = self.camera.getMat().getRow3(1)
            distance = 20.0 * globalClock.getDt()
            currentPos = self.camera.getPos()
            newPos = currentPos + forward * distance
            self.camera.setPos(newPos)
        except Exception as e:
            print(f"滚轮前进失败: {e}")
    

    def _on_wheel_down(self):
        """滚轮向下滚动 - 相机后退"""
        try:
            # 检查鼠标是否在ImGui窗口上
            if self._is_mouse_over_imgui():
                return
                
            # 沿相机前向向量移动
            forward = self.camera.getMat().getRow3(1)
            distance = -20.0 * globalClock.getDt()
            currentPos = self.camera.getPos()
            newPos = currentPos + forward * distance
            self.camera.setPos(newPos)
        except Exception as e:
            print(f"滚轮后退失败: {e}")
    

    def _is_mouse_over_imgui(self):
        """检测鼠标是否在ImGui窗口上"""
        try:
            # 检查是否有任何ImGui窗口想要捕获鼠标
            if hasattr(imgui, 'get_io') and imgui.get_io().want_capture_mouse:
                return True
            
            # 检查鼠标是否在任何ImGui窗口内
            mouse_pos = self.mouseWatcherNode.getMouse()
            if not mouse_pos:
                return False
                
            # 简单的边界检查（可以根据需要扩展）
            display_size = imgui.get_io().display_size
            mouse_x = mouse_pos.get_x() * display_size.x / 2 + display_size.x / 2
            mouse_y = display_size.y - (mouse_pos.get_y() * display_size.y / 2 + display_size.y / 2)
            
            # 检查是否在常见的ImGui界面区域内
            # 这里可以根据实际的ImGui窗口位置进行更精确的检测
            if mouse_x < 300 and mouse_y < 200:  # 左上角区域（菜单栏）
                return True
            if mouse_x < 300 and mouse_y > display_size.y - 200:  # 左下角区域（工具栏）
                return True
            if mouse_x > display_size.x - 300 and mouse_y < 200:  # 右上角区域
                return True
                
            return False
        except Exception as e:
            print(f"ImGui界面检测失败: {e}")
            return False
    

    def processImGuiMouseClick(self, x, y):
        """处理ImGui鼠标点击事件，返回是否消费了该事件"""
        try:
            # ImGui优先策略：如果ImGui想要捕获鼠标，则由ImGui处理
            if hasattr(imgui, 'get_io') and imgui.get_io().want_capture_mouse:
                return True
            
            # 检查是否有任何ImGui窗口悬停
            try:
                if imgui.is_any_window_hovered():
                    return True
            except AttributeError:
                # 如果方法不存在，跳过这个检查
                pass
            
            # 检查鼠标是否在ImGui界面区域内
            if self._is_mouse_over_imgui():
                return True
            
            # 如果以上条件都不满足，则让3D场景处理该事件
            return False
            
        except Exception as e:
            print(f"ImGui鼠标点击处理失败: {e}")
            return False
    
    # ==================== 消息系统 ====================
    

    def add_message(self, text, color=(1.0, 1.0, 1.0, 1.0)):
        """添加消息到消息列表，同时输出到终端"""
        import datetime
        timestamp = datetime.datetime.now().strftime("%H:%M:%S")
        
        # 输出到终端
        print(f"[{timestamp}] {text}")
        
        # 添加到GUI消息列表
        self.messages.append({
            'text': text,
            'color': color,
            'timestamp': timestamp
        })
        
        # 限制消息数量
        if len(self.messages) > self.max_messages:
            self.messages = self.messages[-self.max_messages:]
    

    def add_success_message(self, text):
        """添加成功消息"""
        self.add_message(f"✓ {text}", (0.176, 1.0, 0.769, 1.0))
    

    def add_error_message(self, text):
        """添加错误消息"""
        self.add_message(f"✗ {text}", (1.0, 0.3, 0.3, 1.0))
    

    def add_warning_message(self, text):
        """添加警告消息"""
        self.add_message(f"⚠ {text}", (0.953, 0.616, 0.471, 1.0))
    

    def add_info_message(self, text):
        """添加信息消息"""
        self.add_message(f"ℹ {text}", (0.157, 0.620, 1.0, 1.0))
    
    # ==================== 编辑菜单功能实现 ====================
    

    def _on_undo(self):
        """处理撤销操作"""
        try:
            # 1) 优先使用命令系统（删除/创建等命令）
            if hasattr(self, 'command_manager') and self.command_manager and self.command_manager.can_undo():
                success = self.command_manager.undo()
                if success:
                    self.add_success_message("撤销操作成功")
                    return
                self.add_error_message("撤销操作失败")
                return

            # 2) 回退到 TransformGizmo 历史（拖拽位移/旋转/缩放）
            tg = getattr(self, 'newTransform', None)
            if tg and hasattr(tg, 'undo_last') and tg.undo_last():
                self.add_success_message("撤销操作成功")
                return

            self.add_warning_message("没有可撤销的操作")
        except Exception as e:
            self.add_error_message(f"撤销操作失败: {e}")
    

    def _on_redo(self):
        """处理重做操作"""
        try:
            # 1) 优先使用命令系统
            if hasattr(self, 'command_manager') and self.command_manager and self.command_manager.can_redo():
                success = self.command_manager.redo()
                if success:
                    self.add_success_message("重做操作成功")
                    return
                self.add_error_message("重做操作失败")
                return

            # 2) 回退到 TransformGizmo 重做栈
            tg = getattr(self, 'newTransform', None)
            if tg and hasattr(tg, 'redo_last') and tg.redo_last():
                self.add_success_message("重做操作成功")
                return

            self.add_warning_message("没有可重做的操作")
        except Exception as e:
            self.add_error_message(f"重做操作失败: {e}")
    

    def _on_copy(self):
        """处理复制操作"""
        try:
            if not hasattr(self, 'selection') or not self.selection:
                self.add_error_message("选择系统未初始化")
                return
            
            # 获取当前选中的节点
            selected_node = self._resolve_cut_copy_node(self.selection.selectedNode)
            if not selected_node:
                self.add_warning_message("没有选中的节点")
                return
            
            # 检查节点有效性（不能复制根节点）
            if selected_node.getName() == "render":
                self.add_warning_message("不能复制根节点")
                return
            
            # 序列化节点
            if hasattr(self, 'scene_manager') and self.scene_manager:
                node_data = self.scene_manager.serializeNodeForCopy(selected_node)
                if node_data:
                    self.clipboard = [node_data]
                    # Keep live source nodes for robust copy->paste clone path.
                    self.clipboard_source_nodes = [selected_node]
                    self.clipboard_mode = "copy"
                    self.add_success_message(f"已复制节点: {selected_node.getName()}")
                else:
                    self.add_error_message("节点序列化失败")
            else:
                self.add_error_message("场景管理器未初始化")
        except Exception as e:
            self.add_error_message(f"复制操作失败: {e}")
    

    def _on_cut(self):
        """处理剪切操作"""
        try:
            if not hasattr(self, 'selection') or not self.selection:
                self.add_error_message("选择系统未初始化")
                return
            
            # 获取当前选中的节点
            selected_node = self._resolve_cut_copy_node(self.selection.selectedNode)
            if not selected_node:
                self.add_warning_message("没有选中的节点")
                return
            
            # 检查节点有效性（不能剪切根节点和系统节点）
            node_name = selected_node.getName()
            if node_name == "render":
                self.add_warning_message("不能剪切根节点")
                return
            
            # 序列化节点
            if hasattr(self, 'scene_manager') and self.scene_manager:
                node_data = self.scene_manager.serializeNodeForCopy(selected_node)
                if node_data:
                    self.clipboard = [node_data]
                    # Cut preserves the source node references for cloning in paste
                    self.clipboard_source_nodes = [selected_node]
                    self.clipboard_mode = "cut"
                    
                    # 删除原节点
                    if self._delete_node(selected_node):
                        self.selection.clearSelection()
                        self.add_success_message(f"已剪切节点: {node_name}")
                    else:
                        self.add_error_message(f"剪切失败，无法删除节点: {node_name}")
                else:
                    self.add_error_message("节点序列化失败")
            else:
                self.add_error_message("场景管理器未初始化")
        except Exception as e:
            self.add_error_message(f"剪切操作失败: {e}")
    

    def _on_paste(self):
        """处理粘贴操作"""
        try:
            if not self.clipboard:
                self.add_warning_message("剪切板为空")
                return
            
            if not hasattr(self, 'scene_manager') or not self.scene_manager:
                self.add_error_message("场景管理器未初始化")
                return
            
            # 确定粘贴目标父节点
            parent_node = None
            if hasattr(self, 'selection') and self.selection:
                selected_node = self.selection.selectedNode
                if selected_node and not selected_node.isEmpty():
                    # Paste as sibling by default (not as child of selected node),
                    # which matches editor expectations and avoids "pasted but invisible".
                    if selected_node.getName() == "render":
                        parent_node = selected_node
                    else:
                        p = selected_node.getParent()
                        parent_node = p if p and not p.isEmpty() else self.render
            
            # 如果没有选中节点，使用渲染根节点
            if not parent_node:
                parent_node = self.render
            
            # 反序列化并添加节点
            created_any = False
            source_nodes = getattr(self, "clipboard_source_nodes", []) or []
            for i, node_data in enumerate(self.clipboard):
                def _paste_create_node(parent):
                    created = None

                    # Copy mode: prefer direct NodePath clone to preserve visual geometry.
                    if self.clipboard_mode in ("copy", "cut") and i < len(source_nodes):
                        source_node = source_nodes[i]
                        if source_node and not source_node.isEmpty():
                            try:
                                if self.clipboard_mode == "cut":
                                    source_node.reparentTo(parent)
                                    created = source_node
                                else:
                                    created = source_node.copyTo(parent)
                                    if hasattr(self.scene_manager, "_generateUniqueName"):
                                        unique_name = self.scene_manager._generateUniqueName(source_node.getName(), parent)
                                        created.setName(unique_name)
                                    # Preserve model source tags so later cut/paste can rebuild real model.
                                    for _tag in ("model_path", "saved_model_path", "original_path", "file", "element_type"):
                                        if source_node.hasTag(_tag):
                                            created.setTag(_tag, source_node.getTag(_tag))
                                    # Offset slightly so the new node can be seen immediately.
                                    created.setPos(created.getX() + 0.2, created.getY() + 0.2, created.getZ())
                            except Exception:
                                created = None

                    # Fallback: recreate from serialized data.
                    if not created:
                        if hasattr(self.scene_manager, "recreateNodeFromData"):
                            created = self.scene_manager.recreateNodeFromData(node_data, parent)
                        else:
                            created = self.scene_manager.deserializeNode(node_data, parent)
                    return created

                new_node = None
                if hasattr(self, "command_manager") and self.command_manager:
                    try:
                        from core.Command_System import CreateNodeCommand
                        create_cmd = CreateNodeCommand(_paste_create_node, parent_node)
                        self.command_manager.execute_command(create_cmd)
                        new_node = create_cmd.created_node
                    except Exception as e:
                        print(f"[Paste] command create failed, fallback direct create: {e}")
                        new_node = _paste_create_node(parent_node)
                else:
                    new_node = _paste_create_node(parent_node)

                if new_node:
                    created_any = True
                    # Ensure pasted model can be picked by legacy collision fallback.
                    try:
                        if hasattr(self, "scene_manager") and self.scene_manager:
                            if not new_node.hasTag("tree_item_type"):
                                new_node.setTag("tree_item_type", "IMPORTED_MODEL_NODE")
                            new_node.setTag("is_model_root", "1")
                            new_node.setTag("is_scene_element", "1")
                            self.scene_manager.setupCollision(new_node)
                            if hasattr(self.scene_manager, "models") and new_node not in self.scene_manager.models:
                                self.scene_manager.models.append(new_node)
                    except Exception as _e:
                        print(f"[Paste] setup collision for pasted node failed: {_e}")
                    self.add_success_message(f"已粘贴节点: {new_node.getName()}")
                else:
                    self.add_error_message("节点反序列化失败")

            # 如果是剪切模式且粘贴成功，清空剪切板
            if created_any and self.clipboard_mode == "cut":
                self.clipboard = []
                self.clipboard_source_nodes = []
                self.clipboard_mode = ""
        except Exception as e:
            self.add_error_message(f"粘贴操作失败: {e}")
    

    def _on_delete(self):
        """处理删除操作"""
        try:
            if not hasattr(self, 'selection') or not self.selection:
                self.add_error_message("选择系统未初始化")
                return
            
            # 获取当前选中的节点
            selected_node = self.selection.selectedNode
            if not selected_node:
                self.add_warning_message("没有选中的节点")
                return
            
            # 检查节点有效性（不能删除根节点）
            node_name = selected_node.getName()
            if node_name == "render":
                self.add_warning_message("不能删除根节点")
                return
            
            # 删除节点
            if hasattr(self, 'scene_manager') and self.scene_manager:
                self._delete_node(selected_node)
                self.selection.clearSelection()
                self.add_success_message(f"已删除节点: {node_name}")
            else:
                self.add_error_message("场景管理器未初始化")
        except Exception as e:
            self.add_error_message(f"删除操作失败: {e}")
    

    def _delete_node(self, node):
        """删除节点的通用方法 - 使用命令系统"""
        try:
            if not node or node.isEmpty():
                return False
            
            node_name = node.getName() or "未命名节点"
            parent = node.getParent()
            
            # 创建删除命令
            if hasattr(self, 'command_manager') and self.command_manager:
                from core.Command_System import DeleteNodeCommand
                command = DeleteNodeCommand(node, parent, self)
                self.command_manager.execute_command(command)
                print(f"[命令系统] 创建删除命令: {node_name}")
            else:
                # 备用方案：直接删除并执行清理
                print(f"[删除] 命令管理器不可用，直接删除节点: {node_name}")
                self._perform_node_cleanup(node)
                node.removeNode()
            
            print(f"[删除] 成功删除节点: {node_name}")
            return True
            
        except Exception as e:
            print(f"[删除] 删除节点失败: {e}")
            return False
    

    def _perform_node_cleanup(self, node):
        """执行节点清理逻辑"""
        try:
            node_name = node.getName() or "未命名节点"
            
            # 从场景管理器的模型列表中移除（如果是模型）
            if hasattr(self, 'scene_manager') and self.scene_manager:
                if node in self.scene_manager.models:
                    self.scene_manager.models.remove(node)
                    print(f"[场景管理器] 从模型列表移除: {node_name}")
            
            # 停止所有与该节点相关的脚本
            if hasattr(self, 'script_manager') and self.script_manager:
                try:
                    # 移除该节点上的所有脚本
                    if node in self.script_manager.object_scripts:
                        del self.script_manager.object_scripts[node]
                        print(f"[脚本系统] 移除节点 {node_name} 的所有脚本")
                except Exception as e:
                    print(f"[脚本系统] 移除脚本失败: {e}")
            
            # 清理碰撞体
            if hasattr(self, 'collision_manager') and self.collision_manager:
                try:
                    self.collision_manager.remove_collision_for_node(node)
                    print(f"[碰撞系统] 移除节点 {node_name} 的碰撞体")
                except Exception as e:
                    print(f"[碰撞系统] 移除碰撞体失败: {e}")
            
            # 清理Actor缓存（如果有动画）
            if hasattr(self, '_actor_cache') and node in self._actor_cache:
                actor = self._actor_cache[node]
                try:
                    # 清理相关任务
                    taskMgr.remove(f"maintain_anim_pos_{id(actor)}")
                    # 清理Actor
                    if not actor.isEmpty():
                        actor.cleanup()
                        actor.removeNode()
                    print(f"[动画系统] 清理节点 {node_name} 的Actor缓存")
                except Exception as e:
                    print(f"[动画系统] 清理Actor缓存失败: {e}")
                finally:
                    del self._actor_cache[node]
                    
        except Exception as e:
            print(f"[清理] 节点清理失败: {e}")
    
    # ==================== 对话框绘制函数 ====================
    

    def _create_new_project(self, name, path):
        """创建新项目的实际实现"""
        if not hasattr(self, 'project_manager') or not self.project_manager:
            print("✗ 项目管理器未初始化")
            return
        
        try:
            if self._create_new_project_impl(name, path):
                print(f"✓ 项目创建成功: {name}")
            else:
                print(f"✗ 项目创建失败: {name}")
        except Exception as e:
            print(f"✗ 项目创建失败: {e}")
    

    def _open_project_path(self, path):
        """打开项目的实际实现"""
        if not hasattr(self, 'project_manager') or not self.project_manager:
            print("✗ 项目管理器未初始化")
            return
        
        try:
            print(f"打开项目: {path}")
            if self._open_project_impl(path):
                print(f"✓ 项目打开成功: {path}")
            else:
                print(f"✗ 项目打开失败: {path}")
        except Exception as e:
            print(f"✗ 项目打开失败: {e}")
    
    # ==================== 项目管理具体实现 ====================
    

    def _save_project_impl(self):
        """保存项目的具体实现（不依赖Qt）"""
        import json
        import datetime
        import os
        
        project_path = self.project_manager.current_project_path
        scenes_path = os.path.join(project_path, "scenes")
        
        # 固定的场景文件名
        scene_file = os.path.join(scenes_path, "scene.bam")
        
        # 如果存在旧文件，先删除
        if os.path.exists(scene_file):
            try:
                os.remove(scene_file)
                print(f"已删除旧场景文件: {scene_file}")
            except Exception as e:
                print(f"删除旧场景文件失败: {str(e)}")
                return False
        
        # 保存场景
        if self.scene_manager.saveScene(scene_file, project_path):
            # 更新项目配置文件
            config_file = os.path.join(project_path, "project.json")
            if os.path.exists(config_file):
                with open(config_file, "r", encoding="utf-8") as f:
                    project_config = json.load(f)
                
                # 更新最后修改时间
                project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                # 记录场景文件路径
                project_config["scene_file"] = os.path.relpath(scene_file, project_path)
                
                with open(config_file, "w", encoding="utf-8") as f:
                    json.dump(project_config, f, ensure_ascii=False, indent=4)
            
            # 更新项目配置
            self.project_manager.project_config = project_config
            return True
        return False
    

    def _open_project_impl(self, project_path):
        """打开项目的具体实现（不依赖Qt）"""
        import json
        import datetime
        import os
        
        try:
            # 检查项目管理器是否已初始化
            if not hasattr(self, 'project_manager') or not self.project_manager:
                print("✗ 项目管理器未初始化")
                self.add_error_message("项目管理器未初始化")
                return False
            
            # 检查场景管理器是否已初始化
            if not hasattr(self, 'scene_manager') or not self.scene_manager:
                print("✗ 场景管理器未初始化")
                self.add_error_message("场景管理器未初始化")
                return False
            
            # 检查是否是有效的项目文件夹
            config_file = os.path.join(project_path, "project.json")
            if not os.path.exists(config_file):
                print(f"⚠ 选择的不是有效的项目文件夹: {project_path}")
                self.add_warning_message(f"选择的不是有效的项目文件夹: {project_path}")
                return False

            # 读取项目配置
            try:
                with open(config_file, "r", encoding="utf-8") as f:
                    project_config = json.load(f)
            except Exception as e:
                print(f"✗ 读取项目配置文件失败: {e}")
                self.add_error_message(f"读取项目配置文件失败: {e}")
                return False

            # 检查场景文件
            scene_file = os.path.join(project_path, "scenes", "scene.bam")
            if os.path.exists(scene_file):
                # 加载场景
                try:
                    if self.scene_manager.loadScene(scene_file):
                        # 更新项目配置
                        project_config["scene_file"] = os.path.relpath(scene_file, project_path)
                        print(f"✓ 场景加载成功: {scene_file}")
                    else:
                        print(f"⚠ 场景加载失败: {scene_file}")
                        self.add_warning_message(f"场景加载失败: {scene_file}")
                except Exception as e:
                    print(f"✗ 加载场景时发生错误: {e}")
                    self.add_error_message(f"加载场景时发生错误: {e}")
                    # 继续执行，不阻止项目打开

            # 更新项目配置
            project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            try:
                with open(config_file, "w", encoding="utf-8") as f:
                    json.dump(project_config, f, ensure_ascii=False, indent=4)
            except Exception as e:
                print(f"✗ 保存项目配置失败: {e}")
                self.add_error_message(f"保存项目配置失败: {e}")

            # 更新项目状态
            self.project_manager.current_project_path = project_path
            self.project_manager.project_config = project_config
            
            # 更新窗口标题
            project_name = os.path.basename(project_path)
            self._update_window_title(project_name)
            
            print(f"✓ 项目打开成功: {project_path}")
            self.add_success_message(f"项目打开成功: {project_name}")
            return True
            
        except Exception as e:
            print(f"✗ 打开项目时发生错误: {e}")
            self.add_error_message(f"打开项目时发生错误: {e}")
            return False
    

    def _create_new_project_impl(self, name, path):
        """创建新项目的具体实现（不依赖Qt）"""
        import json
        import datetime
        import os
        
        full_project_path = os.path.normpath(os.path.join(path, name))
        print(f"创建项目路径: {full_project_path}")
        
        try:
            # 创建项目文件夹结构
            os.makedirs(full_project_path)
            os.makedirs(os.path.join(full_project_path, "models"))  # 模型文件夹
            os.makedirs(os.path.join(full_project_path, "textures"))  # 贴图文件夹
            scenes_path = os.path.join(full_project_path, "scenes")  # 场景文件夹
            os.makedirs(scenes_path)
            
            # 创建项目配置文件
            project_config = {
                "name": name,
                "path": full_project_path,
                "created": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                "last_modified": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                "version": "1.0",
                "scene_file": "scenes/scene.bam"
            }
            
            # 保存项目配置
            config_file = os.path.join(full_project_path, "project.json")
            with open(config_file, "w", encoding="utf-8") as f:
                json.dump(project_config, f, ensure_ascii=False, indent=4)
            
            # 保存初始场景
            scene_file = os.path.join(scenes_path, "scene.bam")
            self.scene_manager.saveScene(scene_file, full_project_path)
            
            # 更新项目管理器状态
            self.project_manager.current_project_path = full_project_path
            self.project_manager.project_config = project_config
            
            # 更新窗口标题
            self._update_window_title(name)
            
            return True
            
        except Exception as e:
            print(f"创建项目失败: {e}")
            return False
    

    def _update_window_title(self, project_name):
        """更新窗口标题"""
        try:
            props = WindowProperties()
            props.set_title(f"EG Engine - {project_name}")
            self.win.request_properties(props)
            print(f"窗口标题已更新: EG Engine - {project_name}")
        except Exception as e:
            print(f"更新窗口标题失败: {e}")
    
    # ==================== 路径浏览器辅助方法 ====================
    

    def _import_model_for_runtime(self, file_path, prefer_scene_manager=False):
        """Import model through the active runtime path.
        SSBO mode: load via SSBOEditor only (avoid duplicate SceneManager model).
        Legacy mode: load via SceneManager.
        """
        if prefer_scene_manager:
            if hasattr(self, 'scene_manager') and self.scene_manager:
                return self.scene_manager.importModel(file_path)
            return None

        if self.use_ssbo_mouse_picking and getattr(self, 'ssbo_editor', None):
            try:
                # Clear selection/gizmo first to avoid dangling references to soon-to-be removed nodes.
                if hasattr(self, 'selection') and self.selection:
                    try:
                        self.selection.clearSelection()
                    except Exception:
                        try:
                            self.selection.updateSelection(None)
                        except Exception:
                            pass

                # Remove legacy scene-manager models to avoid duplicate rendering
                if hasattr(self, 'scene_manager') and self.scene_manager and hasattr(self.scene_manager, 'models'):
                    for m in list(self.scene_manager.models):
                        try:
                            if m and not m.isEmpty():
                                m.removeNode()
                        except Exception:
                            pass
                    self.scene_manager.models = []

                # Replace previous SSBO model
                old_model = getattr(self.ssbo_editor, 'model', None)
                if old_model is not None:
                    try:
                        if not old_model.isEmpty():
                            old_model.removeNode()
                    except Exception:
                        pass

                self.ssbo_editor.load_model(file_path)
                model_np = getattr(self.ssbo_editor, 'model', None)
                # Keep legacy ray-pick fallback usable by adding a collision body.
                if model_np:
                    # Apply vital tags manually since SSBO overrides SceneManager loader
                    model_np.setTag("model_path", file_path)
                    model_np.setTag("original_path", file_path)
                    model_np.setTag("is_model_root", "1")
                    model_np.setTag("is_scene_element", "1")
                    model_np.setTag("file", os.path.basename(file_path))
                    model_np.setName(os.path.basename(file_path))
                    
                    if hasattr(self, 'scene_manager') and self.scene_manager:
                        try:
                            self.scene_manager.setupCollision(model_np)
                            self.scene_manager._processModelAnimations(model_np)
                        except Exception as e:
                            print(f"[SSBO] setup components failed: {e}")
                return model_np
            except Exception as e:
                print(f"[SSBO] load_model failed: {e}")
                return None

        # Legacy fallback
        if hasattr(self, 'scene_manager') and self.scene_manager:
            return self.scene_manager.importModel(file_path)
        return None

    def _import_model_with_menu_logic(
        self,
        file_path,
        select_model=True,
        set_origin=True,
        show_info_message=True,
        show_success_message=True,
    ):
        """统一的单文件导入入口，保持与菜单导入一致的处理流程。"""
        try:
            if not file_path:
                self.add_error_message("请选择要导入的文件")
                return None

            normalized_path = os.fspath(file_path)
            if not os.path.exists(normalized_path):
                self.add_error_message(f"文件不存在: {normalized_path}")
                return None

            file_ext = os.path.splitext(normalized_path)[1].lower()
            if file_ext not in self.supported_formats:
                self.add_error_message(f"不支持的文件格式: {file_ext}")
                return None

            if not hasattr(self, 'scene_manager') or not self.scene_manager:
                self.add_error_message("场景管理器未初始化")
                return None

            file_name = os.path.basename(normalized_path)
            if show_info_message:
                self.add_info_message(f"正在导入模型: {file_name}")

            model_node = self._import_model_for_runtime(normalized_path)
            if not model_node:
                self.add_error_message("模型导入失败")
                return None

            if hasattr(self.scene_manager, 'processMaterials'):
                self.scene_manager.processMaterials(model_node)
                if show_info_message:
                    self.add_info_message("已应用默认材质")

            try:
                model_node.clearMaterial()
                model_node.clearTexture()

                if hasattr(self.scene_manager, 'processMaterials'):
                    self.scene_manager.processMaterials(model_node)

                try:
                    color = model_node.getColor()
                    if color and len(color) >= 4 and color == (1, 1, 1, 1):
                        model_node.setColor(0.8, 0.8, 0.8, 1.0)
                    elif not color:
                        model_node.setColor(0.8, 0.8, 0.8, 1.0)
                except Exception:
                    model_node.setColor(0.8, 0.8, 0.8, 1.0)
            except Exception as e:
                self.add_warning_message(f"材质处理警告: {e}")

            if set_origin:
                model_node.setPos(0, 0, 0)

            if hasattr(self.scene_manager, 'models'):
                self.scene_manager.models.append(model_node)

            if select_model and hasattr(self, 'selection') and self.selection:
                self.selection.updateSelection(model_node)

            if show_success_message:
                self.add_success_message(f"模型导入成功: {file_name}")
            return model_node
        except Exception as e:
            self.add_error_message(f"导入模型失败: {e}")
            return None

    def _on_import_model(self):
        """处理导入模型菜单项"""
        self.add_info_message("打开导入模型对话框")
        self.show_import_dialog = True
    

    def _import_model(self):
        """导入模型的具体实现"""
        model_node = self._import_model_with_menu_logic(self.import_file_path)
        self.import_file_path = ""
        return model_node
