using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Core.Config;
using NavisworksTransport.Core.Models;
using NavisworksTransport.Utils;
using TimeTagProfile = NavisworksTransport.Core.Models.TimeTagProfile;
using TimeTagEvent = NavisworksTransport.Core.Models.TimeTagEvent;
using EventType = NavisworksTransport.Core.Models.EventType;
using NavisworksTransport.Core.Database;

namespace NavisworksTransport
{
    /// <summary>
    /// 路径数据库管理器 - 简化版
    /// 负责路径和碰撞数据的持久化存储
    /// </summary>
    public class PathDatabase : IDisposable
    {
        internal SQLiteConnection _connection;
        private readonly string _dbPath;
        private BackupManager _backupManager;

        /// <summary>
        /// 数据库连接（供内部使用）
        /// </summary>
        internal SQLiteConnection Connection => _connection;

        /// <summary>
        /// 简单数据库备份管理器（直接备份文件，最可靠）
        /// </summary>
        public BackupManager Backup => _backupManager ?? (_backupManager = new BackupManager(this));

        /// <summary>
        /// 初始化路径数据库
        /// </summary>
        /// <param name="documentPath">Navisworks文档路径</param>
        public PathDatabase(string documentPath)
        {
            // 数据库文件与nwd文件同名，扩展名为.db；
            // 支持环境变量 TRANSPORTPLUGIN_TEST_DB_PATH 覆盖（F1 副实例无 .db 时用临时库）
            string overrideDb = Environment.GetEnvironmentVariable("TRANSPORTPLUGIN_TEST_DB_PATH");
            _dbPath = string.IsNullOrWhiteSpace(overrideDb)
                ? Path.ChangeExtension(documentPath, ".db")
                : overrideDb;

            InitializeDatabase();
        }

        /// <summary>
        /// 初始化数据库连接和表结构
        /// </summary>
        private void InitializeDatabase()
        {
            bool isNewDatabase = !File.Exists(_dbPath);

            _connection = new SQLiteConnection($"Data Source={_dbPath};Version=3;");
            _connection.Open();

            // 启用WAL模式（Write-Ahead Logging，支持并发读）
            ExecuteNonQuery("PRAGMA journal_mode=WAL");
            ExecuteNonQuery("PRAGMA foreign_keys=ON");

            if (isNewDatabase)
            {
                LogManager.Info($"创建新数据库: {_dbPath}");
            }
            else
            {
                LogManager.Info($"打开现有数据库: {_dbPath}");
            }

            try
            {
                CreateTables();
                EnsurePathRoutesTableExtended();
            }
            catch (Exception ex)
            {
                LogManager.Error($"数据库初始化失败: {ex.Message}", ex);
                try { _connection?.Close(); } catch { }
                throw;
            }
        }

        /// <summary>
        /// 创建数据库表结构
        /// </summary>
        private void CreateTables()
        {
            // 路径基本信息表
            // 注意：TotalLength 字段已从数据库中删除
            // 路径长度由 PathRoute.TotalLength 计算属性实时计算：
            // - 地面路径：从 Edges.PhysicalLength 累加（支持圆弧）
            // - 空轨/吊装路径：从 Points 计算直线距离
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS PathRoutes (
                    Id TEXT PRIMARY KEY,
                    Name TEXT NOT NULL,
                    EstimatedTime REAL,
                    TurnRadius REAL,
                    IsCurved INTEGER,
                    MaxObjectLength REAL,
                    MaxObjectWidth REAL,
                    MaxObjectHeight REAL,
                    SafetyMargin REAL,
                    GridSize REAL,
                    PathType INTEGER,
                    LiftHeight REAL,
                    RailMountMode INTEGER,
                    RailPathDefinitionMode INTEGER,
                    RailNormalOffset REAL,
                    RailPreferredNormalX REAL,
                    RailPreferredNormalY REAL,
                    RailPreferredNormalZ REAL,
                    IsTwoPointVertical INTEGER DEFAULT 0,
                    CreatedTime DATETIME,
                    LastModified DATETIME
                )
            ");

            // 碰撞报告表（替换原有的CollisionResults表）
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS CollisionReports (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    RouteId TEXT,
                    AnimatedObjectName TEXT,
                    UniqueCollidedObjectsCount INTEGER,
                    FrameRate INTEGER,
                    Duration REAL,
                    DetectionGap REAL,
                    AnimationCollisionCount INTEGER,
                    ClashDetectiveCollisionCount INTEGER,
                    ReportTime DATETIME
                )
            ");

            // 分析结果表（扩展版，包含5维度评分）
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS AnalysisResults (
                    RouteId TEXT PRIMARY KEY,
                    CollisionCount INTEGER,
                    SafetyScore REAL,
                    EfficiencyScore REAL,
                    OverallScore REAL,
                    AnalysisTime DATETIME,
                    Strategy TEXT,
                    TurnDifficultyScore REAL,
                    TortuosityScore REAL,
                    RedundancyScore REAL,
                    HotspotCount INTEGER DEFAULT 0,
                    GroupId TEXT,
                    GroupRanking INTEGER DEFAULT 0,
                    FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE
                )
            ");

            // 路径点表（新增 CustomTurnRadius）
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS PathPoints (
                    Id TEXT PRIMARY KEY,
                    RouteId TEXT NOT NULL,
                    SequenceNumber INTEGER NOT NULL,
                    Name TEXT,
                    X REAL,
                    Y REAL,
                    Z REAL,
                    Type INTEGER,
                    CustomTurnRadius REAL,
                    FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE
                )
            ");

            // 路径边表（新增SequenceNumber字段）
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS PathEdges (
                    Id TEXT PRIMARY KEY,
                    RouteId TEXT NOT NULL,
                    StartPointId TEXT NOT NULL,
                    EndPointId TEXT NOT NULL,
                    SegmentType INTEGER NOT NULL,
                    PhysicalLength REAL NOT NULL,
                    SequenceNumber INTEGER NOT NULL,
                    Ts_X REAL,
                    Ts_Y REAL,
                    Ts_Z REAL,
                    Te_X REAL,
                    Te_Y REAL,
                    Te_Z REAL,
                    ArcCenter_X REAL,
                    ArcCenter_Y REAL,
                    ArcCenter_Z REAL,
                    RequestedRadius REAL,
                    ActualRadius REAL,
                    DeflectionAngle REAL,
                    ArcLength REAL,
                    FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE
                )
            ");

            // 碰撞检测记录表
            // 包含检测配置和检测结果（原 ClashDetectiveResults 表已合并）
            // 注意：以下表的外键依赖此表 — ClashDetectiveCollisionObjects, CollisionReportScreenshots,
            //       ClashDetectiveExcludedObjects, CollisionDetectionExcludedObjects, CollisionDetectionManualTargets
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS CollisionDetectionRecords (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    RouteId TEXT NOT NULL,
                    CreatedTime DATETIME DEFAULT CURRENT_TIMESTAMP,
                    -- 动画参数
                    FrameRate INTEGER NOT NULL DEFAULT 30,
                    DurationSeconds REAL NOT NULL DEFAULT 10.0,
                    -- 碰撞检测参数
                    DetectionTolerance REAL NOT NULL DEFAULT 0.05,
                    -- 运动物体参数
                    IsVirtualObject INTEGER NOT NULL DEFAULT 0,
                    AnimatedObjectName TEXT,
                    ObjectModelIndex INTEGER,
                    ObjectPathId TEXT,
                    VirtualObjectLength REAL,
                    VirtualObjectWidth REAL,
                    VirtualObjectHeight REAL,
                    -- 检测模式
                    DetectAllObjects INTEGER NOT NULL DEFAULT 1,
                    -- 角度修正
                    ObjectRotationCorrection REAL DEFAULT 0.0,
                    -- 备注/描述
                    Description TEXT,
                    -- 碰撞结果字段（原 ClashDetectiveResults 表字段）
                    TestName TEXT UNIQUE,              -- ClashDetective测试名称
                    TestTime DATETIME,                 -- 碰撞测试完成时间
                    CollisionCount INTEGER DEFAULT 0,  -- 总碰撞数
                    AnimationCollisionCount INTEGER DEFAULT 0,  -- 动画碰撞数
                    FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE
                )
            ");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_detection_route ON CollisionDetectionRecords(RouteId)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_detection_time ON CollisionDetectionRecords(CreatedTime DESC)");

            // ClashDetective碰撞对象表
            // 注意：外键关联到 CollisionDetectionRecords 表
            // 扩展字段：记录碰撞时运动物体的位置和朝向，用于还原碰撞场景
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS ClashDetectiveCollisionObjects (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    DetectionRecordId INTEGER NOT NULL,
                    ModelIndex INTEGER,
                    PathId TEXT,
                    DisplayName TEXT,
                    ObjectName TEXT,
                    -- 碰撞时运动物体的动画跟踪点位置和姿态（用于还原碰撞场景）
                    AnimatedObjectTrackedPosX REAL,
                    AnimatedObjectTrackedPosY REAL,
                    AnimatedObjectTrackedPosZ REAL,
                    AnimatedObjectTrackedYawRadians REAL,
                    AnimatedObjectTrackedRotA REAL,
                    AnimatedObjectTrackedRotB REAL,
                    AnimatedObjectTrackedRotC REAL,
                    AnimatedObjectTrackedRotD REAL,
                    AnimatedObjectHasTrackedRotation INTEGER DEFAULT 0,
                    HasPositionInfo INTEGER DEFAULT 0,
                    FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE CASCADE
                )
            ");

            // 碰撞报告截图表（支持多张截图）
            // 注意：外键关联到 CollisionDetectionRecords 表
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS CollisionReportScreenshots (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    DetectionRecordId INTEGER NOT NULL,
                    FilePath TEXT NOT NULL,
                    Format TEXT,
                    Width INTEGER,
                    Height INTEGER,
                    CaptureTime DATETIME DEFAULT CURRENT_TIMESTAMP,
                    Description TEXT,
                    SortOrder INTEGER DEFAULT 0,
                    FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE CASCADE
                )
            ");

            // 时标配置表
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS TimeTagProfiles (
                    Id TEXT PRIMARY KEY,
                    RouteId TEXT NOT NULL,
                    Name TEXT NOT NULL,
                    DefaultSpeed REAL DEFAULT 1.0,
                    TurnSpeedRatio REAL DEFAULT 0.5,
                    CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE
                )
            ");

            // 时标事件表
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS TimeTagEvents (
                    Id TEXT PRIMARY KEY,
                    ProfileId TEXT NOT NULL,
                    PointId TEXT,
                    Distance REAL NOT NULL,
                    Type TEXT NOT NULL,
                    Name TEXT NOT NULL,
                    WaitTime REAL DEFAULT 0,
                    ActionTime REAL DEFAULT 0,
                    CumulativeTime REAL DEFAULT 0,
                    ArriveSpeed REAL,
                    LeaveSpeed REAL,
                    SortOrder INTEGER NOT NULL,
                    FOREIGN KEY(ProfileId) REFERENCES TimeTagProfiles(Id) ON DELETE CASCADE
                )
            ");

            // 创建索引
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_reports_route ON CollisionReports(RouteId)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_pathpoints_route ON PathPoints(RouteId)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_pathedges_route ON PathEdges(RouteId)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_timetag_profiles_route ON TimeTagProfiles(RouteId)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_timetag_events_profile ON TimeTagEvents(ProfileId)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_detection_test_name ON CollisionDetectionRecords(TestName)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_detection_test_time ON CollisionDetectionRecords(TestTime DESC)");
            SafeCreateIndex("CREATE INDEX IF NOT EXISTS idx_clash_objects_detection ON ClashDetectiveCollisionObjects(DetectionRecordId)", "ClashDetectiveCollisionObjects", "DetectionRecordId");
            SafeCreateIndex("CREATE INDEX IF NOT EXISTS idx_clash_objects_path ON ClashDetectiveCollisionObjects(PathId)", "ClashDetectiveCollisionObjects", "PathId");
            SafeCreateIndex("CREATE INDEX IF NOT EXISTS idx_screenshots_detection ON CollisionReportScreenshots(DetectionRecordId)", "CollisionReportScreenshots", "DetectionRecordId");
            SafeCreateIndex("CREATE INDEX IF NOT EXISTS idx_screenshots_sort ON CollisionReportScreenshots(DetectionRecordId, SortOrder)", "CollisionReportScreenshots", "DetectionRecordId", "SortOrder");

            // 批处理队列表（简化版，只维护FIFO队列）
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS BatchQueueItems (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    RouteId TEXT NOT NULL,
                    Status TEXT NOT NULL,
                    CreatedTime TEXT NOT NULL,
                    StartTime TEXT,
                    EndTime TEXT,
                    ErrorMessage TEXT,
                    FrameRate INTEGER NOT NULL,
                    DurationSeconds REAL NOT NULL,
                    DetectionTolerance REAL NOT NULL,
                    IsVirtualObject INTEGER NOT NULL,
                    VirtualObjectLength REAL,
                    VirtualObjectWidth REAL,
                    VirtualObjectHeight REAL,
                    DetectAllObjects INTEGER NOT NULL DEFAULT 1,
                    ClashDetectiveTestName TEXT,
                    CollisionCount INTEGER,
                    ObjectRotationCorrection REAL
                )
            ");

            // 批处理表索引
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_queue_status ON BatchQueueItems(Status)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_queue_created ON BatchQueueItems(CreatedTime)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_batch_queue_route_id ON BatchQueueItems(RouteId)");

            // 通用 ModelItem 引用表（用于存储运动物体、碰撞物体等）
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS ModelItemReferences (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    ReferenceId INTEGER NOT NULL,
                    ReferenceType TEXT NOT NULL,
                    ModelIndex INTEGER NOT NULL,
                    PathId TEXT NOT NULL,
                    DisplayName TEXT,
                    ObjectName TEXT,
                    Role TEXT
                )
            ");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_model_ref_reference ON ModelItemReferences(ReferenceId)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_model_ref_type ON ModelItemReferences(ReferenceType)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_model_ref_path ON ModelItemReferences(PathId)");

            // 排除对象表（用于存储用户排除的碰撞检测对象）
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS ExcludedObjects (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    RouteId TEXT,
                    ModelIndex INTEGER NOT NULL,
                    PathId TEXT NOT NULL,
                    DisplayName TEXT,
                    ObjectName TEXT,
                    ExcludedTime DATETIME DEFAULT CURRENT_TIMESTAMP,
                    Reason TEXT,
                    IsGlobal INTEGER DEFAULT 0,
                    FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE
                )
            ");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_excluded_route ON ExcludedObjects(RouteId)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_excluded_path ON ExcludedObjects(PathId)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_excluded_global ON ExcludedObjects(IsGlobal)");

            // 碰撞报告排除对象关联表
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS CollisionReportExcludedObjects (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    ReportId INTEGER NOT NULL,
                    ExcludedObjectId INTEGER NOT NULL,
                    FOREIGN KEY(ReportId) REFERENCES CollisionReports(Id) ON DELETE CASCADE,
                    FOREIGN KEY(ExcludedObjectId) REFERENCES ExcludedObjects(Id) ON DELETE CASCADE
                )
            ");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_report_excluded_report ON CollisionReportExcludedObjects(ReportId)");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_report_excluded_object ON CollisionReportExcludedObjects(ExcludedObjectId)");

            // 碰撞检测记录排除对象关联表（记录每次检测时排除的对象）
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS ClashDetectiveExcludedObjects (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    DetectionRecordId INTEGER NOT NULL,
                    ExcludedObjectId INTEGER NOT NULL,
                    FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE CASCADE,
                    FOREIGN KEY(ExcludedObjectId) REFERENCES ExcludedObjects(Id) ON DELETE CASCADE
                )
            ");
            SafeCreateIndex("CREATE INDEX IF NOT EXISTS idx_clash_excluded_detection ON ClashDetectiveExcludedObjects(DetectionRecordId)", "ClashDetectiveExcludedObjects", "DetectionRecordId");
            ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_excluded_object ON ClashDetectiveExcludedObjects(ExcludedObjectId)");

            // 为 BatchQueueItems 补充 DetectionRecordId 列（兼容旧数据库）
            EnsureColumnExists("BatchQueueItems", "DetectionRecordId", "ALTER TABLE BatchQueueItems ADD COLUMN DetectionRecordId INTEGER");

            // 碰撞检测记录排除对象关联表（记录每次检测时的排除列表）
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS CollisionDetectionExcludedObjects (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    DetectionRecordId INTEGER NOT NULL,
                    ModelIndex INTEGER NOT NULL,
                    PathId TEXT NOT NULL,
                    DisplayName TEXT,
                    ObjectName TEXT,
                    Reason TEXT,
                    FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE CASCADE
                )
            ");
            SafeCreateIndex("CREATE INDEX IF NOT EXISTS idx_detection_excluded_record ON CollisionDetectionExcludedObjects(DetectionRecordId)", "CollisionDetectionExcludedObjects", "DetectionRecordId");

            // 碰撞检测记录手工目标关联表（记录每次检测时的手工碰撞目标）
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS CollisionDetectionManualTargets (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    DetectionRecordId INTEGER NOT NULL,
                    ModelIndex INTEGER NOT NULL,
                    PathId TEXT NOT NULL,
                    DisplayName TEXT,
                    ObjectName TEXT,
                    FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE CASCADE
                )
            ");
            SafeCreateIndex("CREATE INDEX IF NOT EXISTS idx_detection_manual_record ON CollisionDetectionManualTargets(DetectionRecordId)", "CollisionDetectionManualTargets", "DetectionRecordId");

            // 别名映射表（节点重命名 — 别名导航树）
            ExecuteNonQuery(@"
                CREATE TABLE IF NOT EXISTS AliasMap (
                    Id          INTEGER PRIMARY KEY AUTOINCREMENT,
                    NodeKey     TEXT    NOT NULL UNIQUE,
                    AliasText   TEXT    NOT NULL,
                    CreatedAt   TEXT    DEFAULT (datetime('now')),
                    ModifiedAt  TEXT    DEFAULT (datetime('now'))
                )
            ");
            SafeCreateIndex("CREATE INDEX IF NOT EXISTS idx_alias_key ON AliasMap(NodeKey)", "AliasMap", "NodeKey");

            // 设置数据库版本
            ExecuteNonQuery("PRAGMA user_version = 10010");
        }

        /// <summary>
        /// 安全创建索引：先检查列是否存在，若旧数据库缺列则跳过并记录警告
        /// </summary>
        private void SafeCreateIndex(string indexSql, string tableName, params string[] columnNames)
        {
            try
            {
                var existingColumns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                using (var cmd = new SQLiteCommand($"PRAGMA table_info({tableName})", _connection))
                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        existingColumns.Add(reader["name"].ToString());
                    }
                }

                foreach (var col in columnNames)
                {
                    if (!existingColumns.Contains(col))
                    {
                        LogManager.Warning($"[数据库] 表 {tableName} 缺少列 {col}，跳过索引创建");
                        return;
                    }
                }

                ExecuteNonQuery(indexSql);
            }
            catch (Exception ex)
            {
                LogManager.Warning($"[数据库] 创建索引失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 按需添加列：检查表是否已有指定列，没有则执行 ALTER TABLE
        /// </summary>
        private void EnsureColumnExists(string tableName, string columnName, string alterSql)
        {
            try
            {
                using (var cmd = new SQLiteCommand($"PRAGMA table_info({tableName})", _connection))
                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        if (string.Equals(reader["name"].ToString(), columnName, StringComparison.OrdinalIgnoreCase))
                            return; // 列已存在
                    }
                }

                ExecuteNonQuery(alterSql);
                LogManager.Info($"[数据库] 已添加 {columnName} 列到 {tableName} 表");
            }
            catch (Exception ex)
            {
                LogManager.Warning($"[数据库] 添加列 {tableName}.{columnName} 失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 保存路径基本信息
        /// </summary>
        public void SavePathRoute(PathRoute route)
        {
            // 使用事务确保路径和路径点一起保存
            using (var transaction = _connection.BeginTransaction())
            {
                try
                {
                    // 保存路径基本信息
                    // 注意：TotalLength 字段已从数据库中删除
                    // 路径长度由 PathRoute.TotalLength 计算属性实时从 Edges/Points 计算
                    var sql = @"
                        INSERT OR REPLACE INTO PathRoutes
                        (Id, Name, EstimatedTime, TurnRadius, IsCurved, MaxObjectLength, MaxObjectWidth, MaxObjectHeight, SafetyMargin, GridSize, PathType, LiftHeight, RailMountMode, RailPathDefinitionMode, RailNormalOffset, RailPreferredNormalX, RailPreferredNormalY, RailPreferredNormalZ, IsTwoPointVertical, CreatedTime, LastModified)
                        VALUES (@id, @name, @time, @turnRadius, @isCurved, @maxLength, @maxWidth, @maxHeight, @safetyMargin, @gridSize, @pathType, @liftHeightMeters, @railMountMode, @railPathDefinitionMode, @railNormalOffset, @railPreferredNormalX, @railPreferredNormalY, @railPreferredNormalZ, @isTwoPointVertical, @created, @modified)
                    ";

                    using (var cmd = new SQLiteCommand(sql, _connection))
                    {
                        cmd.Parameters.AddWithValue("@id", route.Id);
                        cmd.Parameters.AddWithValue("@name", route.Name);
                        cmd.Parameters.AddWithValue("@time", route.EstimatedTime);
                        cmd.Parameters.AddWithValue("@turnRadius", route.TurnRadius);
                        cmd.Parameters.AddWithValue("@isCurved", route.IsCurved ? 1 : 0);
                        cmd.Parameters.AddWithValue("@maxLength", route.MaxObjectLength);
                        cmd.Parameters.AddWithValue("@maxWidth", route.MaxObjectWidth);
                        cmd.Parameters.AddWithValue("@maxHeight", route.MaxObjectHeight);
                        cmd.Parameters.AddWithValue("@safetyMargin", route.SafetyMargin);
                        cmd.Parameters.AddWithValue("@gridSize", route.GridSize);
                        cmd.Parameters.AddWithValue("@pathType", (int)route.PathType);
                        cmd.Parameters.AddWithValue("@liftHeightMeters", route.LiftHeight);
                        cmd.Parameters.AddWithValue("@railMountMode", (int)route.RailMountMode);
                        cmd.Parameters.AddWithValue("@railPathDefinitionMode", (int)route.RailPathDefinitionMode);
                        cmd.Parameters.AddWithValue("@railNormalOffset", route.RailNormalOffset);
                        cmd.Parameters.AddWithValue("@railPreferredNormalX", route.RailPreferredNormal != null ? (object)route.RailPreferredNormal.X : DBNull.Value);
                        cmd.Parameters.AddWithValue("@railPreferredNormalY", route.RailPreferredNormal != null ? (object)route.RailPreferredNormal.Y : DBNull.Value);
                        cmd.Parameters.AddWithValue("@railPreferredNormalZ", route.RailPreferredNormal != null ? (object)route.RailPreferredNormal.Z : DBNull.Value);
                        cmd.Parameters.AddWithValue("@isTwoPointVertical", route.IsTwoPointVertical ? 1 : 0);
                        cmd.Parameters.AddWithValue("@created", route.CreatedTime);
                        cmd.Parameters.AddWithValue("@modified", DateTime.Now);
                        cmd.ExecuteNonQuery();
                    }

                    // 删除旧的路径点和边
                    DeletePathPoints(route.Id);
                    DeletePathEdges(route.Id);

                    // 保存新的路径点
                    if (route.Points != null && route.Points.Count > 0)
                    {
                        var pointSql = @"
                            INSERT INTO PathPoints
                            (Id, RouteId, SequenceNumber, Name, X, Y, Z, Type, CustomTurnRadius)
                            VALUES (@id, @routeId, @seq, @name, @x, @y, @z, @type, @customRadius)
                        ";

                        for (int i = 0; i < route.Points.Count; i++)
                        {
                            var point = route.Points[i];
                            using (var cmd = new SQLiteCommand(pointSql, _connection))
                            {
                                cmd.Parameters.AddWithValue("@id", point.Id);
                                cmd.Parameters.AddWithValue("@routeId", route.Id);
                                cmd.Parameters.AddWithValue("@seq", i);
                                cmd.Parameters.AddWithValue("@name", point.Name ?? "");
                                cmd.Parameters.AddWithValue("@x", point.Position.X);
                                cmd.Parameters.AddWithValue("@y", point.Position.Y);
                                cmd.Parameters.AddWithValue("@z", point.Position.Z);
                                cmd.Parameters.AddWithValue("@type", (int)point.Type);
                                cmd.Parameters.AddWithValue("@customRadius", point.CustomTurnRadius.HasValue ?
                                    (object)point.CustomTurnRadius.Value : DBNull.Value);
                                cmd.ExecuteNonQuery();
                            }
                        }

                        LogManager.Info($"保存路径: {route.Name}，包含 {route.Points.Count} 个路径点");
                    }

                    // 保存路径边
                    if (route.Edges != null && route.Edges.Count > 0)
                    {
                        for (int i = 0; i < route.Edges.Count; i++)
                        {
                            SavePathEdge(route.Id, route.Edges[i], i);
                        }

                        LogManager.Info($"保存路径: {route.Name}，包含 {route.Edges.Count} 个路径边");
                    }

                    transaction.Commit();
                }
                catch (Exception ex)
                {
                    transaction.Rollback();
                    LogManager.Error($"保存路径失败: {ex.Message}", ex);
                    throw;
                }
            }
        }

        /// <summary>
        /// 保存碰撞报告，返回报告ID
        /// </summary>
        public void SaveCollisionReport(string routeId, string pathName, string animatedObjectName,
                                       int uniqueCollidedObjectsCount, int frameRate, double duration, double detectionGap,
                                       int animationCollisionCount, int clashDetectiveCollisionCount)
        {
            try
            {
                var sql = @"
                    INSERT INTO CollisionReports
                    (RouteId, AnimatedObjectName, UniqueCollidedObjectsCount, FrameRate, Duration,
                     DetectionGap, AnimationCollisionCount, ClashDetectiveCollisionCount, ReportTime)
                    VALUES (@routeId, @animatedObjectName, @uniqueCollidedObjectsCount, @frameRate, @duration,
                            @detectionGap, @animationCount, @clashCount, @reportTime)
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@routeId", routeId ?? "");
                    cmd.Parameters.AddWithValue("@animatedObjectName", animatedObjectName ?? "未知对象");
                    cmd.Parameters.AddWithValue("@uniqueCollidedObjectsCount", uniqueCollidedObjectsCount);
                    cmd.Parameters.AddWithValue("@frameRate", frameRate);
                    cmd.Parameters.AddWithValue("@duration", duration);
                    cmd.Parameters.AddWithValue("@detectionGap", detectionGap);
                    cmd.Parameters.AddWithValue("@animationCount", animationCollisionCount);
                    cmd.Parameters.AddWithValue("@clashCount", clashDetectiveCollisionCount);
                    cmd.Parameters.AddWithValue("@reportTime", DateTime.Now);
                    cmd.ExecuteNonQuery();
                }

                LogManager.Info($"碰撞报告已保存: 路径ID={routeId}, 碰撞构件={uniqueCollidedObjectsCount}, 动画碰撞={animationCollisionCount}, ClashDetective碰撞={clashDetectiveCollisionCount}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"保存碰撞报告失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 更新碰撞报告的截图信息（更新CollisionDetectionRecords表）
        /// </summary>
        public void UpdateCollisionReportScreenshot(string routeId, string screenshotPath, 
                                                    string screenshotFormat, int screenshotWidth, int screenshotHeight)
        {
            try
            {
                // 获取该 RouteId 的最新记录Id
                var getIdSql = @"
                    SELECT Id FROM CollisionDetectionRecords
                    WHERE RouteId = @routeId
                    ORDER BY CreatedTime DESC
                    LIMIT 1
                ";
                
                int? detectionRecordId = null;
                using (var cmd = new SQLiteCommand(getIdSql, _connection))
                {
                    cmd.Parameters.AddWithValue("@routeId", routeId ?? "");
                    var result = cmd.ExecuteScalar();
                    if (result != null && result != DBNull.Value)
                    {
                        detectionRecordId = Convert.ToInt32(result);
                    }
                }
                
                if (!detectionRecordId.HasValue)
                {
                    LogManager.Warning($"未找到需要更新的碰撞记录: RouteId={routeId}");
                    return;
                }
                
                // 保存到截图表
                SaveCollisionReportScreenshot(detectionRecordId.Value, screenshotPath, screenshotFormat, screenshotWidth, screenshotHeight, 0);
                
                LogManager.Info($"碰撞报告截图已更新: RouteId={routeId}, 截图={screenshotPath}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"更新碰撞报告截图失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 保存碰撞报告截图到截图表，返回新记录Id
        /// </summary>
        public int SaveCollisionReportScreenshot(int detectionRecordId, string filePath, string format, int width, int height, int sortOrder, string description = null)
        {
            try
            {
                var sql = @"
                    INSERT INTO CollisionReportScreenshots
                    (DetectionRecordId, FilePath, Format, Width, Height, CaptureTime, Description, SortOrder)
                    VALUES (@detectionRecordId, @filePath, @format, @width, @height, @captureTime, @description, @sortOrder)
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId);
                    cmd.Parameters.AddWithValue("@filePath", filePath ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@format", format ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@width", width);
                    cmd.Parameters.AddWithValue("@height", height);
                    cmd.Parameters.AddWithValue("@captureTime", DateTime.Now);
                    cmd.Parameters.AddWithValue("@description", description ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@sortOrder", sortOrder);
                    cmd.ExecuteNonQuery();
                }

                // 获取新插入记录的Id
                int newId = (int)_connection.LastInsertRowId;
                LogManager.Debug($"碰撞报告截图已保存: Id={newId}, DetectionRecordId={detectionRecordId}, FilePath={filePath}");
                return newId;
            }
            catch (Exception ex)
            {
                LogManager.Error($"保存碰撞报告截图失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 获取碰撞报告的所有截图
        /// </summary>
        public List<CollisionReportScreenshotRecord> GetCollisionReportScreenshots(int detectionRecordId)
        {
            var screenshots = new List<CollisionReportScreenshotRecord>();
            
            try
            {
                var sql = @"
                    SELECT Id, DetectionRecordId, FilePath, Format, Width, Height, CaptureTime, Description, SortOrder
                    FROM CollisionReportScreenshots
                    WHERE DetectionRecordId = @detectionRecordId
                    ORDER BY SortOrder, CaptureTime
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            screenshots.Add(new CollisionReportScreenshotRecord
                            {
                                Id = Convert.ToInt32(reader["Id"]),
                                DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]),
                                FilePath = reader["FilePath"]?.ToString(),
                                Format = reader["Format"]?.ToString(),
                                Width = reader["Width"] != DBNull.Value ? Convert.ToInt32(reader["Width"]) : 0,
                                Height = reader["Height"] != DBNull.Value ? Convert.ToInt32(reader["Height"]) : 0,
                                CaptureTime = reader["CaptureTime"] != DBNull.Value ? Convert.ToDateTime(reader["CaptureTime"]) : DateTime.MinValue,
                                Description = reader["Description"]?.ToString(),
                                SortOrder = reader["SortOrder"] != DBNull.Value ? Convert.ToInt32(reader["SortOrder"]) : 0
                            });
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取碰撞报告截图失败: {ex.Message}", ex);
            }

            return screenshots;
        }

        /// <summary>
        /// 删除碰撞报告截图
        /// </summary>
        public void DeleteCollisionReportScreenshot(int screenshotId)
        {
            try
            {
                var sql = "DELETE FROM CollisionReportScreenshots WHERE Id = @id";
                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@id", screenshotId);
                    cmd.ExecuteNonQuery();
                }
                LogManager.Debug($"碰撞报告截图已删除: ScreenshotId={screenshotId}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"删除碰撞报告截图失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 更新截图排序和描述
        /// </summary>
        public void UpdateCollisionReportScreenshotMetadata(int screenshotId, int sortOrder, string description)
        {
            try
            {
                var sql = @"
                    UPDATE CollisionReportScreenshots
                    SET SortOrder = @sortOrder, Description = @description
                    WHERE Id = @id
                ";
                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@id", screenshotId);
                    cmd.Parameters.AddWithValue("@sortOrder", sortOrder);
                    cmd.Parameters.AddWithValue("@description", description ?? (object)DBNull.Value);
                    cmd.ExecuteNonQuery();
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"更新截图元数据失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 更新路径名称（仅更新名称字段）
        /// </summary>
        public void UpdatePathName(string routeId, string newName)
        {
            if (string.IsNullOrEmpty(routeId) || string.IsNullOrEmpty(newName))
            {
                LogManager.Warning("更新路径名称失败：routeId或newName为空");
                return;
            }

            var sql = @"UPDATE PathRoutes 
                        SET Name = @name, LastModified = @modified 
                        WHERE Id = @id";

            try
            {
                ExecuteNonQuery(sql,
                    new { id = routeId, name = newName, modified = DateTime.Now });
                LogManager.Info($"更新路径名称成功: {routeId} -> {newName}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"更新路径名称失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 保存路径的碰撞结果（已废弃，保留以兼容旧代码）
        /// </summary>
        public void SaveCollisions(string routeId, List<CollisionResult> collisions)
        {
            try
            {
                // 先删除旧的碰撞数据（CollisionResults 表已废弃移除，失败时忽略）
                ExecuteNonQuery("DELETE FROM CollisionResults WHERE RouteId=@id",
                              new { id = routeId });
            }
            catch
            {
                // CollisionResults 表已移除，忽略删除失败
            }

            if (collisions == null || collisions.Count == 0)
                return;

            // 批量插入新数据
            using (var transaction = _connection.BeginTransaction())
            {
                try
                {
                    var sql = @"
                        INSERT INTO CollisionResults
                        (RouteId, Item1Name, Item2Name, CenterX, CenterY, CenterZ, Distance, DetectedTime)
                        VALUES (@rid, @i1, @i2, @x, @y, @z, @dist, @time)
                    ";

                    try
                    {
                        foreach (var collision in collisions)
                        {
                            using (var cmd = new SQLiteCommand(sql, _connection))
                            {
                                cmd.Parameters.AddWithValue("@rid", routeId);
                                cmd.Parameters.AddWithValue("@i1", GetItemDisplayName(collision.Item1));
                                cmd.Parameters.AddWithValue("@i2", GetItemDisplayName(collision.Item2));
                                cmd.Parameters.AddWithValue("@x", collision.Center.X);
                                cmd.Parameters.AddWithValue("@y", collision.Center.Y);
                                cmd.Parameters.AddWithValue("@z", collision.Center.Z);
                                cmd.Parameters.AddWithValue("@dist", collision.Distance);
                                cmd.Parameters.AddWithValue("@time", DateTime.Now);
                                cmd.ExecuteNonQuery();
                            }
                        }
                    }
                    catch
                    {
                        // CollisionResults 表已移除：插入失败属预期，不影响主流程
                    }

                    transaction.Commit();
                    LogManager.Info($"保存碰撞结果: RouteId={routeId}, Count={collisions.Count}");
                }
                catch (Exception ex)
                {
                    transaction.Rollback();
                    LogManager.Error($"保存碰撞结果失败: {ex.Message}", ex);
                    throw;
                }
            }
        }

        #region 碰撞检测记录查询

        /// <summary>
        /// 获取路径的所有碰撞检测记录（包含碰撞结果）
        /// </summary>
        public List<CollisionDetectionRecord> GetDetectionResultsByPath(string pathName)
        {
            var results = new List<CollisionDetectionRecord>();

            try
            {
                var sql = @"
                    SELECT cdr.*, pr.Name AS PathName
                    FROM CollisionDetectionRecords cdr
                    INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id
                    WHERE pr.Name = @pathName AND cdr.TestName IS NOT NULL
                    ORDER BY cdr.TestTime DESC
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@pathName", pathName);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            var record = ReadCollisionDetectionRecordFromReader(reader);
                            results.Add(record);
                        }
                    }
                }

                LogManager.Debug($"获取到 {results.Count} 条碰撞检测记录");
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取碰撞检测记录失败: {ex.Message}", ex);
            }

            return results;
        }

        /// <summary>
        /// 获取路径最新的碰撞检测记录（包含碰撞结果）
        /// </summary>
        public CollisionDetectionRecord GetLatestDetectionResult(string pathName)
        {
            try
            {
                var sql = @"
                    SELECT cdr.*, pr.Name AS PathName
                    FROM CollisionDetectionRecords cdr
                    INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id
                    WHERE pr.Name = @pathName AND cdr.TestName IS NOT NULL
                    ORDER BY cdr.TestTime DESC
                    LIMIT 1
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@pathName", pathName);
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            return ReadCollisionDetectionRecordFromReader(reader);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取最新碰撞检测记录失败: {ex.Message}", ex);
            }

            return null;
        }

        /// <summary>
        /// 根据ID获取碰撞检测记录
        /// </summary>
        public CollisionDetectionRecord GetDetectionResultById(int id)
        {
            try
            {
                var sql = @"
                    SELECT cdr.*, pr.Name AS PathName
                    FROM CollisionDetectionRecords cdr
                    INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id
                    WHERE cdr.Id = @id
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@id", id);
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            return ReadCollisionDetectionRecordFromReader(reader);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"根据ID获取碰撞检测记录失败: {ex.Message}", ex);
            }

            return null;
        }

        /// <summary>
        /// 删除碰撞检测记录
        /// </summary>
        public void DeleteDetectionResult(int id)
        {
            try
            {
                ExecuteNonQuery("DELETE FROM CollisionDetectionRecords WHERE Id=@id",
                              new { id = id });
                LogManager.Info($"已删除碰撞检测记录: Id={id}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"删除碰撞检测记录失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 获取所有碰撞检测记录（包含碰撞结果）
        /// </summary>
        public List<CollisionDetectionRecord> GetAllDetectionResults()
        {
            var results = new List<CollisionDetectionRecord>();
            
            try
            {
                var sql = @"
                    SELECT cdr.*, pr.Name AS PathName
                    FROM CollisionDetectionRecords cdr
                    INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id
                    WHERE cdr.TestName IS NOT NULL
                    ORDER BY cdr.TestTime DESC
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var record = ReadCollisionDetectionRecordFromReader(reader);
                        results.Add(record);
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取所有碰撞检测记录失败: {ex.Message}", ex);
            }

            return results;
        }

        /// <summary>
        /// 清空所有数据
        /// </summary>
        public void ClearAllData()
        {
            try
            {
                // 按依赖关系顺序删除（先删除子表，再删除主表）
                var tables = new[]
                {
                    "CollisionReportScreenshots",
                    "ClashDetectiveCollisionObjects",
                    "CollisionDetectionExcludedObjects",
                    "CollisionDetectionManualTargets",
                    "CollisionDetectionRecords",
                    "PathEdges",
                    "PathPoints",
                    "AnalysisResults",
                    "CollisionReports",
                    "PathRoutes",
                    "BatchQueueItems"
                };

                foreach (var table in tables)
                {
                    try
                    {
                        ExecuteNonQuery($"DELETE FROM {table}");
                        LogManager.Debug($"已清空表: {table}");
                    }
                    catch (Exception ex)
                    {
                        LogManager.Warning($"清空表 {table} 失败（可能表不存在）: {ex.Message}");
                    }
                }

                // 重置自增ID
                try
                {
                    ExecuteNonQuery("DELETE FROM sqlite_sequence");
                }
                catch (Exception ex)
                {
                    LogManager.Debug($"[数据库] 重置自增ID失败: {ex.Message}");
                }

                LogManager.Info("已清空所有数据表");
            }
            catch (Exception ex)
            {
                LogManager.Error($"清空数据失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 根据测试名称获取碰撞检测记录
        /// </summary>
        public CollisionDetectionRecord GetDetectionResultByTestName(string testName)
        {
            try
            {
                var sql = @"
                    SELECT cdr.*, pr.Name AS PathName
                    FROM CollisionDetectionRecords cdr
                    INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id
                    WHERE cdr.TestName = @testName
               ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@testName", testName);
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            return ReadCollisionDetectionRecordFromReader(reader);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"根据测试名称获取碰撞检测记录失败: {ex.Message}", ex);
            }

            return null;
        }

        /// <summary>
        /// 保存ClashDetective碰撞对象
        /// </summary>
        public void SaveClashDetectiveCollisionObjects(int detectionRecordId, List<ClashDetectiveCollisionObjectRecord> objects)
        {
            if (objects == null || objects.Count == 0)
                return;

            try
            {
                using (var transaction = _connection.BeginTransaction())
                {
                    var sql = @"
                        INSERT INTO ClashDetectiveCollisionObjects
                        (DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName, 
                         AnimatedObjectTrackedPosX, AnimatedObjectTrackedPosY, AnimatedObjectTrackedPosZ, AnimatedObjectTrackedYawRadians,
                         AnimatedObjectTrackedRotA, AnimatedObjectTrackedRotB, AnimatedObjectTrackedRotC, AnimatedObjectTrackedRotD, AnimatedObjectHasTrackedRotation, HasPositionInfo)
                        VALUES (@detectionRecordId, @modelIndex, @pathId, @displayName, @objectName,
                                @trackedPosX, @trackedPosY, @trackedPosZ, @trackedYawRadians,
                                @trackedRotA, @trackedRotB, @trackedRotC, @trackedRotD, @hasTrackedRotation, @hasPositionInfo)
                    ";

                    foreach (var obj in objects)
                    {
                        using (var cmd = new SQLiteCommand(sql, _connection))
                        {
                            cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId);
                            cmd.Parameters.AddWithValue("@modelIndex", obj.ModelIndex.HasValue ? (object)obj.ModelIndex.Value : DBNull.Value);
                            cmd.Parameters.AddWithValue("@pathId", obj.PathId ?? (object)DBNull.Value);
                            cmd.Parameters.AddWithValue("@displayName", obj.DisplayName ?? "");
                            cmd.Parameters.AddWithValue("@objectName", obj.ObjectName ?? "");
                            cmd.Parameters.AddWithValue("@trackedPosX", obj.AnimatedObjectTrackedPosX.HasValue ? (object)obj.AnimatedObjectTrackedPosX.Value : DBNull.Value);
                            cmd.Parameters.AddWithValue("@trackedPosY", obj.AnimatedObjectTrackedPosY.HasValue ? (object)obj.AnimatedObjectTrackedPosY.Value : DBNull.Value);
                            cmd.Parameters.AddWithValue("@trackedPosZ", obj.AnimatedObjectTrackedPosZ.HasValue ? (object)obj.AnimatedObjectTrackedPosZ.Value : DBNull.Value);
                            cmd.Parameters.AddWithValue("@trackedYawRadians", obj.AnimatedObjectTrackedYawRadians.HasValue ? (object)obj.AnimatedObjectTrackedYawRadians.Value : DBNull.Value);
                            cmd.Parameters.AddWithValue("@trackedRotA", obj.AnimatedObjectTrackedRotA.HasValue ? (object)obj.AnimatedObjectTrackedRotA.Value : DBNull.Value);
                            cmd.Parameters.AddWithValue("@trackedRotB", obj.AnimatedObjectTrackedRotB.HasValue ? (object)obj.AnimatedObjectTrackedRotB.Value : DBNull.Value);
                            cmd.Parameters.AddWithValue("@trackedRotC", obj.AnimatedObjectTrackedRotC.HasValue ? (object)obj.AnimatedObjectTrackedRotC.Value : DBNull.Value);
                            cmd.Parameters.AddWithValue("@trackedRotD", obj.AnimatedObjectTrackedRotD.HasValue ? (object)obj.AnimatedObjectTrackedRotD.Value : DBNull.Value);
                            cmd.Parameters.AddWithValue("@hasTrackedRotation", obj.AnimatedObjectHasTrackedRotation ? 1 : 0);
                            cmd.Parameters.AddWithValue("@hasPositionInfo", obj.HasPositionInfo ? 1 : 0);
                            cmd.ExecuteNonQuery();
                        }
                    }

                    transaction.Commit();
                    LogManager.Info($"已保存 {objects.Count} 个碰撞对象到数据库，DetectionRecordId={detectionRecordId}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"保存ClashDetective碰撞对象失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 获取指定碰撞检测记录的所有碰撞对象
        /// </summary>
        public List<ClashDetectiveCollisionObjectRecord> GetClashDetectiveCollisionObjects(int detectionRecordId)
        {
            var results = new List<ClashDetectiveCollisionObjectRecord>();

            try
            {
                var sql = @"
                    SELECT Id, DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName,
                           AnimatedObjectTrackedPosX, AnimatedObjectTrackedPosY, AnimatedObjectTrackedPosZ, AnimatedObjectTrackedYawRadians,
                           AnimatedObjectTrackedRotA, AnimatedObjectTrackedRotB, AnimatedObjectTrackedRotC, AnimatedObjectTrackedRotD, AnimatedObjectHasTrackedRotation, HasPositionInfo
                    FROM ClashDetectiveCollisionObjects
                    WHERE DetectionRecordId = @detectionRecordId
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            results.Add(new ClashDetectiveCollisionObjectRecord
                            {
                                Id = Convert.ToInt32(reader["Id"]),
                                DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]),
                                ModelIndex = reader["ModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ModelIndex"]) : (int?)null,
                                PathId = reader["PathId"] != DBNull.Value ? reader["PathId"].ToString() : null,
                                DisplayName = reader["DisplayName"]?.ToString(),
                                ObjectName = reader["ObjectName"]?.ToString(),
                                AnimatedObjectTrackedPosX = reader["AnimatedObjectTrackedPosX"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedPosX"]) : (double?)null,
                                AnimatedObjectTrackedPosY = reader["AnimatedObjectTrackedPosY"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedPosY"]) : (double?)null,
                                AnimatedObjectTrackedPosZ = reader["AnimatedObjectTrackedPosZ"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedPosZ"]) : (double?)null,
                                AnimatedObjectTrackedYawRadians = reader["AnimatedObjectTrackedYawRadians"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedYawRadians"]) : (double?)null,
                                AnimatedObjectTrackedRotA = reader["AnimatedObjectTrackedRotA"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotA"]) : (double?)null,
                                AnimatedObjectTrackedRotB = reader["AnimatedObjectTrackedRotB"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotB"]) : (double?)null,
                                AnimatedObjectTrackedRotC = reader["AnimatedObjectTrackedRotC"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotC"]) : (double?)null,
                                AnimatedObjectTrackedRotD = reader["AnimatedObjectTrackedRotD"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotD"]) : (double?)null,
                                AnimatedObjectHasTrackedRotation = reader["AnimatedObjectHasTrackedRotation"] != DBNull.Value && Convert.ToInt32(reader["AnimatedObjectHasTrackedRotation"]) == 1,
                                HasPositionInfo = reader["HasPositionInfo"] != DBNull.Value && Convert.ToInt32(reader["HasPositionInfo"]) == 1
                            });
                        }
                    }
                }

                LogManager.Info($"[获取碰撞对象] 查询到 {results.Count} 个碰撞对象，DetectionRecordId={detectionRecordId}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取ClashDetective碰撞对象失败: {ex.Message}", ex);
            }

            return results;
        }

        /// <summary>
        /// 获取检测记录的排除对象列表
        /// </summary>
        public List<CollisionDetectionExcludedObjectRecord> GetDetectionExcludedObjects(int detectionRecordId)
        {
            var results = new List<CollisionDetectionExcludedObjectRecord>();
            
            try
            {
                var sql = @"
                    SELECT Id, DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName, Reason
                    FROM CollisionDetectionExcludedObjects
                    WHERE DetectionRecordId = @detectionRecordId
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            results.Add(new CollisionDetectionExcludedObjectRecord
                            {
                                Id = Convert.ToInt32(reader["Id"]),
                                DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]),
                                ModelIndex = reader["ModelIndex"] == DBNull.Value ? 0 : Convert.ToInt32(reader["ModelIndex"]),
                                PathId = reader["PathId"] == DBNull.Value ? null : reader["PathId"].ToString(),
                                DisplayName = reader["DisplayName"]?.ToString(),
                                ObjectName = reader["ObjectName"]?.ToString(),
                                Reason = reader["Reason"]?.ToString()
                            });
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取排除对象列表失败: {ex.Message}", ex);
            }
            
            return results;
        }

        /// <summary>
        /// 获取检测记录的手工碰撞目标列表
        /// </summary>
        public List<CollisionDetectionManualTargetRecord> GetDetectionManualTargets(int detectionRecordId)
        {
            var results = new List<CollisionDetectionManualTargetRecord>();
            
            try
            {
                var sql = @"
                    SELECT Id, DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName
                    FROM CollisionDetectionManualTargets
                    WHERE DetectionRecordId = @detectionRecordId
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            results.Add(new CollisionDetectionManualTargetRecord
                            {
                                Id = Convert.ToInt32(reader["Id"]),
                                DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]),
                                ModelIndex = reader["ModelIndex"] == DBNull.Value ? 0 : Convert.ToInt32(reader["ModelIndex"]),
                                PathId = reader["PathId"] == DBNull.Value ? null : reader["PathId"].ToString(),
                                DisplayName = reader["DisplayName"]?.ToString(),
                                ObjectName = reader["ObjectName"]?.ToString()
                            });
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取手工目标列表失败: {ex.Message}", ex);
            }
            
            return results;
        }

        #endregion

        /// <summary>
        /// 通过基础参数查询匹配的检测记录（用于检查配置是否重复）
        /// 注意：数值参数使用容差匹配，返回的记录还需要进一步比较排除列表和手工目标
        /// </summary>
        public List<CollisionDetectionRecord> FindDetectionRecordsByBaseParams(
            string routeId,
            int frameRate,
            double durationSeconds,
            double detectionTolerance,
            bool isVirtualObject,
            double? virtualObjectLength,
            double? virtualObjectWidth,
            double? virtualObjectHeight,
            double objectRotationCorrection,
            bool isManualMode)
        {
            var results = new List<CollisionDetectionRecord>();
            
            try
            {
                // 使用容差匹配浮动数值参数
                var sql = @"
                    SELECT cdr.*, pr.Name AS PathName
                    FROM CollisionDetectionRecords cdr
                    INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id
                    WHERE cdr.RouteId = @routeId
                      AND cdr.FrameRate = @frameRate
                      AND ABS(cdr.DurationSeconds - @duration) < 0.01
                      AND ABS(cdr.DetectionTolerance - @tolerance) < 0.001
                      AND cdr.IsVirtualObject = @isVirtual
                      AND ABS(cdr.ObjectRotationCorrection - @rotation) < 0.01
                      AND cdr.DetectAllObjects = @detectAll
                      AND cdr.TestName IS NOT NULL
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@routeId", routeId);
                    cmd.Parameters.AddWithValue("@frameRate", frameRate);
                    cmd.Parameters.AddWithValue("@duration", durationSeconds);
                    cmd.Parameters.AddWithValue("@tolerance", detectionTolerance);
                    cmd.Parameters.AddWithValue("@isVirtual", isVirtualObject ? 1 : 0);
                    cmd.Parameters.AddWithValue("@rotation", objectRotationCorrection);
                    cmd.Parameters.AddWithValue("@detectAll", isManualMode ? 0 : 1);

                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            var record = ReadCollisionDetectionRecordFromReader(reader);
                            
                            // 虚拟物体尺寸比较（如果是虚拟物体）
                            if (isVirtualObject && virtualObjectLength.HasValue)
                            {
                                if (!record.VirtualObjectLength.HasValue ||
                                    Math.Abs(record.VirtualObjectLength.Value - virtualObjectLength.Value) > 0.01)
                                    continue;
                                if (!record.VirtualObjectWidth.HasValue ||
                                    Math.Abs(record.VirtualObjectWidth.Value - virtualObjectWidth.Value) > 0.01)
                                    continue;
                                if (!record.VirtualObjectHeight.HasValue ||
                                    Math.Abs(record.VirtualObjectHeight.Value - virtualObjectHeight.Value) > 0.01)
                                    continue;
                            }
                            
                            results.Add(record);
                        }
                    }
                }

                LogManager.Debug($"[FindByBaseParams] 找到 {results.Count} 条基础参数匹配的记录");
            }
            catch (Exception ex)
            {
                LogManager.Error($"通过基础参数查询检测记录失败: {ex.Message}", ex);
            }

            return results;
        }

        /// <summary>
        /// 保存分析评分结果
        /// </summary>
        public void SaveAnalysisScore(string routeId, int collisionCount, double safetyScore,
                                       double efficiencyScore, string strategy)
        {
            var overallScore = (safetyScore + efficiencyScore) / 2.0;

            var sql = @"
                INSERT OR REPLACE INTO AnalysisResults
                (RouteId, CollisionCount, SafetyScore, EfficiencyScore, OverallScore, AnalysisTime, Strategy)
                VALUES (@id, @count, @safety, @eff, @overall, @time, @strategy)
            ";

            using (var cmd = new SQLiteCommand(sql, _connection))
            {
                cmd.Parameters.AddWithValue("@id", routeId);
                cmd.Parameters.AddWithValue("@count", collisionCount);
                cmd.Parameters.AddWithValue("@safety", safetyScore);
                cmd.Parameters.AddWithValue("@eff", efficiencyScore);
                cmd.Parameters.AddWithValue("@overall", overallScore);
                cmd.Parameters.AddWithValue("@time", DateTime.Now);
                cmd.Parameters.AddWithValue("@strategy", strategy ?? "默认");
                cmd.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// 获取路径的碰撞数量（CollisionResults 表已废弃，改为查询 CollisionReports 检测记录）
        /// </summary>
        public int GetCollisionCount(string routeId)
        {
            var sql = "SELECT COUNT(*) FROM CollisionReports WHERE RouteId=@id";

            using (var cmd = new SQLiteCommand(sql, _connection))
            {
                cmd.Parameters.AddWithValue("@id", routeId);
                var result = cmd.ExecuteScalar();
                return Convert.ToInt32(result);
            }
        }

        /// <summary>
        /// 获取路径的分析结果
        /// </summary>
        public PathAnalysisResult GetAnalysisResult(string routeId)
        {
            var sql = "SELECT * FROM AnalysisResults WHERE RouteId=@id";

            using (var cmd = new SQLiteCommand(sql, _connection))
            {
                cmd.Parameters.AddWithValue("@id", routeId);
                using (var reader = cmd.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        return new PathAnalysisResult
                        {
                            RouteId = reader["RouteId"].ToString(),
                            CollisionCount = Convert.ToInt32(reader["CollisionCount"]),
                            SafetyScore = Convert.ToDouble(reader["SafetyScore"]),
                            EfficiencyScore = Convert.ToDouble(reader["EfficiencyScore"]),
                            OverallScore = Convert.ToDouble(reader["OverallScore"]),
                            AnalysisTime = Convert.ToDateTime(reader["AnalysisTime"]),
                            Strategy = reader["Strategy"].ToString()
                        };
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// 获取所有路径的分析结果
        /// </summary>
        public List<PathAnalysisResult> GetAllAnalysisResults()
        {
            var results = new List<PathAnalysisResult>();
            var sql = "SELECT * FROM AnalysisResults ORDER BY OverallScore DESC";

            using (var cmd = new SQLiteCommand(sql, _connection))
            using (var reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    results.Add(new PathAnalysisResult
                    {
                        RouteId = reader["RouteId"].ToString(),
                        CollisionCount = Convert.ToInt32(reader["CollisionCount"]),
                        SafetyScore = Convert.ToDouble(reader["SafetyScore"]),
                        EfficiencyScore = Convert.ToDouble(reader["EfficiencyScore"]),
                        OverallScore = Convert.ToDouble(reader["OverallScore"]),
                        AnalysisTime = Convert.ToDateTime(reader["AnalysisTime"]),
                        Strategy = reader["Strategy"].ToString()
                    });
                }
            }

            return results;
        }

        /// <summary>
        /// 删除路径及其相关数据
        /// </summary>
        /// <param name="routeId">路径ID</param>
        public void DeletePathRoute(string routeId)
        {
            try
            {
                // 由于设置了外键级联删除，删除路径时会自动删除相关的碰撞结果和分析结果
                var sql = "DELETE FROM PathRoutes WHERE Id = @id";
                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@id", routeId);
                    int affected = cmd.ExecuteNonQuery();
                    LogManager.Info($"删除路径记录: ID={routeId}, 影响行数={affected}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"删除路径失败: ID={routeId}, {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 获取所有路径基本信息
        /// </summary>
        public List<PathRoute> GetAllPathRoutes()
        {
            var routes = new List<PathRoute>();
            var sql = @"SELECT pr.*, ar.CollisionCount, ar.SafetyScore, ar.EfficiencyScore, ar.OverallScore
                        FROM PathRoutes pr
                        LEFT JOIN AnalysisResults ar ON pr.Id = ar.RouteId
                        ORDER BY pr.LastModified DESC";

            using (var cmd = new SQLiteCommand(sql, _connection))
            using (var reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    // 🔥 不使用默认值，数据缺失时抛出异常，避免隐藏bug
                    // 注意：TotalLength 字段已废弃，不再从数据库加载（由计算属性实时提供）
                    var route = new PathRoute
                    {
                        Id = reader["Id"].ToString(),
                        Name = reader["Name"].ToString(),
                        // TotalLength：不再从数据库加载，由 PathRoute.TotalLength 计算属性实时提供
                        EstimatedTime = Convert.ToDouble(reader["EstimatedTime"]),
                        TurnRadius = Convert.ToDouble(reader["TurnRadius"]),
                        IsCurved = Convert.ToInt32(reader["IsCurved"]) == 1,
                        MaxObjectLength = Convert.ToDouble(reader["MaxObjectLength"]),
                        MaxObjectWidth = Convert.ToDouble(reader["MaxObjectWidth"]),
                        MaxObjectHeight = Convert.ToDouble(reader["MaxObjectHeight"]),
                        SafetyMargin = Convert.ToDouble(reader["SafetyMargin"]),
                        GridSize = Convert.ToDouble(reader["GridSize"]),
                        PathType = (PathType)Convert.ToInt32(reader["PathType"]),
                        LiftHeight = Convert.ToDouble(reader["LiftHeight"]),
                        RailMountMode = (RailMountMode)Convert.ToInt32(reader["RailMountMode"]),
                        RailPathDefinitionMode = (RailPathDefinitionMode)Convert.ToInt32(reader["RailPathDefinitionMode"]),
                        RailNormalOffset = reader["RailNormalOffset"] != DBNull.Value ? Convert.ToDouble(reader["RailNormalOffset"]) : 0.0,
                        IsTwoPointVertical = reader["IsTwoPointVertical"] != DBNull.Value && Convert.ToInt32(reader["IsTwoPointVertical"]) == 1,
                        CreatedTime = Convert.ToDateTime(reader["CreatedTime"]),
                        LastModified = Convert.ToDateTime(reader["LastModified"])
                    };

                    if (!reader.IsDBNull(reader.GetOrdinal("RailPreferredNormalX")) &&
                        !reader.IsDBNull(reader.GetOrdinal("RailPreferredNormalY")) &&
                        !reader.IsDBNull(reader.GetOrdinal("RailPreferredNormalZ")))
                    {
                        route.RailPreferredNormal = new Point3D(
                            Convert.ToDouble(reader["RailPreferredNormalX"]),
                            Convert.ToDouble(reader["RailPreferredNormalY"]),
                            Convert.ToDouble(reader["RailPreferredNormalZ"]));
                    }

                    // 添加分析结果（如果存在）
                    if (!reader.IsDBNull(reader.GetOrdinal("CollisionCount")))
                    {
                        route.CollisionCount = Convert.ToInt32(reader["CollisionCount"]);
                        route.SafetyScore = Convert.ToDouble(reader["SafetyScore"]);
                        route.EfficiencyScore = Convert.ToDouble(reader["EfficiencyScore"]);
                        route.OverallScore = Convert.ToDouble(reader["OverallScore"]);
                    }

                    // 迁移：旧 IsTwoPointVertical 吊装路径 → 自由路径（方案A）
                    // 旧 2 点路径的点坐标直接作为自由路径的物体几何中心
                    if (route.PathType == PathType.Hoisting && route.IsTwoPointVertical)
                    {
                        LogManager.Info($"[迁移] 旧2点吊装路径 {route.Name} → 转换为自由路径");
                        route.PathType = PathType.Free;
                        route.IsTwoPointVertical = false;
                    }

                    routes.Add(route);
                }
            }

            // 为每个路径加载路径点和路径边
            foreach (var route in routes)
            {
                LoadPathPoints(route);
                LoadPathEdges(route);
            }

            LogManager.Info($"从数据库加载了 {routes.Count} 条路径记录");
            return routes;
        }

        /// <summary>
        /// 加载指定路径的路径点
        /// </summary>
        private void LoadPathPoints(PathRoute route)
        {
            var sql = @"SELECT * FROM PathPoints
                        WHERE RouteId=@routeId
                        ORDER BY SequenceNumber";

            using (var cmd = new SQLiteCommand(sql, _connection))
            {
                cmd.Parameters.AddWithValue("@routeId", route.Id);
                using (var reader = cmd.ExecuteReader())
                {
                    int index = 0;
                    while (reader.Read())
                    {
                        var point = new PathPoint
                        {
                            Id = reader["Id"].ToString(),
                            Name = reader["Name"].ToString(),
                            Position = new Autodesk.Navisworks.Api.Point3D(
                                Convert.ToDouble(reader["X"]),
                                Convert.ToDouble(reader["Y"]),
                                Convert.ToDouble(reader["Z"])
                            ),
                            Type = (PathPointType)Convert.ToInt32(reader["Type"]),
                            Index = index++
                        };

                        // 加载自定义转向半径
                        if (!reader.IsDBNull(reader.GetOrdinal("CustomTurnRadius")))
                        {
                            point.CustomTurnRadius = Convert.ToDouble(reader["CustomTurnRadius"]);
                        }

                        route.Points.Add(point);
                    }
                    
                    LogManager.Debug($"[LoadPathPoints] 路径 {route.Name} (Id={route.Id}) 加载了 {index} 个路径点");
                }
            }
        }

        /// <summary>
        /// 保存路径边
        /// </summary>
        private void SavePathEdge(string routeId, PathEdge edge, int sequenceNumber)
        {
            var sql = @"
                INSERT INTO PathEdges
                (Id, RouteId, StartPointId, EndPointId, SegmentType, PhysicalLength, SequenceNumber,
                 Ts_X, Ts_Y, Ts_Z, Te_X, Te_Y, Te_Z,
                 ArcCenter_X, ArcCenter_Y, ArcCenter_Z,
                 RequestedRadius, ActualRadius, DeflectionAngle, ArcLength)
                VALUES (@id, @routeId, @startId, @endId, @segType, @length, @seqNum,
                        @tsx, @tsy, @tsz, @tex, @tey, @tez,
                        @acx, @acy, @acz,
                        @reqR, @actR, @angle, @arcLen)
            ";

            using (var cmd = new SQLiteCommand(sql, _connection))
            {
                cmd.Parameters.AddWithValue("@id", edge.Id);
                cmd.Parameters.AddWithValue("@routeId", routeId);
                cmd.Parameters.AddWithValue("@startId", edge.StartPointId ?? "");
                cmd.Parameters.AddWithValue("@endId", edge.EndPointId ?? "");
                cmd.Parameters.AddWithValue("@segType", (int)edge.SegmentType);
                cmd.Parameters.AddWithValue("@length", edge.PhysicalLength);
                cmd.Parameters.AddWithValue("@seqNum", sequenceNumber);

                // 直线段：保存起止点坐标
                if (edge.SegmentType == PathSegmentType.Straight && edge.SampledPoints != null && edge.SampledPoints.Count >= 2)
                {
                    cmd.Parameters.AddWithValue("@tsx", edge.SampledPoints.First().X);
                    cmd.Parameters.AddWithValue("@tsy", edge.SampledPoints.First().Y);
                    cmd.Parameters.AddWithValue("@tsz", edge.SampledPoints.First().Z);
                    cmd.Parameters.AddWithValue("@tex", edge.SampledPoints.Last().X);
                    cmd.Parameters.AddWithValue("@tey", edge.SampledPoints.Last().Y);
                    cmd.Parameters.AddWithValue("@tez", edge.SampledPoints.Last().Z);

                    // 圆弧参数为NULL
                    cmd.Parameters.AddWithValue("@acx", DBNull.Value);
                    cmd.Parameters.AddWithValue("@acy", DBNull.Value);
                    cmd.Parameters.AddWithValue("@acz", DBNull.Value);
                    cmd.Parameters.AddWithValue("@reqR", DBNull.Value);
                    cmd.Parameters.AddWithValue("@actR", DBNull.Value);
                    cmd.Parameters.AddWithValue("@angle", DBNull.Value);
                    cmd.Parameters.AddWithValue("@arcLen", DBNull.Value);
                }
                else if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null)
                {
                    // 圆弧段：保存圆弧轨迹数据
                    cmd.Parameters.AddWithValue("@tsx", edge.Trajectory.Ts.X);
                    cmd.Parameters.AddWithValue("@tsy", edge.Trajectory.Ts.Y);
                    cmd.Parameters.AddWithValue("@tsz", edge.Trajectory.Ts.Z);
                    cmd.Parameters.AddWithValue("@tex", edge.Trajectory.Te.X);
                    cmd.Parameters.AddWithValue("@tey", edge.Trajectory.Te.Y);
                    cmd.Parameters.AddWithValue("@tez", edge.Trajectory.Te.Z);
                    cmd.Parameters.AddWithValue("@acx", edge.Trajectory.ArcCenter.X);
                    cmd.Parameters.AddWithValue("@acy", edge.Trajectory.ArcCenter.Y);
                    cmd.Parameters.AddWithValue("@acz", edge.Trajectory.ArcCenter.Z);
                    cmd.Parameters.AddWithValue("@reqR", edge.Trajectory.RequestedRadius);
                    cmd.Parameters.AddWithValue("@actR", edge.Trajectory.ActualRadius);
                    cmd.Parameters.AddWithValue("@angle", edge.Trajectory.DeflectionAngle);
                    cmd.Parameters.AddWithValue("@arcLen", edge.Trajectory.ArcLength);
                }
                else
                {
                    // 其他情况，所有参数为NULL
                    cmd.Parameters.AddWithValue("@tsx", DBNull.Value);
                    cmd.Parameters.AddWithValue("@tsy", DBNull.Value);
                    cmd.Parameters.AddWithValue("@tsz", DBNull.Value);
                    cmd.Parameters.AddWithValue("@tex", DBNull.Value);
                    cmd.Parameters.AddWithValue("@tey", DBNull.Value);
                    cmd.Parameters.AddWithValue("@tez", DBNull.Value);
                    cmd.Parameters.AddWithValue("@acx", DBNull.Value);
                    cmd.Parameters.AddWithValue("@acy", DBNull.Value);
                    cmd.Parameters.AddWithValue("@acz", DBNull.Value);
                    cmd.Parameters.AddWithValue("@reqR", DBNull.Value);
                    cmd.Parameters.AddWithValue("@actR", DBNull.Value);
                    cmd.Parameters.AddWithValue("@angle", DBNull.Value);
                    cmd.Parameters.AddWithValue("@arcLen", DBNull.Value);
                }

                cmd.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// 删除路径点
        /// </summary>
        private void DeletePathPoints(string routeId)
        {
            using (var cmd = new SQLiteCommand("DELETE FROM PathPoints WHERE RouteId = @routeId", _connection))
            {
                cmd.Parameters.AddWithValue("@routeId", routeId);
                cmd.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// 删除路径边
        /// </summary>
        private void DeletePathEdges(string routeId)
        {
            using (var cmd = new SQLiteCommand("DELETE FROM PathEdges WHERE RouteId = @routeId", _connection))
            {
                cmd.Parameters.AddWithValue("@routeId", routeId);
                cmd.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// 加载路径边
        /// </summary>
        private void LoadPathEdges(PathRoute route)
        {
            var sql = "SELECT * FROM PathEdges WHERE RouteId = @routeId ORDER BY SequenceNumber";

            using (var cmd = new SQLiteCommand(sql, _connection))
            {
                cmd.Parameters.AddWithValue("@routeId", route.Id);

                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var edge = new PathEdge
                        {
                            Id = reader["Id"].ToString(),
                            StartPointId = reader["StartPointId"].ToString(),
                            EndPointId = reader["EndPointId"].ToString(),
                            SegmentType = (PathSegmentType)Convert.ToInt32(reader["SegmentType"]),
                            PhysicalLength = Convert.ToDouble(reader["PhysicalLength"])
                        };

                        // 加载圆弧轨迹数据
                        if (edge.SegmentType == PathSegmentType.Arc)
                        {
                            edge.Trajectory = new ArcTrajectory
                            {
                                Ts = new Point3D(
                                    Convert.ToDouble(reader["Ts_X"]),
                                    Convert.ToDouble(reader["Ts_Y"]),
                                    Convert.ToDouble(reader["Ts_Z"])),
                                Te = new Point3D(
                                    Convert.ToDouble(reader["Te_X"]),
                                    Convert.ToDouble(reader["Te_Y"]),
                                    Convert.ToDouble(reader["Te_Z"])),
                                ArcCenter = new Point3D(
                                    Convert.ToDouble(reader["ArcCenter_X"]),
                                    Convert.ToDouble(reader["ArcCenter_Y"]),
                                    Convert.ToDouble(reader["ArcCenter_Z"])),
                                RequestedRadius = Convert.ToDouble(reader["RequestedRadius"]),
                                ActualRadius = Convert.ToDouble(reader["ActualRadius"]),
                                DeflectionAngle = Convert.ToDouble(reader["DeflectionAngle"]),
                                ArcLength = Convert.ToDouble(reader["ArcLength"])
                            };
                        }
                        else if (edge.SegmentType == PathSegmentType.Straight)
                        {
                            // 直线段也加载Ts和Te（用于重新计算SampledPoints）
                            if (!reader.IsDBNull(reader.GetOrdinal("Ts_X")) &&
                                !reader.IsDBNull(reader.GetOrdinal("Te_X")))
                            {
                                edge.Trajectory = new ArcTrajectory
                                {
                                    Ts = new Point3D(
                                        Convert.ToDouble(reader["Ts_X"]),
                                        Convert.ToDouble(reader["Ts_Y"]),
                                        Convert.ToDouble(reader["Ts_Z"])),
                                    Te = new Point3D(
                                        Convert.ToDouble(reader["Te_X"]),
                                        Convert.ToDouble(reader["Te_Y"]),
                                        Convert.ToDouble(reader["Te_Z"])),
                                    ArcCenter = new Point3D(0, 0, 0),  // 直线段没有圆心
                                    RequestedRadius = 0,
                                    ActualRadius = 0,
                                    DeflectionAngle = 0,
                                    ArcLength = 0
                                };
                            }
                        }

                        route.Edges.Add(edge);
                    }
                }
            }

            // 注意：TotalLength 是计算属性，自动从 Edges/Points 实时计算，无需手动设置

            // 重新计算SampledPoints（因为数据库中没有保存采样点）
            if (route.Edges.Count > 0)
            {
                double samplingStep = ConfigManager.Instance.Current.PathEditing.ArcSamplingStep;
                foreach (var edge in route.Edges)
                {
                    if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null)
                    {
                        // 圆弧段：使用PathCurveEngine重新采样
                        edge.SampledPoints = PathCurveEngine.SampleArc(edge.Trajectory, samplingStep);
                    }
                    else if (edge.SegmentType == PathSegmentType.Straight)
                    {
                        // 直线段：从数据库加载的Ts和Te重新采样
                        Point3D startPoint = null;
                        Point3D endPoint = null;

                        // 尝试从Trajectory中获取（直线段也可能用Trajectory存储起止点）
                        if (edge.Trajectory != null)
                        {
                            startPoint = edge.Trajectory.Ts;
                            endPoint = edge.Trajectory.Te;
                        }

                        // 如果找到了起止点，重新采样
                        if (startPoint != null && endPoint != null)
                        {
                            int sampleCount = Math.Max(2, (int)Math.Ceiling(edge.PhysicalLength / samplingStep));
                            edge.SampledPoints = new List<Point3D>();
                            for (int i = 0; i <= sampleCount; i++)
                            {
                                double t = i / (double)sampleCount;
                                Point3D sampledPoint = startPoint + (endPoint - startPoint) * t;
                                edge.SampledPoints.Add(sampledPoint);
                            }
                        }
                    }
                }
            }
        }

        #region 排除列表管理

        /// <summary>
        /// 保存排除对象
        /// </summary>
        public int SaveExcludedObject(ExcludedObjectRecord record)
        {
            try
            {
                // 检查是否已存在（根据PathId和RouteId）
                var checkSql = @"
                    SELECT Id FROM ExcludedObjects 
                    WHERE PathId = @pathId AND (RouteId = @routeId OR (RouteId IS NULL AND @routeId IS NULL))
                ";
                using (var checkCmd = new SQLiteCommand(checkSql, _connection))
                {
                    checkCmd.Parameters.AddWithValue("@pathId", record.PathId);
                    checkCmd.Parameters.AddWithValue("@routeId", string.IsNullOrEmpty(record.RouteId) ? (object)DBNull.Value : record.RouteId);
                    var existingId = checkCmd.ExecuteScalar();
                    if (existingId != null)
                    {
                        // 已存在，更新记录
                        var updateSql = @"
                            UPDATE ExcludedObjects 
                            SET DisplayName = @displayName, ObjectName = @objectName, 
                                Reason = @reason, ExcludedTime = @excludedTime
                            WHERE Id = @id
                        ";
                        using (var updateCmd = new SQLiteCommand(updateSql, _connection))
                        {
                            updateCmd.Parameters.AddWithValue("@id", Convert.ToInt32(existingId));
                            updateCmd.Parameters.AddWithValue("@displayName", record.DisplayName ?? "");
                            updateCmd.Parameters.AddWithValue("@objectName", record.ObjectName ?? "");
                            updateCmd.Parameters.AddWithValue("@reason", record.Reason ?? "");
                            updateCmd.Parameters.AddWithValue("@excludedTime", record.ExcludedTime);
                            updateCmd.ExecuteNonQuery();
                        }
                        return Convert.ToInt32(existingId);
                    }
                }

                // 插入新记录
                var sql = @"
                    INSERT INTO ExcludedObjects 
                    (RouteId, ModelIndex, PathId, DisplayName, ObjectName, ExcludedTime, Reason, IsGlobal)
                    VALUES (@routeId, @modelIndex, @pathId, @displayName, @objectName, @excludedTime, @reason, @isGlobal)
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@routeId", string.IsNullOrEmpty(record.RouteId) ? (object)DBNull.Value : record.RouteId);
                    cmd.Parameters.AddWithValue("@modelIndex", record.ModelIndex);
                    cmd.Parameters.AddWithValue("@pathId", record.PathId);
                    cmd.Parameters.AddWithValue("@displayName", record.DisplayName ?? "");
                    cmd.Parameters.AddWithValue("@objectName", record.ObjectName ?? "");
                    cmd.Parameters.AddWithValue("@excludedTime", record.ExcludedTime);
                    cmd.Parameters.AddWithValue("@reason", record.Reason ?? "");
                    cmd.Parameters.AddWithValue("@isGlobal", record.IsGlobal ? 1 : 0);
                    cmd.ExecuteNonQuery();
                }

                int newId = (int)_connection.LastInsertRowId;
                LogManager.Debug($"排除对象已保存: Id={newId}, PathId={record.PathId}, Name={record.DisplayName}");
                return newId;
            }
            catch (Exception ex)
            {
                LogManager.Error($"保存排除对象失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 批量保存排除对象
        /// </summary>
        public void SaveExcludedObjects(List<ExcludedObjectRecord> records)
        {
            if (records == null || records.Count == 0) return;

            try
            {
                using (var transaction = _connection.BeginTransaction())
                {
                    foreach (var record in records)
                    {
                        SaveExcludedObject(record);
                    }
                    transaction.Commit();
                }
                LogManager.Info($"批量保存 {records.Count} 个排除对象完成");
            }
            catch (Exception ex)
            {
                LogManager.Error($"批量保存排除对象失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 获取路径的排除对象列表
        /// </summary>
        public List<ExcludedObjectRecord> GetExcludedObjects(string routeId = null, bool includeGlobal = true)
        {
            var results = new List<ExcludedObjectRecord>();

            try
            {
                string sql;
                if (string.IsNullOrEmpty(routeId))
                {
                    // 获取全局排除对象
                    sql = "SELECT * FROM ExcludedObjects WHERE IsGlobal = 1 ORDER BY ExcludedTime DESC";
                }
                else if (includeGlobal)
                {
                    // 获取指定路径的排除对象 + 全局排除对象
                    sql = @"
                        SELECT * FROM ExcludedObjects 
                        WHERE RouteId = @routeId OR IsGlobal = 1 
                        ORDER BY ExcludedTime DESC
                    ";
                }
                else
                {
                    // 仅获取指定路径的排除对象
                    sql = "SELECT * FROM ExcludedObjects WHERE RouteId = @routeId ORDER BY ExcludedTime DESC";
                }

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    if (!string.IsNullOrEmpty(routeId))
                    {
                        cmd.Parameters.AddWithValue("@routeId", routeId);
                    }

                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            results.Add(new ExcludedObjectRecord
                            {
                                Id = Convert.ToInt32(reader["Id"]),
                                RouteId = reader["RouteId"]?.ToString(),
                                ModelIndex = Convert.ToInt32(reader["ModelIndex"]),
                                PathId = reader["PathId"].ToString(),
                                DisplayName = reader["DisplayName"]?.ToString(),
                                ObjectName = reader["ObjectName"]?.ToString(),
                                ExcludedTime = Convert.ToDateTime(reader["ExcludedTime"]),
                                Reason = reader["Reason"]?.ToString(),
                                IsGlobal = Convert.ToInt32(reader["IsGlobal"]) == 1
                            });
                        }
                    }
                }

                LogManager.Debug($"获取到 {results.Count} 个排除对象");
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取排除对象失败: {ex.Message}", ex);
            }

            return results;
        }

        /// <summary>
        /// 删除排除对象
        /// </summary>
        public void DeleteExcludedObject(int id)
        {
            try
            {
                var sql = "DELETE FROM ExcludedObjects WHERE Id = @id";
                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@id", id);
                    int affected = cmd.ExecuteNonQuery();
                    LogManager.Debug($"删除排除对象: Id={id}, 影响行数={affected}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"删除排除对象失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 根据PathId删除排除对象
        /// </summary>
        public void DeleteExcludedObjectByPathId(string pathId, string routeId = null)
        {
            try
            {
                string sql;
                if (string.IsNullOrEmpty(routeId))
                {
                    sql = "DELETE FROM ExcludedObjects WHERE PathId = @pathId";
                }
                else
                {
                    sql = "DELETE FROM ExcludedObjects WHERE PathId = @pathId AND RouteId = @routeId";
                }

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@pathId", pathId);
                    if (!string.IsNullOrEmpty(routeId))
                    {
                        cmd.Parameters.AddWithValue("@routeId", routeId);
                    }
                    int affected = cmd.ExecuteNonQuery();
                    LogManager.Debug($"删除排除对象: PathId={pathId}, RouteId={routeId}, 影响行数={affected}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"删除排除对象失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 清除路径的所有排除对象
        /// </summary>
        public void ClearExcludedObjects(string routeId = null)
        {
            try
            {
                string sql;
                if (string.IsNullOrEmpty(routeId))
                {
                    sql = "DELETE FROM ExcludedObjects";
                }
                else
                {
                    sql = "DELETE FROM ExcludedObjects WHERE RouteId = @routeId";
                }

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    if (!string.IsNullOrEmpty(routeId))
                    {
                        cmd.Parameters.AddWithValue("@routeId", routeId);
                    }
                    int affected = cmd.ExecuteNonQuery();
                    LogManager.Info($"清除排除对象: RouteId={routeId}, 影响行数={affected}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"清除排除对象失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 关联排除对象到碰撞报告
        /// </summary>
        public void LinkExcludedObjectsToCollisionReport(int reportId, List<int> excludedObjectIds)
        {
            if (excludedObjectIds == null || excludedObjectIds.Count == 0) return;

            try
            {
                using (var transaction = _connection.BeginTransaction())
                {
                    var sql = @"
                        INSERT OR IGNORE INTO CollisionReportExcludedObjects (ReportId, ExcludedObjectId)
                        VALUES (@reportId, @excludedObjectId)
                    ";

                    foreach (var objectId in excludedObjectIds)
                    {
                        using (var cmd = new SQLiteCommand(sql, _connection))
                        {
                            cmd.Parameters.AddWithValue("@reportId", reportId);
                            cmd.Parameters.AddWithValue("@excludedObjectId", objectId);
                            cmd.ExecuteNonQuery();
                        }
                    }

                    transaction.Commit();
                    LogManager.Debug($"关联 {excludedObjectIds.Count} 个排除对象到碰撞报告: ReportId={reportId}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"关联排除对象到碰撞报告失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 获取碰撞报告的排除对象
        /// </summary>
        public List<ExcludedObjectRecord> GetExcludedObjectsForCollisionReport(int reportId)
        {
            var results = new List<ExcludedObjectRecord>();

            try
            {
                var sql = @"
                    SELECT eo.* FROM ExcludedObjects eo
                    INNER JOIN CollisionReportExcludedObjects creo ON eo.Id = creo.ExcludedObjectId
                    WHERE creo.ReportId = @reportId
                    ORDER BY eo.ExcludedTime DESC
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@reportId", reportId);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            results.Add(new ExcludedObjectRecord
                            {
                                Id = Convert.ToInt32(reader["Id"]),
                                RouteId = reader["RouteId"]?.ToString(),
                                ModelIndex = Convert.ToInt32(reader["ModelIndex"]),
                                PathId = reader["PathId"].ToString(),
                                DisplayName = reader["DisplayName"]?.ToString(),
                                ObjectName = reader["ObjectName"]?.ToString(),
                                ExcludedTime = Convert.ToDateTime(reader["ExcludedTime"]),
                                Reason = reader["Reason"]?.ToString(),
                                IsGlobal = Convert.ToInt32(reader["IsGlobal"]) == 1
                            });
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取碰撞报告的排除对象失败: {ex.Message}", ex);
            }

            return results;
        }

        /// <summary>
        /// 关联排除对象到碰撞检测记录
        /// </summary>
        public void LinkExcludedObjectsToDetectionRecord(int detectionRecordId, List<int> excludedObjectIds)
        {
            if (excludedObjectIds == null || excludedObjectIds.Count == 0) return;

            try
            {
                using (var transaction = _connection.BeginTransaction())
                {
                    var sql = @"
                        INSERT OR IGNORE INTO ClashDetectiveExcludedObjects (DetectionRecordId, ExcludedObjectId)
                        VALUES (@detectionRecordId, @excludedObjectId)
                    ";

                    foreach (var objectId in excludedObjectIds)
                    {
                        using (var cmd = new SQLiteCommand(sql, _connection))
                        {
                            cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId);
                            cmd.Parameters.AddWithValue("@excludedObjectId", objectId);
                            cmd.ExecuteNonQuery();
                        }
                    }

                    transaction.Commit();
                    LogManager.Debug($"关联 {excludedObjectIds.Count} 个排除对象到检测记录: DetectionRecordId={detectionRecordId}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"关联排除对象到检测记录失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 获取检测记录的排除对象
        /// </summary>
        public List<ExcludedObjectRecord> GetExcludedObjectsForDetectionRecord(int detectionRecordId)
        {
            var results = new List<ExcludedObjectRecord>();

            try
            {
                var sql = @"
                    SELECT eo.* FROM ExcludedObjects eo
                    INNER JOIN ClashDetectiveExcludedObjects cdeo ON eo.Id = cdeo.ExcludedObjectId
                    WHERE cdeo.DetectionRecordId = @detectionRecordId
                    ORDER BY eo.ExcludedTime DESC
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@detectionRecordId", detectionRecordId);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            results.Add(new ExcludedObjectRecord
                            {
                                Id = Convert.ToInt32(reader["Id"]),
                                RouteId = reader["RouteId"]?.ToString(),
                                ModelIndex = Convert.ToInt32(reader["ModelIndex"]),
                                PathId = reader["PathId"].ToString(),
                                DisplayName = reader["DisplayName"]?.ToString(),
                                ObjectName = reader["ObjectName"]?.ToString(),
                                ExcludedTime = Convert.ToDateTime(reader["ExcludedTime"]),
                                Reason = reader["Reason"]?.ToString(),
                                IsGlobal = Convert.ToInt32(reader["IsGlobal"]) == 1
                            });
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取ClashDetective结果的排除对象失败: {ex.Message}", ex);
            }

            return results;
        }

        #endregion

        #region 碰撞检测记录

        /// <summary>
        /// 保存碰撞检测记录
        /// 每次点击"生成动画"创建一条记录
        /// </summary>
        public int SaveCollisionDetectionRecord(CollisionDetectionRecord record)
        {
            try
            {
                var sql = @"
                    INSERT INTO CollisionDetectionRecords (
                        RouteId, CreatedTime, FrameRate, DurationSeconds, DetectionTolerance,
                        IsVirtualObject, AnimatedObjectName, ObjectModelIndex, ObjectPathId,
                        VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight,
                        DetectAllObjects, ObjectRotationCorrection, Description,
                        TestName, TestTime, CollisionCount, AnimationCollisionCount
                    )
                    VALUES (
                        @RouteId, @CreatedTime, @FrameRate, @DurationSeconds, @DetectionTolerance,
                        @IsVirtualObject, @AnimatedObjectName, @ObjectModelIndex, @ObjectPathId,
                        @VirtualObjectLength, @VirtualObjectWidth, @VirtualObjectHeight,
                        @DetectAllObjects, @ObjectRotationCorrection, @Description,
                        @TestName, @TestTime, @CollisionCount, @AnimationCollisionCount
                    );
                    SELECT last_insert_rowid();
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@RouteId", record.RouteId);
                    cmd.Parameters.AddWithValue("@CreatedTime", record.CreatedTime);
                    cmd.Parameters.AddWithValue("@FrameRate", record.FrameRate);
                    cmd.Parameters.AddWithValue("@DurationSeconds", record.DurationSeconds);
                    // 内部使用模型单位，写入数据库时转换为米单位
                    cmd.Parameters.AddWithValue("@DetectionTolerance", record.DetectionTolerance); // 模型单位，不转换
                    cmd.Parameters.AddWithValue("@IsVirtualObject", record.IsVirtualObject ? 1 : 0);
                    cmd.Parameters.AddWithValue("@AnimatedObjectName", record.AnimatedObjectName ?? "");
                    cmd.Parameters.AddWithValue("@ObjectModelIndex", record.ObjectModelIndex ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@ObjectPathId", record.ObjectPathId ?? "");
                    cmd.Parameters.AddWithValue("@VirtualObjectLength", record.VirtualObjectLength ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@VirtualObjectWidth", record.VirtualObjectWidth ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@VirtualObjectHeight", record.VirtualObjectHeight ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@DetectAllObjects", record.DetectAllObjects ? 1 : 0);
                    cmd.Parameters.AddWithValue("@ObjectRotationCorrection", record.ObjectRotationCorrection);
                    cmd.Parameters.AddWithValue("@Description", record.Description ?? "");
                    cmd.Parameters.AddWithValue("@TestName", record.TestName ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@TestTime", record.TestTime ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@CollisionCount", record.CollisionCount);
                    cmd.Parameters.AddWithValue("@AnimationCollisionCount", record.AnimationCollisionCount);

                    var id = Convert.ToInt32(cmd.ExecuteScalar());
                    LogManager.Info($"保存碰撞检测记录: Id={id}, RouteId={record.RouteId}");
                    return id;
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"保存碰撞检测记录失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 更新碰撞检测结果到检测记录
        /// 在ClashDetective测试完成后调用，保存测试结果
        /// </summary>
        public void UpdateCollisionDetectionResult(int detectionRecordId, string testName, int collisionCount, int animationCollisionCount)
        {
            try
            {
                var sql = @"
                    UPDATE CollisionDetectionRecords
                    SET TestName = @TestName,
                        TestTime = @TestTime,
                        CollisionCount = @CollisionCount,
                        AnimationCollisionCount = @AnimationCollisionCount
                    WHERE Id = @Id
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@Id", detectionRecordId);
                    cmd.Parameters.AddWithValue("@TestName", testName ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@TestTime", DateTime.Now);
                    cmd.Parameters.AddWithValue("@CollisionCount", collisionCount);
                    cmd.Parameters.AddWithValue("@AnimationCollisionCount", animationCollisionCount);
                    cmd.ExecuteNonQuery();
                }

                LogManager.Info($"更新碰撞检测结果: DetectionRecordId={detectionRecordId}, TestName={testName}, CollisionCount={collisionCount}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"更新碰撞检测结果失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 保存碰撞检测记录的排除对象
        /// </summary>
        public void SaveCollisionDetectionExcludedObjects(int detectionRecordId, List<CollisionDetectionExcludedObjectRecord> objects)
        {
            if (objects == null || objects.Count == 0) return;

            try
            {
                using (var transaction = _connection.BeginTransaction())
                {
                    var sql = @"
                        INSERT INTO CollisionDetectionExcludedObjects 
                        (DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName, Reason)
                        VALUES (@DetectionRecordId, @ModelIndex, @PathId, @DisplayName, @ObjectName, @Reason)
                    ";

                    foreach (var obj in objects)
                    {
                        using (var cmd = new SQLiteCommand(sql, _connection))
                        {
                            cmd.Parameters.AddWithValue("@DetectionRecordId", detectionRecordId);
                            cmd.Parameters.AddWithValue("@ModelIndex", obj.ModelIndex);
                            cmd.Parameters.AddWithValue("@PathId", obj.PathId);
                            cmd.Parameters.AddWithValue("@DisplayName", obj.DisplayName ?? "");
                            cmd.Parameters.AddWithValue("@ObjectName", obj.ObjectName ?? "");
                            cmd.Parameters.AddWithValue("@Reason", obj.Reason ?? "");
                            cmd.ExecuteNonQuery();
                        }
                    }

                    transaction.Commit();
                    LogManager.Info($"保存检测记录排除对象: DetectionRecordId={detectionRecordId}, 数量={objects.Count}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"保存检测记录排除对象失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 保存碰撞检测记录的手工目标
        /// </summary>
        public void SaveCollisionDetectionManualTargets(int detectionRecordId, List<CollisionDetectionManualTargetRecord> targets)
        {
            if (targets == null || targets.Count == 0) return;

            try
            {
                using (var transaction = _connection.BeginTransaction())
                {
                    var sql = @"
                        INSERT INTO CollisionDetectionManualTargets 
                        (DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName)
                        VALUES (@DetectionRecordId, @ModelIndex, @PathId, @DisplayName, @ObjectName)
                    ";

                    foreach (var target in targets)
                    {
                        using (var cmd = new SQLiteCommand(sql, _connection))
                        {
                            cmd.Parameters.AddWithValue("@DetectionRecordId", detectionRecordId);
                            cmd.Parameters.AddWithValue("@ModelIndex", target.ModelIndex);
                            cmd.Parameters.AddWithValue("@PathId", target.PathId);
                            cmd.Parameters.AddWithValue("@DisplayName", target.DisplayName ?? "");
                            cmd.Parameters.AddWithValue("@ObjectName", target.ObjectName ?? "");
                            cmd.ExecuteNonQuery();
                        }
                    }

                    transaction.Commit();
                    LogManager.Info($"保存检测记录手工目标: DetectionRecordId={detectionRecordId}, 数量={targets.Count}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"保存检测记录手工目标失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 获取碰撞检测记录
        /// </summary>
        public CollisionDetectionRecord GetCollisionDetectionRecord(int id)
        {
            try
            {
                var sql = @"
                    SELECT * FROM CollisionDetectionRecords WHERE Id = @Id
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@Id", id);
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            return ReadCollisionDetectionRecordFromReader(reader);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取碰撞检测记录失败: {ex.Message}", ex);
            }
            return null;
        }

        /// <summary>
        /// 获取路径的最新碰撞检测记录
        /// </summary>
        public CollisionDetectionRecord GetLatestCollisionDetectionRecord(string routeId)
        {
            try
            {
                var sql = @"
                    SELECT * FROM CollisionDetectionRecords 
                    WHERE RouteId = @RouteId 
                    ORDER BY CreatedTime DESC 
                    LIMIT 1
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@RouteId", routeId);
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            return ReadCollisionDetectionRecordFromReader(reader);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取最新碰撞检测记录失败: {ex.Message}", ex);
            }
            return null;
        }

        /// <summary>
        /// 获取碰撞检测记录的排除对象
        /// </summary>
        public List<CollisionDetectionExcludedObjectRecord> GetCollisionDetectionExcludedObjects(int detectionRecordId)
        {
            var results = new List<CollisionDetectionExcludedObjectRecord>();

            try
            {
                var sql = @"
                    SELECT * FROM CollisionDetectionExcludedObjects 
                    WHERE DetectionRecordId = @DetectionRecordId
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@DetectionRecordId", detectionRecordId);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            results.Add(new CollisionDetectionExcludedObjectRecord
                            {
                                Id = Convert.ToInt32(reader["Id"]),
                                DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]),
                                ModelIndex = Convert.ToInt32(reader["ModelIndex"]),
                                PathId = reader["PathId"].ToString(),
                                DisplayName = reader["DisplayName"]?.ToString(),
                                ObjectName = reader["ObjectName"]?.ToString(),
                                Reason = reader["Reason"]?.ToString()
                            });
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取检测记录排除对象失败: {ex.Message}", ex);
            }

            return results;
        }

        /// <summary>
        /// 获取碰撞检测记录的手工目标
        /// </summary>
        public List<CollisionDetectionManualTargetRecord> GetCollisionDetectionManualTargets(int detectionRecordId)
        {
            var results = new List<CollisionDetectionManualTargetRecord>();

            try
            {
                var sql = @"
                    SELECT * FROM CollisionDetectionManualTargets 
                    WHERE DetectionRecordId = @DetectionRecordId
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@DetectionRecordId", detectionRecordId);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            results.Add(new CollisionDetectionManualTargetRecord
                            {
                                Id = Convert.ToInt32(reader["Id"]),
                                DetectionRecordId = Convert.ToInt32(reader["DetectionRecordId"]),
                                ModelIndex = Convert.ToInt32(reader["ModelIndex"]),
                                PathId = reader["PathId"].ToString(),
                                DisplayName = reader["DisplayName"]?.ToString(),
                                ObjectName = reader["ObjectName"]?.ToString()
                            });
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取检测记录手工目标失败: {ex.Message}", ex);
            }

            return results;
        }

        /// <summary>
        /// 从DataReader读取碰撞检测记录
        /// </summary>
        private CollisionDetectionRecord ReadCollisionDetectionRecordFromReader(SQLiteDataReader reader)
        {
            return new CollisionDetectionRecord
            {
                Id = Convert.ToInt32(reader["Id"]),
                RouteId = reader["RouteId"].ToString(),
                CreatedTime = Convert.ToDateTime(reader["CreatedTime"]),
                FrameRate = Convert.ToInt32(reader["FrameRate"]),
                DurationSeconds = Convert.ToDouble(reader["DurationSeconds"]),
                // 数据库中存储米单位，读取时转换为模型单位
                DetectionTolerance = Convert.ToDouble(reader["DetectionTolerance"]), // 模型单位，不转换
                IsVirtualObject = Convert.ToInt32(reader["IsVirtualObject"]) == 1,
                AnimatedObjectName = reader["AnimatedObjectName"]?.ToString(),
                ObjectModelIndex = reader["ObjectModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ObjectModelIndex"]) : (int?)null,
                ObjectPathId = reader["ObjectPathId"]?.ToString(),
                VirtualObjectLength = reader["VirtualObjectLength"] != DBNull.Value ? Convert.ToDouble(reader["VirtualObjectLength"]) : (double?)null,
                VirtualObjectWidth = reader["VirtualObjectWidth"] != DBNull.Value ? Convert.ToDouble(reader["VirtualObjectWidth"]) : (double?)null,
                VirtualObjectHeight = reader["VirtualObjectHeight"] != DBNull.Value ? Convert.ToDouble(reader["VirtualObjectHeight"]) : (double?)null,
                DetectAllObjects = Convert.ToInt32(reader["DetectAllObjects"]) == 1,
                ObjectRotationCorrection = Convert.ToDouble(reader["ObjectRotationCorrection"]),
                Description = reader["Description"]?.ToString(),
                // 碰撞结果字段（原 ClashDetectiveResults 表字段）
                TestName = reader["TestName"]?.ToString(),
                TestTime = reader["TestTime"] != DBNull.Value ? (DateTime?)Convert.ToDateTime(reader["TestTime"]) : null,
                CollisionCount = reader["CollisionCount"] != DBNull.Value ? Convert.ToInt32(reader["CollisionCount"]) : 0,
                AnimationCollisionCount = reader["AnimationCollisionCount"] != DBNull.Value ? Convert.ToInt32(reader["AnimationCollisionCount"]) : 0
            };
        }

        #endregion

        /// <summary>
        /// 执行非查询SQL语句
        /// </summary>
        private void ExecuteNonQuery(string sql, object parameters = null)
        {
            using (var cmd = new SQLiteCommand(sql, _connection))
            {
                if (parameters != null)
                {
                    var props = parameters.GetType().GetProperties();
                    foreach (var prop in props)
                    {
                        cmd.Parameters.AddWithValue($"@{prop.Name}", prop.GetValue(parameters));
                    }
                }
                cmd.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// 获取ModelItem的显示名称
        /// </summary>
        private string GetItemDisplayName(ModelItem item)
        {
            if (item == null)
                return "未知对象";

            try
            {
                return item.DisplayName ?? item.ToString();
            }
            catch
            {
                return "未知对象";
            }
        }

        #region 批处理任务管理

        /// <summary>
        /// 根据测试名称获取碰撞检测记录（异步）
        /// </summary>
        public async Task<CollisionDetectionRecord> GetDetectionResultByTestNameAsync(string testName)
        {
            return await Task.Run(() =>
            {
                using (var cmd = new SQLiteCommand(_connection))
                {
                    cmd.CommandText = @"
                        SELECT cdr.*, pr.Name AS PathName
                        FROM CollisionDetectionRecords cdr
                        INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id
                        WHERE cdr.TestName = @TestName";
                    
                    cmd.Parameters.AddWithValue("@TestName", testName);
                    
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            return ReadCollisionDetectionRecordFromReader(reader);
                        }
                        return null;
                    }
                }
            });
        }

        /// <summary>
        /// 异步获取路径信息
        /// </summary>
        public async Task<PathRoute> GetPathRouteAsync(string routeId)
        {
            return await Task.Run(() =>
            {
                return GetPathRouteSync(routeId);
            });
        }

        /// <summary>
        /// 同步获取路径信息（用于需要同步调用的场景）
        /// </summary>
        public PathRoute GetPathRouteSync(string routeId)
        {
            using (var cmd = new SQLiteCommand(_connection))
            {
                // 注意：TotalLength 字段已从数据库中删除
                cmd.CommandText = @"
                    SELECT Id, Name, CreatedTime, LastModified, 
                           TurnRadius, IsCurved, MaxObjectLength, MaxObjectWidth, MaxObjectHeight, 
                        SafetyMargin, GridSize, PathType, LiftHeight, RailMountMode, RailPathDefinitionMode,
                        RailNormalOffset, RailPreferredNormalX, RailPreferredNormalY, RailPreferredNormalZ, IsTwoPointVertical
                    FROM PathRoutes
                    WHERE Id = @Id";

                cmd.Parameters.AddWithValue("@Id", routeId);

                using (var reader = cmd.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        var route = new PathRoute(reader["Name"].ToString())
                        {
                            Id = reader["Id"].ToString(),
                            CreatedTime = DateTime.Parse(reader["CreatedTime"].ToString()),
                            LastModified = DateTime.Parse(reader["LastModified"].ToString()),
                            // TotalLength：由 PathRoute.TotalLength 计算属性实时提供
                            TurnRadius = Convert.ToDouble(reader["TurnRadius"]),
                            IsCurved = Convert.ToInt32(reader["IsCurved"]) == 1,
                            MaxObjectLength = Convert.ToDouble(reader["MaxObjectLength"]),
                            MaxObjectWidth = Convert.ToDouble(reader["MaxObjectWidth"]),
                            MaxObjectHeight = Convert.ToDouble(reader["MaxObjectHeight"]),
                            SafetyMargin = Convert.ToDouble(reader["SafetyMargin"]),
                            GridSize = Convert.ToDouble(reader["GridSize"]),
                            PathType = (PathType)Convert.ToInt32(reader["PathType"]),
                            LiftHeight = Convert.ToDouble(reader["LiftHeight"]),
                            RailMountMode = (RailMountMode)Convert.ToInt32(reader["RailMountMode"]),
                        RailPathDefinitionMode = (RailPathDefinitionMode)Convert.ToInt32(reader["RailPathDefinitionMode"]),
                        RailNormalOffset = reader["RailNormalOffset"] != DBNull.Value ? Convert.ToDouble(reader["RailNormalOffset"]) : 0.0,
                        IsTwoPointVertical = reader["IsTwoPointVertical"] != DBNull.Value && Convert.ToInt32(reader["IsTwoPointVertical"]) == 1
                        };

                        if (reader["RailPreferredNormalX"] != DBNull.Value &&
                            reader["RailPreferredNormalY"] != DBNull.Value &&
                            reader["RailPreferredNormalZ"] != DBNull.Value)
                        {
                            route.RailPreferredNormal = new Point3D(
                                Convert.ToDouble(reader["RailPreferredNormalX"]),
                                Convert.ToDouble(reader["RailPreferredNormalY"]),
                                Convert.ToDouble(reader["RailPreferredNormalZ"]));
                        }

                        // 迁移：旧 IsTwoPointVertical 吊装路径 → 自由路径（方案A）
                        if (route.PathType == PathType.Hoisting && route.IsTwoPointVertical)
                        {
                            LogManager.Info($"[迁移] 旧2点吊装路径 {route.Name} → 转换为自由路径");
                            route.PathType = PathType.Free;
                            route.IsTwoPointVertical = false;
                        }

                        // 加载路径点和路径边
                        LoadPathPoints(route);
                        LoadPathEdges(route);
                        return route;
                    }
                }
            }
            return null;
        }

        #endregion

        /// <summary>
        /// 检查并扩展 PathRoutes 表结构
        /// </summary>
        public void EnsurePathRoutesTableExtended()
        {
            try
            {
                var checkSql = "PRAGMA table_info(PathRoutes)";
                var existingColumns = new HashSet<string>();

                using (var cmd = new SQLiteCommand(checkSql, _connection))
                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        existingColumns.Add(reader["name"].ToString().ToLower());
                    }
                }

                var columnsToAdd = new Dictionary<string, string>
                {
                    ["railmountmode"] = "ALTER TABLE PathRoutes ADD COLUMN RailMountMode INTEGER DEFAULT 0",
                    ["railpathdefinitionmode"] = "ALTER TABLE PathRoutes ADD COLUMN RailPathDefinitionMode INTEGER DEFAULT 0",
                ["railnormaloffset"] = "ALTER TABLE PathRoutes ADD COLUMN RailNormalOffset REAL DEFAULT 0",
                ["railpreferrednormalx"] = "ALTER TABLE PathRoutes ADD COLUMN RailPreferredNormalX REAL",
                    ["railpreferrednormaly"] = "ALTER TABLE PathRoutes ADD COLUMN RailPreferredNormalY REAL",
                    ["railpreferrednormalz"] = "ALTER TABLE PathRoutes ADD COLUMN RailPreferredNormalZ REAL",
                    ["istwopointvertical"] = "ALTER TABLE PathRoutes ADD COLUMN IsTwoPointVertical INTEGER DEFAULT 0"
                };

                foreach (var column in columnsToAdd)
                {
                    if (!existingColumns.Contains(column.Key))
                    {
                        ExecuteNonQuery(column.Value);
                        LogManager.Info($"扩展 PathRoutes 表: 添加字段 {column.Key}");
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"扩展 PathRoutes 表失败: {ex.Message}", ex);
            }
        }

        #region 批处理队列管理（简化版）

        /// <summary>
        /// 创建批处理队列项
        /// </summary>
        public async Task<int> CreateBatchQueueItemAsync(BatchQueueItem item)
        {
            return await Task.Run(() =>
            {
                using (var cmd = new SQLiteCommand(_connection))
                {
                    cmd.CommandText = @"
                        INSERT INTO BatchQueueItems (
                            RouteId, Status, CreatedTime, StartTime, EndTime, ErrorMessage,
                            FrameRate, DurationSeconds, DetectionTolerance,
                            IsVirtualObject, VirtualObjectLength, VirtualObjectWidth, VirtualObjectHeight,
                            DetectAllObjects,
                            ClashDetectiveTestName, CollisionCount, ObjectRotationCorrection,
                            DetectionRecordId
                        )
                        VALUES (
                            @RouteId, @Status, @CreatedTime, @StartTime, @EndTime, @ErrorMessage,
                            @FrameRate, @DurationSeconds, @DetectionTolerance,
                            @IsVirtualObject, @VirtualObjectLength, @VirtualObjectWidth, @VirtualObjectHeight,
                            @DetectAllObjects,
                            @ClashDetectiveTestName, @CollisionCount, @ObjectRotationCorrection,
                            @DetectionRecordId
                        );
                        SELECT last_insert_rowid();";

                    cmd.Parameters.AddWithValue("@RouteId", item.RouteId);
                    cmd.Parameters.AddWithValue("@Status", item.Status.ToString());
                    cmd.Parameters.AddWithValue("@CreatedTime", item.CreatedTime.ToString("o"));
                    cmd.Parameters.AddWithValue("@StartTime", item.StartTime?.ToString("o") ?? "");
                    cmd.Parameters.AddWithValue("@EndTime", item.EndTime?.ToString("o") ?? "");
                    cmd.Parameters.AddWithValue("@ErrorMessage", item.ErrorMessage ?? "");
                    cmd.Parameters.AddWithValue("@FrameRate", item.FrameRate);
                    cmd.Parameters.AddWithValue("@DurationSeconds", item.DurationSeconds);
                    // 内部使用模型单位，写入数据库时转换为米单位
                    cmd.Parameters.AddWithValue("@DetectionTolerance", item.DetectionTolerance); // 模型单位，不转换
                    cmd.Parameters.AddWithValue("@IsVirtualObject", item.IsVirtualObject ? 1 : 0);
                    cmd.Parameters.AddWithValue("@VirtualObjectLength", item.VirtualObjectLength);
                    cmd.Parameters.AddWithValue("@VirtualObjectWidth", item.VirtualObjectWidth);
                    cmd.Parameters.AddWithValue("@VirtualObjectHeight", item.VirtualObjectHeight);
                    cmd.Parameters.AddWithValue("@DetectAllObjects", item.DetectAllObjects ? 1 : 0);
                    cmd.Parameters.AddWithValue("@ClashDetectiveTestName", item.ClashDetectiveTestName ?? "");
                    cmd.Parameters.AddWithValue("@CollisionCount", item.CollisionCount ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@ObjectRotationCorrection", item.ObjectRotationCorrection);
                    cmd.Parameters.AddWithValue("@DetectionRecordId", item.DetectionRecordId ?? (object)DBNull.Value);

                    var queueItemId = Convert.ToInt32(cmd.ExecuteScalar());

                    // 注意：运动物体和碰撞检测目标都存储在 ModelItemReferences 表中
                    // 这些数据在调用 CreateBatchQueueItemAsync 之前插入
                    // Role = "MovingObject" 表示运动物体
                    // Role = "CollisionTarget" 表示碰撞检测目标

                    return queueItemId;
                }
            });
        }

        /// <summary>
        /// 从 SQLiteDataReader 读取 BatchQueueItem 对象
        /// </summary>
        private BatchQueueItem ReadBatchQueueItemFromReader(SQLiteDataReader reader)
        {
            var itemId = Convert.ToInt32(reader["Id"]);
            var isVirtualObject = Convert.ToBoolean(reader["IsVirtualObject"]);

            var item = new BatchQueueItem
            {
                Id = itemId,
                RouteId = reader["RouteId"].ToString(),
                PathRouteName = reader["PathRouteName"].ToString(),
                PathType = (PathType)Convert.ToInt32(reader["PathType"]),
                Status = (BatchQueueStatus)Enum.Parse(typeof(BatchQueueStatus), reader["Status"].ToString()),
                CreatedTime = DateTime.Parse(reader["CreatedTime"].ToString()),
                StartTime = reader["StartTime"].ToString() != "" ? (DateTime?)DateTime.Parse(reader["StartTime"].ToString()) : null,
                EndTime = reader["EndTime"].ToString() != "" ? (DateTime?)DateTime.Parse(reader["EndTime"].ToString()) : null,
                ErrorMessage = reader["ErrorMessage"].ToString(),
                FrameRate = Convert.ToInt32(reader["FrameRate"]),
                DurationSeconds = Convert.ToDouble(reader["DurationSeconds"]),
                // 数据库中存储米单位，读取时转换为模型单位
                DetectionTolerance = Convert.ToDouble(reader["DetectionTolerance"]), // 模型单位，不转换
                IsVirtualObject = isVirtualObject,
                VirtualObjectLength = Convert.ToDouble(reader["VirtualObjectLength"]),
                VirtualObjectWidth = Convert.ToDouble(reader["VirtualObjectWidth"]),
                VirtualObjectHeight = Convert.ToDouble(reader["VirtualObjectHeight"]),
                DetectAllObjects = Convert.ToBoolean(reader["DetectAllObjects"]),
                ClashDetectiveTestName = reader["ClashDetectiveTestName"].ToString(),
                CollisionCount = !Convert.IsDBNull(reader["CollisionCount"]) ? (int?)Convert.ToInt32(reader["CollisionCount"]) : null,
                ObjectRotationCorrection = Convert.ToDouble(reader["ObjectRotationCorrection"]),
                DetectionRecordId = !Convert.IsDBNull(reader["DetectionRecordId"]) ? (int?)Convert.ToInt32(reader["DetectionRecordId"]) : null
            };

            // 填充 MovingObjectName 属性
            if (!isVirtualObject)
            {
                // 从 ModelItemReferences 表查询运动物体名称
                var movingObjectReferences = GetModelItemReferencesSync(itemId, "BatchQueueItem", "MovingObject");
                if (movingObjectReferences.Count > 0)
                {
                    item.MovingObjectName = movingObjectReferences[0].Item3; // Item3 是 DisplayName
                }
            }
            // 虚拟物体时，MovingObjectName 保持 null

            return item;
        }

        /// <summary>
        /// 获取批处理队列项列表
        /// </summary>
        public async Task<List<BatchQueueItem>> GetBatchQueueItemsAsync(BatchQueueStatus statusFilter = BatchQueueStatus.All, int limit = 50, int offset = 0)
        {
            return await Task.Run(() =>
            {
                var items = new List<BatchQueueItem>();
                using (var cmd = new SQLiteCommand(_connection))
                {
                    var sql = @"
                        SELECT bqi.Id, bqi.RouteId, pr.Name AS PathRouteName, pr.PathType, bqi.Status, bqi.CreatedTime, bqi.StartTime, bqi.EndTime, bqi.ErrorMessage,
                               bqi.FrameRate, bqi.DurationSeconds, bqi.DetectionTolerance,
                               bqi.IsVirtualObject, bqi.VirtualObjectLength, bqi.VirtualObjectWidth, bqi.VirtualObjectHeight,
                               bqi.DetectAllObjects,
                               bqi.ClashDetectiveTestName, bqi.CollisionCount, bqi.ObjectRotationCorrection,
                               bqi.DetectionRecordId
                        FROM BatchQueueItems bqi
                        INNER JOIN PathRoutes pr ON bqi.RouteId = pr.Id";
                    var conditions = new List<string>();

                    // 只有当 statusFilter 不是 All 时才添加筛选条件
                    if (statusFilter != BatchQueueStatus.All)
                    {
                        conditions.Add("bqi.Status = @Status");
                        cmd.Parameters.AddWithValue("@Status", statusFilter.ToString());
                    }

                    if (conditions.Count > 0)
                    {
                        sql += " WHERE " + string.Join(" AND ", conditions);
                    }

                    sql += " ORDER BY bqi.CreatedTime DESC";

                    if (limit > 0)
                    {
                        sql += " LIMIT @Limit";
                        cmd.Parameters.AddWithValue("@Limit", limit);
                    }

                    if (offset > 0)
                    {
                        sql += " OFFSET @Offset";
                        cmd.Parameters.AddWithValue("@Offset", offset);
                    }

                    cmd.CommandText = sql;

                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            items.Add(ReadBatchQueueItemFromReader(reader));
                        }
                    }
                }
                return items;
            });
        }

        /// <summary>
        /// 获取批处理队列项
        /// </summary>
        public async Task<BatchQueueItem> GetBatchQueueItemAsync(int itemId)
        {
            return await Task.Run(() =>
            {
                using (var cmd = new SQLiteCommand(_connection))
                {
                    cmd.CommandText = @"
                        SELECT bqi.Id, bqi.RouteId, pr.Name AS PathRouteName, pr.PathType, bqi.Status, bqi.CreatedTime, bqi.StartTime, bqi.EndTime, bqi.ErrorMessage,
                               bqi.FrameRate, bqi.DurationSeconds, bqi.DetectionTolerance,
                               bqi.IsVirtualObject, bqi.VirtualObjectLength, bqi.VirtualObjectWidth, bqi.VirtualObjectHeight,
                               bqi.DetectAllObjects,
                               bqi.ClashDetectiveTestName, bqi.CollisionCount, bqi.ObjectRotationCorrection,
                               bqi.DetectionRecordId
                        FROM BatchQueueItems bqi
                        INNER JOIN PathRoutes pr ON bqi.RouteId = pr.Id
                        WHERE bqi.Id = @Id";

                    cmd.Parameters.AddWithValue("@Id", itemId);

                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            return ReadBatchQueueItemFromReader(reader);
                        }
                    }
                }
                return null;
            });
        }

        /// <summary>
        /// 更新批处理队列项
        /// </summary>
        public async Task UpdateBatchQueueItemAsync(BatchQueueItem item)
        {
            await Task.Run(() =>
            {
                using (var cmd = new SQLiteCommand(_connection))
                {
                    cmd.CommandText = @"
                        UPDATE BatchQueueItems
                        SET Status = @Status, StartTime = @StartTime, EndTime = @EndTime, ErrorMessage = @ErrorMessage,
                            ClashDetectiveTestName = @ClashDetectiveTestName, CollisionCount = @CollisionCount
                        WHERE Id = @Id";
                    
                    cmd.Parameters.AddWithValue("@Status", item.Status.ToString());
                    cmd.Parameters.AddWithValue("@StartTime", item.StartTime?.ToString("o") ?? "");
                    cmd.Parameters.AddWithValue("@EndTime", item.EndTime?.ToString("o") ?? "");
                    cmd.Parameters.AddWithValue("@ErrorMessage", item.ErrorMessage ?? "");
                    cmd.Parameters.AddWithValue("@ClashDetectiveTestName", item.ClashDetectiveTestName ?? "");
                    cmd.Parameters.AddWithValue("@CollisionCount", item.CollisionCount ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@Id", item.Id);
                    
                    cmd.ExecuteNonQuery();
                }
            });
        }

        /// <summary>
        /// 删除批处理队列项
        /// </summary>
        public async Task DeleteBatchQueueItemAsync(int itemId)
        {
            await Task.Run(() =>
            {
                using (var cmd = new SQLiteCommand(_connection))
                {
                    cmd.CommandText = "DELETE FROM BatchQueueItems WHERE Id = @Id";
                    cmd.Parameters.AddWithValue("@Id", itemId);
                    cmd.ExecuteNonQuery();
                }
            });
        }

        /// <summary>
        /// 添加 ModelItem 引用到 ModelItemReferences 表
        /// </summary>
        public async Task<int> AddModelItemReferenceAsync(int referenceId, string referenceType, int modelIndex, string pathId, string displayName, string objectName, string role)
        {
            return await Task.Run(() =>
            {
                using (var cmd = new SQLiteCommand(_connection))
                {
                    cmd.CommandText = @"
                        INSERT INTO ModelItemReferences (ReferenceId, ReferenceType, ModelIndex, PathId, DisplayName, ObjectName, Role)
                        VALUES (@ReferenceId, @ReferenceType, @ModelIndex, @PathId, @DisplayName, @ObjectName, @Role);
                        SELECT last_insert_rowid();";

                    cmd.Parameters.AddWithValue("@ReferenceId", referenceId);
                    cmd.Parameters.AddWithValue("@ReferenceType", referenceType);
                    cmd.Parameters.AddWithValue("@ModelIndex", modelIndex);
                    cmd.Parameters.AddWithValue("@PathId", pathId);
                    cmd.Parameters.AddWithValue("@DisplayName", displayName ?? "");
                    cmd.Parameters.AddWithValue("@ObjectName", objectName ?? "");
                    cmd.Parameters.AddWithValue("@Role", role);

                    return Convert.ToInt32(cmd.ExecuteScalar());
                }
            });
        }

        /// <summary>
        /// 获取指定 ReferenceId 和 Role 的所有 ModelItem 引用
        /// </summary>
        public async Task<List<(int ModelIndex, string PathId, string DisplayName, string ObjectName)>> GetModelItemReferencesAsync(int referenceId, string referenceType, string role = null)
        {
            return await Task.Run(() =>
            {
                var items = new List<(int, string, string, string)>();
                using (var cmd = new SQLiteCommand(_connection))
                {
                    var sql = @"
                        SELECT ModelIndex, PathId, DisplayName, ObjectName
                        FROM ModelItemReferences
                        WHERE ReferenceId = @ReferenceId AND ReferenceType = @ReferenceType";

                    if (!string.IsNullOrEmpty(role))
                    {
                        sql += " AND Role = @Role";
                        cmd.Parameters.AddWithValue("@Role", role);
                    }

                    cmd.CommandText = sql;
                    cmd.Parameters.AddWithValue("@ReferenceId", referenceId);
                    cmd.Parameters.AddWithValue("@ReferenceType", referenceType);

                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            items.Add((
                                Convert.ToInt32(reader["ModelIndex"]),
                                reader["PathId"].ToString(),
                                reader["DisplayName"].ToString(),
                                reader["ObjectName"].ToString()
                            ));
                        }
                    }
                }
                return items;
            });
        }

        /// <summary>
        /// 获取指定 ReferenceId 和 Role 的所有 ModelItem 引用（同步版本，用于在 Task.Run 内部调用）
        /// </summary>
        private List<(int ModelIndex, string PathId, string DisplayName, string ObjectName)> GetModelItemReferencesSync(int referenceId, string referenceType, string role = null)
        {
            var items = new List<(int, string, string, string)>();
            using (var cmd = new SQLiteCommand(_connection))
            {
                var sql = @"
                    SELECT ModelIndex, PathId, DisplayName, ObjectName
                    FROM ModelItemReferences
                    WHERE ReferenceId = @ReferenceId AND ReferenceType = @ReferenceType";

                if (!string.IsNullOrEmpty(role))
                {
                    sql += " AND Role = @Role";
                    cmd.Parameters.AddWithValue("@Role", role);
                }

                cmd.CommandText = sql;
                cmd.Parameters.AddWithValue("@ReferenceId", referenceId);
                cmd.Parameters.AddWithValue("@ReferenceType", referenceType);

                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        items.Add((
                            Convert.ToInt32(reader["ModelIndex"]),
                            reader["PathId"].ToString(),
                            reader["DisplayName"].ToString(),
                            reader["ObjectName"].ToString()
                        ));
                    }
                }
            }
            return items;
        }

        /// <summary>
        /// 删除指定 ReferenceId 的所有 ModelItem 引用
        /// </summary>
        public async Task DeleteModelItemReferencesAsync(int referenceId, string referenceType = null)
        {
            await Task.Run(() =>
            {
                using (var cmd = new SQLiteCommand(_connection))
                {
                    var sql = "DELETE FROM ModelItemReferences WHERE ReferenceId = @ReferenceId";
                    if (!string.IsNullOrEmpty(referenceType))
                    {
                        sql += " AND ReferenceType = @ReferenceType";
                        cmd.Parameters.AddWithValue("@ReferenceType", referenceType);
                    }

                    cmd.CommandText = sql;
                    cmd.Parameters.AddWithValue("@ReferenceId", referenceId);
                    cmd.ExecuteNonQuery();
                }
            });
        }

        #endregion

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _connection?.Close();
                _connection?.Dispose();
            }
        }

        #region 路径分析扩展方法

        /// <summary>
        /// 获取路径最新的ClashDetective检测结果
        /// </summary>
        public CollisionDetectionRecord GetLatestDetectionResultByRouteId(string routeId)
        {
            try
            {
                var sql = @"
                    SELECT cdr.*, pr.Name AS PathName
                    FROM CollisionDetectionRecords cdr
                    INNER JOIN PathRoutes pr ON cdr.RouteId = pr.Id
                    WHERE cdr.RouteId = @routeId AND cdr.TestName IS NOT NULL
                    ORDER BY cdr.TestTime DESC
                    LIMIT 1
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@routeId", routeId ?? "");
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            return ReadCollisionDetectionRecordFromReader(reader);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取路径最新碰撞检测记录失败: {ex.Message}", ex);
            }

            return null;
        }

        /// <summary>
        /// 保存详细分析结果（扩展字段）
        /// </summary>
        public void SaveDetailedAnalysisResult(PathAnalysisResult result)
        {
            try
            {
                var sql = @"
                    INSERT OR REPLACE INTO AnalysisResults
                    (RouteId, CollisionCount, SafetyScore, EfficiencyScore, OverallScore, AnalysisTime, Strategy,
                     TurnDifficultyScore, TortuosityScore, RedundancyScore, HotspotCount, GroupId, GroupRanking)
                    VALUES (@routeId, @collisionCount, @safetyScore, @efficiencyScore, @overallScore, @analysisTime, @strategy,
                            @turnDifficultyScore, @tortuosityScore, @redundancyScore, @hotspotCount, @groupId, @groupRanking)
                ";

                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@routeId", result.RouteId);
                    cmd.Parameters.AddWithValue("@collisionCount", result.CollisionCount);
                    cmd.Parameters.AddWithValue("@safetyScore", result.SafetyScore);
                    cmd.Parameters.AddWithValue("@efficiencyScore", result.EfficiencyScore);
                    cmd.Parameters.AddWithValue("@overallScore", result.OverallScore);
                    cmd.Parameters.AddWithValue("@analysisTime", result.AnalysisTime);
                    cmd.Parameters.AddWithValue("@strategy", result.Strategy ?? "平衡模式");
                    cmd.Parameters.AddWithValue("@turnDifficultyScore", result.TurnDifficultyScore);
                    cmd.Parameters.AddWithValue("@tortuosityScore", result.TortuosityScore);
                    cmd.Parameters.AddWithValue("@redundancyScore", result.RedundancyScore);
                    cmd.Parameters.AddWithValue("@hotspotCount", result.HotspotCount);
                    cmd.Parameters.AddWithValue("@groupId", result.GroupId ?? (object)DBNull.Value);
                    cmd.Parameters.AddWithValue("@groupRanking", result.GroupRanking);

                    cmd.ExecuteNonQuery();
                }

                LogManager.Debug($"保存详细分析结果: RouteId={result.RouteId}, OverallScore={result.OverallScore:F1}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"保存详细分析结果失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 检查并扩展 AnalysisResults 表结构（用于升级现有数据库）
        /// </summary>
        public void EnsureAnalysisResultsTableExtended()
        {
            try
            {
                // 检查表是否存在扩展字段
                var checkSql = "PRAGMA table_info(AnalysisResults)";
                var existingColumns = new HashSet<string>();

                using (var cmd = new SQLiteCommand(checkSql, _connection))
                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        existingColumns.Add(reader["name"].ToString().ToLower());
                    }
                }

                // 需要添加的字段
                var columnsToAdd = new Dictionary<string, string>
                {
                    ["turndifficultyscore"] = "ALTER TABLE AnalysisResults ADD COLUMN TurnDifficultyScore REAL",
                    ["tortuosityscore"] = "ALTER TABLE AnalysisResults ADD COLUMN TortuosityScore REAL",
                    ["redundancyscore"] = "ALTER TABLE AnalysisResults ADD COLUMN RedundancyScore REAL",
                    ["hotspotcount"] = "ALTER TABLE AnalysisResults ADD COLUMN HotspotCount INTEGER DEFAULT 0",
                    ["groupid"] = "ALTER TABLE AnalysisResults ADD COLUMN GroupId TEXT",
                    ["groupranking"] = "ALTER TABLE AnalysisResults ADD COLUMN GroupRanking INTEGER DEFAULT 0"
                };

                foreach (var column in columnsToAdd)
                {
                    if (!existingColumns.Contains(column.Key))
                    {
                        try
                        {
                            ExecuteNonQuery(column.Value);
                            LogManager.Info($"扩展 AnalysisResults 表: 添加字段 {column.Key}");
                        }
                        catch (Exception ex)
                        {
                            LogManager.Warning($"添加字段 {column.Key} 可能已存在: {ex.Message}");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"扩展 AnalysisResults 表失败: {ex.Message}", ex);
            }
        }

        #endregion

        #region 时标管理

        /// <summary>
        /// 保存时标配置
        /// </summary>
        public void SaveTimeTagProfile(TimeTagProfile profile)
        {
            if (profile == null) return;

            try
            {
                using (var transaction = _connection.BeginTransaction())
                {
                    var profileSql = @"
                        INSERT OR REPLACE INTO TimeTagProfiles
                        (Id, RouteId, Name, DefaultSpeed, TurnSpeedRatio, CreatedAt)
                        VALUES (@id, @routeId, @name, @defaultSpeed, @turnSpeedRatio, @createdAt)
                    ";

                    using (var cmd = new SQLiteCommand(profileSql, _connection))
                    {
                        cmd.Parameters.AddWithValue("@id", profile.Id);
                        cmd.Parameters.AddWithValue("@routeId", profile.RouteId);
                        cmd.Parameters.AddWithValue("@name", profile.Name);
                        cmd.Parameters.AddWithValue("@defaultSpeed", profile.DefaultSpeed);
                        cmd.Parameters.AddWithValue("@turnSpeedRatio", profile.TurnSpeedRatio);
                        cmd.Parameters.AddWithValue("@createdAt", profile.CreatedAt);
                        cmd.ExecuteNonQuery();
                    }

                    using (var cmd = new SQLiteCommand("DELETE FROM TimeTagEvents WHERE ProfileId = @profileId", _connection))
                    {
                        cmd.Parameters.AddWithValue("@profileId", profile.Id);
                        cmd.ExecuteNonQuery();
                    }

                    var eventSql = @"
                        INSERT INTO TimeTagEvents
                        (Id, ProfileId, PointId, Distance, Type, Name, WaitTime, ActionTime, CumulativeTime, ArriveSpeed, LeaveSpeed, SortOrder)
                        VALUES (@id, @profileId, @pointId, @distance, @type, @name, @waitTime, @actionTime, @cumulativeTime, @arriveSpeed, @leaveSpeed, @sortOrder)
                    ";

                    for (int i = 0; i < profile.Events.Count; i++)
                    {
                        var evt = profile.Events[i];
                        using (var cmd = new SQLiteCommand(eventSql, _connection))
                        {
                            cmd.Parameters.AddWithValue("@id", evt.Id);
                            cmd.Parameters.AddWithValue("@profileId", profile.Id);
                            cmd.Parameters.AddWithValue("@pointId", string.IsNullOrEmpty(evt.PointId) ? (object)DBNull.Value : evt.PointId);
                            cmd.Parameters.AddWithValue("@distance", evt.Distance);
                            cmd.Parameters.AddWithValue("@type", evt.Type.ToString());
                            cmd.Parameters.AddWithValue("@name", evt.Name);
                            cmd.Parameters.AddWithValue("@waitTime", evt.WaitTime);
                            cmd.Parameters.AddWithValue("@actionTime", evt.ActionTime);
                            cmd.Parameters.AddWithValue("@cumulativeTime", evt.CumulativeTime);
                            cmd.Parameters.AddWithValue("@arriveSpeed", evt.ArriveSpeed.HasValue ? (object)evt.ArriveSpeed.Value : DBNull.Value);
                            cmd.Parameters.AddWithValue("@leaveSpeed", evt.LeaveSpeed.HasValue ? (object)evt.LeaveSpeed.Value : DBNull.Value);
                            cmd.Parameters.AddWithValue("@sortOrder", i);
                            cmd.ExecuteNonQuery();
                        }
                    }

                    transaction.Commit();
                    LogManager.Info($"时标配置已保存: {profile.Name}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"保存时标配置失败: {ex.Message}", ex);
                throw;
            }
        }

        /// <summary>
        /// 获取路径的所有时标配置
        /// </summary>
        public List<TimeTagProfile> GetTimeTagProfiles(string routeId)
        {
            var profiles = new List<TimeTagProfile>();
            try
            {
                var sql = "SELECT * FROM TimeTagProfiles WHERE RouteId = @routeId ORDER BY CreatedAt";
                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@routeId", routeId);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            var profile = new TimeTagProfile
                            {
                                Id = reader["Id"].ToString(),
                                RouteId = reader["RouteId"].ToString(),
                                Name = reader["Name"].ToString(),
                                DefaultSpeed = Convert.ToDouble(reader["DefaultSpeed"]),
                                TurnSpeedRatio = Convert.ToDouble(reader["TurnSpeedRatio"]),
                                CreatedAt = Convert.ToDateTime(reader["CreatedAt"])
                            };
                            LoadTimeTagEvents(profile);
                            profiles.Add(profile);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取时标配置列表失败: {ex.Message}", ex);
            }
            return profiles;
        }

        /// <summary>
        /// 根据ID获取时标配置
        /// </summary>
        public TimeTagProfile GetTimeTagProfile(string profileId)
        {
            try
            {
                var sql = "SELECT * FROM TimeTagProfiles WHERE Id = @id";
                using (var cmd = new SQLiteCommand(sql, _connection))
                {
                    cmd.Parameters.AddWithValue("@id", profileId);
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            var profile = new TimeTagProfile
                            {
                                Id = reader["Id"].ToString(),
                                RouteId = reader["RouteId"].ToString(),
                                Name = reader["Name"].ToString(),
                                DefaultSpeed = Convert.ToDouble(reader["DefaultSpeed"]),
                                TurnSpeedRatio = Convert.ToDouble(reader["TurnSpeedRatio"]),
                                CreatedAt = Convert.ToDateTime(reader["CreatedAt"])
                            };
                            LoadTimeTagEvents(profile);
                            return profile;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"获取时标配置失败: {ex.Message}", ex);
            }
            return null;
        }

        private void LoadTimeTagEvents(TimeTagProfile profile)
        {
            profile.Events = new List<TimeTagEvent>();
            var sql = "SELECT * FROM TimeTagEvents WHERE ProfileId = @profileId ORDER BY SortOrder";
            using (var cmd = new SQLiteCommand(sql, _connection))
            {
                cmd.Parameters.AddWithValue("@profileId", profile.Id);
                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var evt = new TimeTagEvent
                        {
                            Id = reader["Id"].ToString(),
                            ProfileId = reader["ProfileId"].ToString(),
                            PointId = reader["PointId"] != DBNull.Value ? reader["PointId"].ToString() : null,
                            Distance = Convert.ToDouble(reader["Distance"]),
                            Type = (EventType)Enum.Parse(typeof(EventType), reader["Type"].ToString()),
                            Name = reader["Name"].ToString(),
                            WaitTime = Convert.ToDouble(reader["WaitTime"]),
                            ActionTime = Convert.ToDouble(reader["ActionTime"]),
                            CumulativeTime = Convert.ToDouble(reader["CumulativeTime"]),
                            ArriveSpeed = reader["ArriveSpeed"] != DBNull.Value ? Convert.ToDouble(reader["ArriveSpeed"]) : (double?)null,
                            LeaveSpeed = reader["LeaveSpeed"] != DBNull.Value ? Convert.ToDouble(reader["LeaveSpeed"]) : (double?)null
                        };
                        profile.Events.Add(evt);
                    }
                }
            }
        }

        /// <summary>
        /// 删除时标配置
        /// </summary>
        public void DeleteTimeTagProfile(string profileId)
        {
            try
            {
                using (var cmd = new SQLiteCommand("DELETE FROM TimeTagProfiles WHERE Id = @id", _connection))
                {
                    cmd.Parameters.AddWithValue("@id", profileId);
                    cmd.ExecuteNonQuery();
                }
                LogManager.Info($"时标配置已删除: {profileId}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"删除时标配置失败: {ex.Message}", ex);
                throw;
            }
        }

        #endregion
    }

    /// <summary>
    /// ClashDetective碰撞对象数据模型
    /// 包含碰撞时运动物体的位置和朝向信息，用于还原碰撞场景
    /// </summary>
    public class ClashDetectiveCollisionObjectRecord
    {
        public int Id { get; set; }
        public int DetectionRecordId { get; set; }
        public int? ModelIndex { get; set; }
        public string PathId { get; set; }
        public string DisplayName { get; set; }
        public string ObjectName { get; set; }
        
        // 碰撞时运动物体的动画跟踪点位置和姿态（用于还原碰撞场景）
        public double? AnimatedObjectTrackedPosX { get; set; }
        public double? AnimatedObjectTrackedPosY { get; set; }
        public double? AnimatedObjectTrackedPosZ { get; set; }
        public double? AnimatedObjectTrackedYawRadians { get; set; }
        public double? AnimatedObjectTrackedRotA { get; set; }
        public double? AnimatedObjectTrackedRotB { get; set; }
        public double? AnimatedObjectTrackedRotC { get; set; }
        public double? AnimatedObjectTrackedRotD { get; set; }
        public bool AnimatedObjectHasTrackedRotation { get; set; }
        public bool HasPositionInfo { get; set; }
    }

    /// <summary>
    /// 碰撞检测记录
    /// 每次点击"生成动画"创建一条记录，保存当时的完整配置和检测结果
    /// 原 ClashDetectiveResults 表已合并到此表
    /// </summary>
    public class CollisionDetectionRecord
    {
        public int Id { get; set; }
        public string RouteId { get; set; }
        public DateTime CreatedTime { get; set; }
        
        // 动画参数
        public int FrameRate { get; set; }
        public double DurationSeconds { get; set; }
        
        // 碰撞检测参数（模型单位）
        public double DetectionTolerance { get; set; }
        
        // 运动物体参数
        public bool IsVirtualObject { get; set; }
        public string AnimatedObjectName { get; set; }
        public int? ObjectModelIndex { get; set; }
        public string ObjectPathId { get; set; }
        public double? VirtualObjectLength { get; set; }
        public double? VirtualObjectWidth { get; set; }
        public double? VirtualObjectHeight { get; set; }
        
        // 检测模式
        public bool DetectAllObjects { get; set; }
        
        // 角度修正
        public double ObjectRotationCorrection { get; set; }
        
        // 备注
        public string Description { get; set; }
        
        // 碰撞结果（原 ClashDetectiveResults 表字段）
        public string TestName { get; set; }           // ClashDetective测试名称
        public DateTime? TestTime { get; set; }        // 碰撞测试完成时间
        public int CollisionCount { get; set; }        // 总碰撞数
        public int AnimationCollisionCount { get; set; } // 动画碰撞数
        
        // 关联数据（不存入数据库，查询时填充）
        public List<CollisionDetectionExcludedObjectRecord> ExcludedObjects { get; set; }
        public List<CollisionDetectionManualTargetRecord> ManualTargets { get; set; }
    }

    /// <summary>
    /// 碰撞检测记录排除对象
    /// </summary>
    public class CollisionDetectionExcludedObjectRecord
    {
        public int Id { get; set; }
        public int DetectionRecordId { get; set; }
        public int ModelIndex { get; set; }
        public string PathId { get; set; }
        public string DisplayName { get; set; }
        public string ObjectName { get; set; }
        public string Reason { get; set; }
    }

    /// <summary>
    /// 碰撞检测记录手工目标
    /// </summary>
    public class CollisionDetectionManualTargetRecord
    {
        public int Id { get; set; }
        public int DetectionRecordId { get; set; }
        public int ModelIndex { get; set; }
        public string PathId { get; set; }
        public string DisplayName { get; set; }
        public string ObjectName { get; set; }
    }

    /// <summary>
    /// 碰撞报告截图记录
    /// </summary>
    public class CollisionReportScreenshotRecord
    {
        public int Id { get; set; }
        public int DetectionRecordId { get; set; }
        public string FilePath { get; set; }
        public string Format { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public DateTime CaptureTime { get; set; }
        public string Description { get; set; }
        public int SortOrder { get; set; }
    }

    /// <summary>
    /// 路径分析结果数据模型（扩展版）
    /// </summary>
    public class PathAnalysisResult
    {
        public string RouteId { get; set; }
        public int CollisionCount { get; set; }
        public double SafetyScore { get; set; }
        public double EfficiencyScore { get; set; }
        public double OverallScore { get; set; }
        public DateTime AnalysisTime { get; set; }
        public string Strategy { get; set; }
        
        // 扩展字段
        public double TurnDifficultyScore { get; set; }
        public double TortuosityScore { get; set; }
        public double RedundancyScore { get; set; }
        public int HotspotCount { get; set; }
        public string GroupId { get; set; }
        public int GroupRanking { get; set; }
    }

    #region 排除列表数据模型

    /// <summary>
    /// 排除对象记录
    /// </summary>
    public class ExcludedObjectRecord
    {
        public int Id { get; set; }
        public string RouteId { get; set; }
        public int ModelIndex { get; set; }
        public string PathId { get; set; }
        public string DisplayName { get; set; }
        public string ObjectName { get; set; }
        public DateTime ExcludedTime { get; set; }
        public string Reason { get; set; }
        public bool IsGlobal { get; set; }
    }

    #endregion


}
