using IoTClient.Common.Helpers;
using Newtonsoft.Json;
using Simple3;
using Simple3.RCS;
using Simple3.RCS.CarTypes;
using Simple3.CADTools;
using Simple3.Props;
using Simple3.UI;
using SimpleCore;
using SimpleCore.Compiler;
using SimpleCore.Library;
using SimpleCore.PropType;
using SimpleCore.Traffic;
using StandardScene.CarTypes;
using StandardScene.CommonTools;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;

namespace StandardScene.Chained
{
    /// <summary>
    /// 抽象链式搬运任务类
    /// 实现AGV搬运任务的核心调度逻辑，支持任务链式执行、智能调度、路径规划、冲突避让等功能
    /// 可以做取满放空操作
    /// </summary>
    public abstract class ChainedDeliveryMission:Mission
    {
        #region Parameters / Status
        /// <summary>
        /// 链式搬运任务参数配置类
        /// </summary>
        private class ChainedDeliveryParams
        {
            /// <summary>
            /// 是否启用打断正在去避让点的小车功能
            /// </summary>
            public bool EnableBlockStandby { get; } = true;

            /// <summary>
            /// 是否启用紧密任务链模式
            /// true: 链式搬运任务可以同时启动（前序任务开始时后续任务即可开始），提高效率但可能造成死锁
            /// false: 前序任务必须完成后，后续任务才能开始，更安全但效率较低
            /// </summary>
            public bool TightDeliveryChain { get; } = true;
        }

        /// <summary>
        /// 搬运任务状态统计类
        /// 记录任务执行过程中的各种统计信息
        /// </summary>
        public class DeliveryMissionStatus : MissionStatus
        {
            /// <summary>排队等待执行的任务数量</summary>
            public int Enqueued;
            /// <summary>已完成的任务数量</summary>
            public int Performed;
            /// <summary>每小时作业数（Jobs Per Hour），用于衡量系统吞吐量</summary>
            public double Jph;
            /// <summary>淤积任务信息字符串，格式：任务ID:淤积次数</summary>
            public string StuckInfo = "/";
            /// <summary>当前淤积的任务数量（无法执行的任务）</summary>
            public int StuckNum;

            /// <summary>
            /// 创建后，等待时间超过该阈值的任务，必须被优先安排执行。单位为秒。
            /// </summary>
            public double MustArrangeThreshold = 300;
        }

        /// <summary>
        /// 继承自基类的任务状态属性，用于记录搬运任务的执行状态并在界面显示。
        /// </summary>
        public override MissionStatus status { get; set; } = new DeliveryMissionStatus();

        /// <summary>
        /// 允许任务打断正在去避让点的小车，使其承接该任务。
        /// </summary>
        public bool EnableBlockStandby;
        #endregion

        #region Delivery model

        /// <summary>
        /// 搬运任务状态枚举
        /// </summary>
        public enum DeliveryStatus
        {
            /// <summary>等待执行</summary>
            Waiting = 0,
            /// <summary>取货中</summary>
            Fetching = 1,
            /// <summary>放货中</summary>
            Putting = 2,
            /// <summary>已完成</summary>
            Finished = 3,
            /// <summary>已取消</summary>
            Canceled = 4,
            /// <summary>已终止（执行过程中被中断）</summary>
            Terminated = 5,
            /// <summary>错误</summary>
            Error = 6,
            /// <summary>挂起</summary>
            Suspended = 7,
        }

        /// <summary>
        /// 重构版 Delivery：字段与原 AbstractDelivery 保持接近，便于迁移；
        /// 状态读写统一走 syncStatus 锁，避免竞态。
        /// </summary>
        public abstract class Delivery
        {
            /// <summary>雪花算法 ID 生成器（所有 Delivery 共享一份）</summary>
            private static readonly SnowflakeIdGenerator IdGenerator =
                new SnowflakeIdGenerator(workerId: 1, datacenterId: 1);

            /// <summary>任务唯一标识ID（雪花算法，Base62编码）</summary>
            public string Id { get; set; } = IdGenerator.NextIdBase62();
            /// <summary>外部系统传入的任务ID</summary>
            public string TaskId = string.Empty;

            /// <summary>物料长度（毫米），用于包络约束</summary>
            public float MaterialLength = -1;
            /// <summary>物料宽度（毫米），用于包络约束</summary>
            public float MaterialWidth = -1;

            /// <summary>
            /// 任务启动条件函数，返回true时任务才能开始执行
            /// 可用于实现复杂的前置条件判断
            /// </summary>
            public Func<bool> StartCondition = null;

            // ========== 任务回调函数 ==========
            /// <summary>任务失败时的回调函数</summary>
            [JsonIgnore] public Action<Delivery> Failed;
            /// <summary>放货完成时的回调函数</summary>
            [JsonIgnore] public Action<Delivery> DonePut;
            /// <summary>取货完成时的回调函数</summary>
            [JsonIgnore] public Action<Delivery> DoneFetch;
            /// <summary>整个任务完成时的回调函数</summary>
            [JsonIgnore] public Action<Delivery> DoneMission;
            /// <summary>任务开始执行时的回调函数</summary>
            [JsonIgnore] public Action<Delivery> OnStart;
            /// <summary>任务终止时的回调函数</summary>
            [JsonIgnore] public Func<Delivery, string, Task<int>> OnTerminated;

            // ========== 任务回调配置（可持久化） ==========
            /// <summary>是否在任务开始时上报回调</summary>
            public bool ReportOnStarted;
            /// <summary>是否在取货完成时上报回调</summary>
            public bool ReportOnFetched;
            /// <summary>是否在放货完成时上报回调</summary>
            public bool ReportOnPut;
            /// <summary>是否在任务最终完成时上报回调</summary>
            public bool ReportOnFinished;
            /// <summary>是否在任务失败时上报回调</summary>
            public bool ReportOnFailed;
            /// <summary>是否在任务被终止/暂停时上报回调</summary>
            public bool ReportOnTerminated;

            /// <summary>OnStart 事件绑定的回调 key 列表</summary>
            public List<string> OnStartCallbackKeys { get; set; } = new();
            /// <summary>DoneFetch 事件绑定的回调 key 列表</summary>
            public List<string> DoneFetchCallbackKeys { get; set; } = new();
            /// <summary>DonePut 事件绑定的回调 key 列表</summary>
            public List<string> DonePutCallbackKeys { get; set; } = new();
            /// <summary>DoneMission 事件绑定的回调 key 列表</summary>
            public List<string> DoneMissionCallbackKeys { get; set; } = new();
            /// <summary>Failed 事件绑定的回调 key 列表</summary>
            public List<string> FailedCallbackKeys { get; set; } = new();
            /// <summary>OnTerminated 事件绑定的回调 key 列表</summary>
            public List<string> OnTerminatedCallbackKeys { get; set; } = new();

            // ========== 任务路径信息 ==========
            /// <summary>取货点站点ID</summary>
            public int Src;
            /// <summary>放货点站点ID</summary>
            public int Dst;
            /// <summary>是否跳过取货步骤（如果小车已在取货点或已载货，可设置为true）</summary>
            public bool SkipFetch;
            /// <summary>是否跳过放货步骤（如果只需要移动到目标点但不放货）</summary>
            public bool SkipPut;
            /// <summary>放货点是否允许查找回路路径</summary>
            public bool DstFindLoop;
            /// <summary>取货点是否允许查找回路路径</summary>
            public bool SrcFindLoop;

            /// <summary>取货路径规划参数字典（传递给路径规划器）</summary>
            [JsonIgnore] public Dictionary<string, string> FetchPlanInfo = new() { { "action", "fetch" } };
            /// <summary>放货路径规划参数字典（传递给路径规划器）</summary>
            [JsonIgnore] public Dictionary<string, string> PutPlanInfo = new() { { "action", "put" } };

            /// <summary>前序任务，当前任务需要等待前序任务执行完毕才能开始（任务链依赖关系）</summary>
            [JsonIgnore] public Delivery Former;
            /// <summary>执行当前任务的小车对象</summary>
            [JsonIgnore] public Car UsingCar;
            /// <summary>小车分组筛选条件（用于筛选特定组的小车）</summary>
            [JsonIgnore] public string Group = string.Empty;
            /// <summary>小车筛选键列表（用于多条件筛选）</summary>
            public List<string> SelectorKeys = new();
            /// <summary>允许执行任务的小车类型（如"Forklift"、"Kiva"等）</summary>
            [JsonIgnore] public string CarType = string.Empty;
            /// <summary>
            /// 任务优先级，数值越大，优先级越高
            /// 任务调度时按优先级降序执行，同优先级按创建时间排序
            /// </summary>
            public int Priority = 0;

            // ========== 时间记录 ==========
            /// <summary>任务创建时间</summary>
            public DateTime CreateTime = DateTime.Now;
            /// <summary>任务开始执行时间</summary>
            public DateTime StartTime;
            /// <summary>任务完成时间</summary>
            public DateTime FinishTime;

            /// <summary>任务状态同步锁对象，用于多线程环境下保证状态修改的线程安全</summary>
            [JsonIgnore] public readonly object SyncStatus = new object();

            // ========== 任务内部状态标志 ==========
            /// <summary>任务是否正在执行中</summary>
            internal bool Active;
            /// <summary>任务是否处于放货阶段（true=放货中，false=取货中）</summary>
            internal bool Putting;
            /// <summary>任务是否已完成</summary>
            internal bool Finished;
            /// <summary>任务是否出错</summary>
            internal bool Error;
            /// <summary>任务是否已取消</summary>
            internal bool Canceled;
            /// <summary>任务是否已终止（执行过程中被中断）</summary>
            internal bool Terminated;
            /// <summary>任务被挂起</summary>
            internal bool Suspended;

            /// <summary>追加的逃逸动作（任务完成后需要执行的额外动作，如去避让点）</summary>
            [JsonIgnore] public Action AppendedEscape;

            /// <summary>任务淤积次数（连续执行失败的次数，用于监控任务阻塞情况）</summary>
            public int StuckTime;
            /// <summary>任务淤积原因说明（记录为什么无法执行）</summary>
            public string StuckReason = "/";

            /// <summary>
            /// 获取任务当前状态
            /// 状态优先级：Finished > Canceled > Terminated > Error > Fetching/Putting > Waiting
            /// </summary>
            /// <returns>任务当前状态枚举值</returns>
            public DeliveryStatus GetStatus()
            {
                lock (SyncStatus)
                {
                    if (Finished) return DeliveryStatus.Finished;
                    if (Canceled) return DeliveryStatus.Canceled;
                    if (Terminated) return DeliveryStatus.Terminated;
                    if (Suspended) return DeliveryStatus.Suspended;
                    if (Error) return DeliveryStatus.Error;
                    if (Active) return Putting ? DeliveryStatus.Putting : DeliveryStatus.Fetching;
                    return DeliveryStatus.Waiting;
                }
            }

            public bool IsActive() { lock (SyncStatus) return Active; }
            public bool IsFinished() { lock (SyncStatus) return Finished; }
            public bool IsError() { lock (SyncStatus) return Error; }
            public bool IsCanceled() { lock (SyncStatus) return Canceled; }
            public bool IsTerminated() { lock (SyncStatus) return Terminated; }

            /// <summary>
            /// 取消任务，当前实现仅占位返回 true，具体取消逻辑可由子类重写
            /// </summary>
            public bool Cancel()
            {
                return true;
            }
        }

        /// <summary>
        /// 用于持久化的任务状态快照
        /// 只包含可序列化、与业务恢复相关的关键信息
        /// </summary>
        protected class DeliveryStateSnapshot
        {
            /// <summary>任务唯一标识ID</summary>
            public string Id { get; set; }
            /// <summary>外部系统传入的任务ID</summary>
            public string TaskId { get; set; }
            /// <summary>取货点站点ID</summary>
            public int Src { get; set; }
            /// <summary>放货点站点ID</summary>
            public int Dst { get; set; }
            /// <summary>是否跳过取货步骤</summary>
            public bool SkipFetch { get; set; }
            /// <summary>是否跳过放货步骤</summary>
            public bool SkipPut { get; set; }
            /// <summary>当前是否处于放货阶段（true=放货中）</summary>
            public bool Putting { get; set; }
            /// <summary>任务状态字符串（Waiting/Fetching/Putting/Finished等）</summary>
            public string Status { get; set; }
            /// <summary>任务创建时间</summary>
            public DateTime CreateTime { get; set; }
            /// <summary>任务开始执行时间</summary>
            public DateTime StartTime { get; set; }
            /// <summary>任务完成时间</summary>
            public DateTime FinishTime { get; set; }
            /// <summary>执行任务的小车ID（-1表示未分配）</summary>
            public int UsingCarId { get; set; }
            /// <summary>允许执行任务的小车类型</summary>
            public string CarType { get; set; }
            /// <summary>是否在任务开始时上报回调</summary>
            public bool ReportOnStarted { get; set; }
            /// <summary>是否在取货完成时上报回调</summary>
            public bool ReportOnFetched { get; set; }
            /// <summary>是否在放货完成时上报回调</summary>
            public bool ReportOnPut { get; set; }
            /// <summary>是否在任务最终完成时上报回调</summary>
            public bool ReportOnFinished { get; set; }
            /// <summary>是否在任务失败时上报回调</summary>
            public bool ReportOnFailed { get; set; }
            /// <summary>是否在任务被终止/暂停时上报回调</summary>
            public bool ReportOnTerminated { get; set; }
            /// <summary>OnStart 事件绑定的回调 key 列表</summary>
            public List<string> OnStartCallbackKeys { get; set; } = new();
            /// <summary>DoneFetch 事件绑定的回调 key 列表</summary>
            public List<string> DoneFetchCallbackKeys { get; set; } = new();
            /// <summary>DonePut 事件绑定的回调 key 列表</summary>
            public List<string> DonePutCallbackKeys { get; set; } = new();
            /// <summary>DoneMission 事件绑定的回调 key 列表</summary>
            public List<string> DoneMissionCallbackKeys { get; set; } = new();
            /// <summary>Failed 事件绑定的回调 key 列表</summary>
            public List<string> FailedCallbackKeys { get; set; } = new();
            /// <summary>OnTerminated 事件绑定的回调 key 列表</summary>
            public List<string> OnTerminatedCallbackKeys { get; set; } = new();
        }

        #endregion

        #region Extensibility hooks

        /// <summary>
        /// 小车离开充电站时的扩展动作钩子，子类可重写以执行自定义逻辑（如通知、统计等）
        /// </summary>
        public virtual void LeaveChargeAction(Car car, Site site) { }
        /// <summary>
        /// 动态调整任务优先级的钩子，在调度循环中每次迭代前调用，子类可重写实现自定义优先级策略
        /// </summary>
        public virtual void ChangePriority() { }
        /// <summary>
        /// 定义避让点类型字符串，用于路径规划时识别可避让的站点，子类可重写以区分不同避让类型
        /// </summary>
        public virtual string DefineGiveWayType(AbstractCar car) => "giveWay";

        #endregion

        #region Queue / persistence (thread-safe)

        /// <summary>队列与持久化操作的同步锁，保证多线程访问 _allDeliveries 时的线程安全</summary>
        [JsonIgnore] private readonly object _sync = new();
        /// <summary>所有搬运任务的集合（含等待中与执行中的任务）</summary>
        [JsonIgnore] private readonly HashSet<Delivery> _allDeliveries = new();

        /// <summary>
        /// 将任务加入队列并持久化到文件，线程安全
        /// </summary>
        /// <param name="d">待入队的搬运任务</param>
        public void Enqueue(Delivery d)
        {
            lock (_sync) _allDeliveries.Add(d);
            PersistSingleDelivery(d);
        }

        /// <summary>
        /// 获取当前任务列表，支持按状态过滤（已完成/已终止/错误/已取消），线程安全
        /// </summary>
        /// <param name="includeFinished">是否包含已完成的任务</param>
        /// <param name="includeAborted">是否包含已终止的任务</param>
        /// <param name="includeError">是否包含错误状态的任务</param>
        /// <param name="includeCanceled">是否包含已取消的任务</param>
        /// <returns>去重后按状态排序的任务列表</returns>
        public List<Delivery> GetDeliveries(bool includeFinished = false, bool includeAborted = false,bool includeError = false,bool includeCanceled=false)
        {
            List<Delivery> ds;
            lock (_sync) ds = _allDeliveries.ToList();

            if (!includeFinished)
                ds = ds.Where(p => p.GetStatus() != DeliveryStatus.Finished).ToList();
            if (!includeAborted)
                ds = ds.Where(p => p.GetStatus() != DeliveryStatus.Terminated).ToList();
            if (!includeError)
                ds = ds.Where(p => p.GetStatus() != DeliveryStatus.Error).ToList();
            if (!includeCanceled)
                ds = ds.Where(p => p.GetStatus() != DeliveryStatus.Canceled).ToList();

            return System.Linq.Enumerable.DistinctBy(ds, e => e.Id).OrderBy(e => e.GetStatus()).ToList();
        }

        /// <summary>
        /// 获取持久化文件路径，默认存储在 BaseDirectory/Deliveries/ 下，子类可重写自定义路径
        /// </summary>
        protected virtual string GetDeliveryPersistFilePath()
        {
            var baseDir = AppDomain.CurrentDomain.BaseDirectory;
            var persistDir = Path.Combine(baseDir, "Deliveries");
            Directory.CreateDirectory(persistDir);

            var fileName = $"{GetType().Name}_Deliveries.json";
            return Path.Combine(persistDir, fileName);
        }

        /// <summary>
        /// 从单个 Delivery 生成持久化快照（仅当状态为未结束时返回非 null）。
        /// 在回调中用于单任务持久化，不修改其他任务记录。
        /// </summary>
        private static DeliveryStateSnapshot SnapshotFromDelivery(Delivery d)
        {
            lock (d.SyncStatus)
            {
                var curStatus = d.GetStatus();
                if (curStatus is DeliveryStatus.Finished or DeliveryStatus.Canceled or DeliveryStatus.Error or DeliveryStatus.Terminated)
                    return null;
                return new DeliveryStateSnapshot
                {
                    Id = d.Id,
                    TaskId = d.TaskId,
                    Src = d.Src,
                    Dst = d.Dst,
                    SkipFetch = d.SkipFetch,
                    SkipPut = d.SkipPut,
                    Putting = d.Putting,
                    Status = curStatus.ToString(),
                    CreateTime = d.CreateTime,
                    StartTime = d.StartTime,
                    FinishTime = d.FinishTime,
                    UsingCarId = d.UsingCar?.id ?? -1,
                    CarType = d.CarType ?? string.Empty,
                    ReportOnStarted = d.ReportOnStarted,
                    ReportOnFetched = d.ReportOnFetched,
                    ReportOnPut = d.ReportOnPut,
                    ReportOnFinished = d.ReportOnFinished,
                    ReportOnFailed = d.ReportOnFailed,
                    ReportOnTerminated = d.ReportOnTerminated,
                    OnStartCallbackKeys = d.OnStartCallbackKeys?.ToList() ?? new List<string>(),
                    DoneFetchCallbackKeys = d.DoneFetchCallbackKeys?.ToList() ?? new List<string>(),
                    DonePutCallbackKeys = d.DonePutCallbackKeys?.ToList() ?? new List<string>(),
                    DoneMissionCallbackKeys = d.DoneMissionCallbackKeys?.ToList() ?? new List<string>(),
                    FailedCallbackKeys = d.FailedCallbackKeys?.ToList() ?? new List<string>(),
                    OnTerminatedCallbackKeys = d.OnTerminatedCallbackKeys?.ToList() ?? new List<string>()
                };
            }
        }

        /// <summary>
        /// 仅持久化当前任务，不更改其他任务记录；高并发下由 CommonTools 原子更新保证数据安全。
        /// </summary>
        private void PersistSingleDelivery(Delivery d)
        {
            try
            {
                var snap = SnapshotFromDelivery(d);
                if (snap == null)
                {
                    RemoveSingleDeliveryFromPersist(d.Id);
                    return;
                }
                var path = GetDeliveryPersistFilePath();
                AtomicFileUpdateHelper.ExecuteAtomicUpdate(path, current =>
                {
                    var list = string.IsNullOrEmpty(current)
                        ? new List<DeliveryStateSnapshot>()
                        : JsonConvert.DeserializeObject<List<DeliveryStateSnapshot>>(current) ?? new List<DeliveryStateSnapshot>();
                    var idx = list.FindIndex(x => x.Id == d.Id);
                    if (idx >= 0)
                        list[idx] = snap;
                    else
                        list.Add(snap);
                    return JsonConvert.SerializeObject(list, Formatting.Indented);
                });
            }
            catch (Exception ex)
            {
                Diagnosis.Log($"PersistSingleDelivery error: {ExceptionFormatter.FormatEx(ex)}", "ChainedDeliveryPersist", true);
            }
        }

        /// <summary>
        /// 对外公开的单任务持久化方法，供界面或外部逻辑在修改 Delivery 状态后调用。
        /// </summary>
        /// <param name="d">需要持久化的搬运任务</param>
        public void PersistDelivery(Delivery d)
        {
            if (d == null) return;
            PersistSingleDelivery(d);
        }

        /// <summary>
        /// 从持久化文件中移除指定任务记录，不修改其他任务记录。
        /// </summary>
        private void RemoveSingleDeliveryFromPersist(string deliveryId)
        {
            try
            {
                var path = GetDeliveryPersistFilePath();
                AtomicFileUpdateHelper.ExecuteAtomicUpdate(path, current =>
                {
                    if (string.IsNullOrEmpty(current)) return null;
                    var list = JsonConvert.DeserializeObject<List<DeliveryStateSnapshot>>(current) ?? new List<DeliveryStateSnapshot>();
                    list.RemoveAll(x => x.Id == deliveryId);
                    return JsonConvert.SerializeObject(list, Formatting.Indented);
                });
            }
            catch (Exception ex)
            {
                Diagnosis.Log($"RemoveSingleDeliveryFromPersist error: {ExceptionFormatter.FormatEx(ex)}", "ChainedDeliveryPersist", true);
            }
        }

        /// <summary>
        /// 工厂方法：当内存中尚不存在某个快照对应的 Delivery 时，
        /// 允许子类根据快照信息创建一个新的 Delivery 实例并加入到 _allDeliveries。
        /// 默认返回 null，表示基类不主动创建，具体业务由子类决定。
        /// </summary>
        /// <param name="snap">持久化快照</param>
        /// <returns>新建的 Delivery；返回 null 则跳过该快照</returns>
        protected virtual Delivery CreateDeliveryFromSnapshot(DeliveryStateSnapshot snap)
        {
            // 基类不知道具体业务类型，交由子类重写。
            return null;
        }

        /// <summary>
        /// 检查小车是否已载货，子类可重写以适配不同车型的载货判断逻辑
        /// </summary>
        /// <param name="car">待检查的小车</param>
        /// <returns>true 表示已载货</returns>
        protected virtual bool CheckCarLoaded(AbstractCar car)
        {
            return false;
        }

        /// <summary>
        /// 从持久化文件恢复未完成的任务到内存队列，在进程启动时调用
        /// </summary>
        private void RecoverDeliveriesFromPersistedFile()
        {
            try
            {
                var path = GetDeliveryPersistFilePath();
                if (!File.Exists(path)) return;

                var json = File.ReadAllText(path);
                if (string.IsNullOrWhiteSpace(json)) return;

                var snapshots = JsonConvert.DeserializeObject<List<DeliveryStateSnapshot>>(json) ?? new List<DeliveryStateSnapshot>();
                if (!snapshots.Any()) return;

                List<Delivery> ds;
                lock (_sync) ds = _allDeliveries.ToList();

                foreach (var snap in snapshots)
                {
                    // 1. 先尝试在现有内存任务中匹配（优先 TaskId，其次内部 Id）
                    var d = ds.FirstOrDefault(x =>
                        (!string.IsNullOrEmpty(snap.TaskId) && x.TaskId == snap.TaskId) || x.Id == snap.Id);

                    // 2. 如果内存中没有对应任务，尝试通过工厂方法创建一个新的 Delivery
                    if (d == null)
                    {
                        var created = CreateDeliveryFromSnapshot(snap);
                        if (created != null)
                        {
                            lock (_sync)
                            {
                                _allDeliveries.Add(created);
                                ds.Add(created); // 以便后续快照仍然可以在本次循环中匹配到
                            }
                            d = created;
                        }
                        else
                        {
                            // 子类未提供创建逻辑，则跳过该快照
                            continue;
                        }
                    }

                    lock (d.SyncStatus)
                    {
                        d.Src = snap.Src;
                        d.Dst = snap.Dst;
                        d.SkipFetch = snap.SkipFetch;
                        d.SkipPut = snap.SkipPut;
                        d.Putting = snap.Putting;
                        d.CreateTime = snap.CreateTime;
                        d.StartTime = snap.StartTime;
                        d.FinishTime = snap.FinishTime;
                        d.ReportOnStarted = snap.ReportOnStarted;
                        d.ReportOnFetched = snap.ReportOnFetched;
                        d.ReportOnPut = snap.ReportOnPut;
                        d.ReportOnFinished = snap.ReportOnFinished;
                        d.ReportOnFailed = snap.ReportOnFailed;
                        d.ReportOnTerminated = snap.ReportOnTerminated;
                        d.OnStartCallbackKeys = snap.OnStartCallbackKeys ?? new List<string>();
                        d.DoneFetchCallbackKeys = snap.DoneFetchCallbackKeys ?? new List<string>();
                        d.DonePutCallbackKeys = snap.DonePutCallbackKeys ?? new List<string>();
                        d.DoneMissionCallbackKeys = snap.DoneMissionCallbackKeys ?? new List<string>();
                        d.FailedCallbackKeys = snap.FailedCallbackKeys ?? new List<string>();
                        d.OnTerminatedCallbackKeys = snap.OnTerminatedCallbackKeys ?? new List<string>();

                        d.Active = false;
                        d.Finished = false;
                        d.Error = false;
                        d.Canceled = false;
                        d.Terminated = false;

                        if (Enum.TryParse<DeliveryStatus>(snap.Status, out var s))
                        {
                            switch (s)
                            {
                                case DeliveryStatus.Fetching:
                                    d.Putting = false;
                                    break;
                                case DeliveryStatus.Putting:
                                    d.Putting = true;
                                    d.SkipFetch = true;
                                    break;
                            }
                        }

                        if (snap.UsingCarId >= 0)
                            d.UsingCar = SimpleLib.GetCar(snap.UsingCarId) as Car;
                    }
                }
            }
            catch (Exception ex)
            {
                Diagnosis.Log($"RecoverDeliveriesFromPersistedFile error: {ExceptionFormatter.FormatEx(ex)}", "ChainedDeliveryPersist", true);
            }
        }

        #endregion

        #region Lifecycle (background loop with cancellation)

        /// <summary>用于取消后台调度循环的取消令牌源</summary>
        [JsonIgnore] private CancellationTokenSource _cts;
        /// <summary>后台调度循环任务</summary>
        [JsonIgnore] private Task _loopTask;
        /// <summary>进程是否已启动</summary>
        [JsonIgnore] private volatile bool _started;
        /// <summary>用于在界面绘制调度状态的画刷</summary>
        [JsonIgnore] private readonly SimpleMonitor.Painter _painter = SimpleMonitor.getPainter("cdmPainter_refactored");

        /// <summary>
        /// 启动搬运任务调度进程：恢复持久化任务、启动后台循环
        /// </summary>
        [MethodMember(Name = "启动进程", Description = "开始处理搬运队列")]
        public override void Execute()
        {
            if (_started) return;
            _started = true;
            status.status = "启动中";

            RecoverDeliveriesFromPersistedFile();

            _cts = new CancellationTokenSource();
            _loopTask = Task.Run(() => LoopAsync(_cts.Token), _cts.Token);
            status.status = "已启动";
        }

        /// <summary>
        /// 关闭调度进程，取消后台循环
        /// </summary>
        [MethodMember(Name = "关闭进程", Description = "关闭任务维护进程")]
        public override void Stop()
        {
            status.status = "关闭中";
            _started = false;
            try
            {
                _cts?.Cancel();
            }
            catch { /* ignore */ }
            status.status = "已关闭";
        }

        /// <summary>
        /// 后台调度循环：周期性地检查等待中的任务，尝试为其分配小车并执行
        /// 每次迭代：读取配置、调整优先级、对等待任务排序、逐个尝试执行并更新淤积统计
        /// </summary>
        /// <param name="token">取消令牌，用于响应 Stop 关闭请求</param>
        private async Task LoopAsync(CancellationToken token)
        {
            var stat = (DeliveryMissionStatus)status;
            var iteration = 0;

            while (!token.IsCancellationRequested)
            {
                var checkingTime = new Dictionary<string, string>();
                try
                {
                    // 读取任务参数
                    var missionField = StringDictConvert<ChainedDeliveryParams>.Convert(fields) ?? new ChainedDeliveryParams();
                    var mustArrangeThreshold = ReadMustArrangeThreshold();
                    stat.MustArrangeThreshold = mustArrangeThreshold;
                    EnableBlockStandby = missionField.EnableBlockStandby;
                    var arrangeThresholdTime = TimeSpan.FromSeconds(mustArrangeThreshold);

                    ChangePriority();

                    // 获取所有等待中的任务，按优先级和超时时间排序（超时必须优先安排）
                    Delivery[] currentChecking;
                    lock (_sync)
                    {
                        var pendingDs = _allDeliveries.Where(p => p.GetStatus() == DeliveryStatus.Waiting).ToList();
                        currentChecking = pendingDs.ToArray();
                        currentChecking = currentChecking.OrderByDescending(p =>
                            p.Priority + ((DateTime.Now - p.CreateTime) > arrangeThresholdTime ? 1 : 0)).ToArray();
                    }

                    stat.Enqueued = currentChecking.Length;

                    var stuckStr = "";
                    var stuckedNum = 0;

                    foreach (var d in currentChecking)
                    {

                        checkingTime[d.Id] = "处理中";
                        UpdateDisplay(iteration, checkingTime);
                        var startAt = DateTime.Now;

                        if (!TryExecuteSingle(d, missionField, out var reason))
                        {
                            d.StuckTime += 1;
                            d.StuckReason = reason ?? d.StuckReason;
                            stuckedNum += 1;
                            stuckStr += $"{d.Id}:{d.StuckTime} ";
                        }

                        checkingTime[d.Id] = $"{(DateTime.Now - startAt).TotalSeconds:0.0}s";
                        UpdateDisplay(iteration, checkingTime);
                    }

                    stat.StuckNum = stuckedNum;
                    stat.StuckInfo = stuckStr;

                    status.status = $"已启动-循环{iteration++}";
                }
                catch (Exception ex)
                {
                    Diagnosis.Log($"CDM(refactored) error: {ExceptionFormatter.FormatEx(ex)}", "error", true);
                }

                UpdateDisplay(iteration, checkingTime);
                try { await Task.Delay(1000, token); } catch { /* cancelled */ }  // 每 1 秒循环一次
            }
        }

        /// <summary>
        /// 从配置中读取“必须安排”阈值（秒），超过该等待时间的任务将被优先调度
        /// </summary>
        private double ReadMustArrangeThreshold()
        {
            if (!fields.TryGetValue("mustArrangeThreshold", out var str) || !double.TryParse(str, out var v))
                return 300;
            return v;
        }

        /// <summary>
        /// 更新界面上的调度状态显示：循环次数及各任务处理耗时
        /// </summary>
        private void UpdateDisplay(int iteration, Dictionary<string, string> checkingTime)
        {
            try
            {
                _painter.clear();
                _painter.drawTextFixed(
                    $"CDM循环数：{iteration}\n{string.Join("\n", checkingTime.Select(kvp => $"{kvp.Key}\t{kvp.Value}"))}",
                    new SolidBrush(Color.Black),
                    VirtualPainter.DrawPosition.RightBottom,
                    Color.AliceBlue);
            }
            catch
            {
                // UI绘制失败不影响调度
            }
        }

        #endregion

        #region Core execution pipeline (refactored)

        /// <summary>
        /// 尝试执行单个搬运任务：校验前置条件、选车规划取货、规划放货、编译下发、启动异步执行
        /// </summary>
        /// <param name="d">待执行的搬运任务</param>
        /// <param name="missionField">任务参数配置</param>
        /// <param name="stuckReason">若失败，返回淤积原因</param>
        /// <returns>true 表示任务已成功启动执行</returns>
        private bool TryExecuteSingle(Delivery d, ChainedDeliveryParams missionField, out string stuckReason)
        {
            stuckReason = null;
            missionField ??= new ChainedDeliveryParams();

            // 已在执行中
            if (d.IsActive()) return true;

            // 依赖关系
            if (d.Former != null && !d.Former.IsActive())
            {
                stuckReason = $"depends on {d.Former.Id} not started";
                return false;
            }

            if (d.Former != null && !d.Former.IsFinished() && !missionField.TightDeliveryChain)
            {
                stuckReason = $"depends on {d.Former.Id} not finished";
                return false;
            }

            if (d.StartCondition != null && !d.StartCondition())
            {
                stuckReason = $"{d.Id} does not meet startCondition";
                return false;
            }

            // 延续任务：已有 fetchPlan/fetchCode,延续任务取消
            //if (d.FetchPlan != null)
            //    return TryHandleContinuationFetch(d, out stuckReason);

            // 非延续：选车并规划取货
            SegmentPlan fetchPlan = null;
            Car usingCar;
            if (!d.SkipFetch)
            {
                if (!TrySelectCarAndPlanFetch(d, out usingCar, out fetchPlan, out stuckReason))
                    return false;
            }
            else
            {
                usingCar = d.UsingCar;
                //fetchPlan = d.FetchPlan;
            }

            if (usingCar == null)
            {
                stuckReason = d.SkipFetch
                    ? $"{d.Id} skip fetch, but no using car assigned for put"
                    : $"ucar null, {d.Src}->{d.Dst}";
                return false;
            }

            // 规划放货
            if (!TryPlanPut(d, usingCar, fetchPlan != null, out var putPlan, out var escPlan, out stuckReason))
                return false;

            // 编译与下发（含标签占用）
            if (!TryCompileAndDispatch(d, usingCar, fetchPlan, putPlan, escPlan, out var allCode, out stuckReason))
                return false;

            // 启动 Runner 异步运行并回收状态
            _ = RunDeliveryAsync(d, usingCar, fetchPlan, escPlan, allCode);

            return true;
        }

        #region 延续任务，功能屏蔽
        //private bool TryHandleContinuationFetch(Delivery d, out string stuckReason)
        //{
        //    stuckReason = null;
        //    var usingCar = (Car)d.FetchPlan.UsingCar;

        //    // redirect_fetched: 0 未开始；1 取货中；3 redirect；333 取货完成等待放货；666 去避让
        //    if (d.RedirectFetched == 0)
        //    {
        //        lock (Commons.PlanSession)
        //        {
        //            if (usingCar.tags.Contains("occupied"))
        //            {
        //                stuckReason = $"preassigned using car {usingCar.id} is still occupied";
        //                return false;
        //            }

        //            d.RedirectFetched = 1;
        //            usingCar.tags.Add("occupied", $"DeliverFetch{d.Id}");
        //            usingCar.tags.Remove("blocking");
        //            usingCar.tags.Remove("idle");
        //            usingCar.tags.Remove("charging");
        //        }

        //        try
        //        {
        //            Commons.DeleteTag(tags, "FailCDMToStandby");
        //            Commons.DeleteTag(tags, "FailCDMToStandbyTime");
        //            var task = d.FetchCode.Queue();

        //            async Task go()
        //            {
        //                try
        //                {
        //                    await task;
        //                    d.doneFetch?.Invoke(d);
        //                    lock (d.SyncStatus) d.Putting = true;
        //                }
        //                catch (Exception ex)
        //                {
        //                    Diagnosis.Log($"Send Prefetch Script to {usingCar.id} exception:{ExceptionFormatter.FormatEx(ex)}", "error", true);
        //                    lock (d.SyncStatus) { d.Error = true; d.Active = false; }
        //                    lock (Commons.PlanSession)
        //                    {
        //                        usingCar.tags.Remove("redirect");
        //                        usingCar.tags.Remove("occupied");
        //                        usingCar.tags.Remove("dest");
        //                        usingCar.tags.Add("idle", DateTime.Now.ToString());
        //                    }
        //                    d.failed?.Invoke(d);
        //                    return;
        //                }

        //                usingCar.siteID = d.Src;
        //                lock (Commons.PlanSession)
        //                {
        //                    usingCar.tags.Remove("occupied");
        //                    usingCar.tags.Remove("redirect");
        //                    usingCar.tags.Add("pendingput", d.Id.ToString());
        //                }
        //                d.RedirectFetched = 333;
        //            }

        //            _ = go();
        //        }
        //        catch (Exception ex)
        //        {
        //            Diagnosis.Log($"pre-fetching of {d.Id} by {usingCar.id} failed: {ExceptionFormatter.FormatEx(ex)}", "error", true);
        //            lock (d.SyncStatus) { d.Error = true; d.Active = false; }
        //            lock (Commons.PlanSession)
        //            {
        //                usingCar.tags.Remove("occupied");
        //                usingCar.tags.Remove("redirect");
        //                usingCar.tags.Remove("dest");
        //                usingCar.tags.Add("idle", DateTime.Now.ToString());
        //            }
        //            d.failed?.Invoke(d);
        //            stuckReason = "prefetch failed";
        //            return false;
        //        }

        //        stuckReason = "prefetching";
        //        return false;
        //    }

        //    if (d.RedirectFetched == 1 || d.RedirectFetched == 3)
        //    {
        //        stuckReason = "redirect fetching or redirect giveWay";
        //        return false;
        //    }

        //    // 333/666：等待放货阶段，由主流程继续处理（fetchPlan != null 分支会进入 TryPlanPut）
        //    stuckReason = "waiting for put after prefetch";
        //    return false;
        //}
        #endregion

        /// <summary>
        /// 从可用小车中选车并规划取货路径：筛选可用车、计算到取货点的路径权重、选择最优车与取货段
        /// </summary>
        /// <param name="d">待执行的搬运任务</param>
        /// <param name="usingCar">输出：选中的小车</param>
        /// <param name="fetchPlan">输出：取货路径规划</param>
        /// <param name="stuckReason">输出：若失败，淤积原因</param>
        /// <returns>true 表示成功选车并完成取货路径规划</returns>
        private bool TrySelectCarAndPlanFetch(Delivery d, out Car usingCar, out SegmentPlan fetchPlan, out string stuckReason)
        {
            usingCar = null;
            fetchPlan = null;
            stuckReason = null;

            // 统计可用车：排除不可调度、类型不匹配、被 hold 到其他站点的车
            var okCars = new List<Car>();
            if (d.UsingCar == null)
            {
                foreach (var car in SimpleLib.GetAllCars().OfType<Car>())
                {
                    if (Commons.SelectCar(car, EnableBlockStandby) < 0) continue;
                    if (d.UsingCar != null && d.UsingCar != car) continue;
                    if (!MeetSelectorKey(car, d.CarType)) continue;
                    if (car.tags.Contains("taskIdsString") && car.tags.TryGetValue("taskIdsString", out var ids) && !ids.Contains(d.TaskId)) continue;
                    if (car.tags.TryGetValue("holdCar", out var holdSite) && holdSite != d.Src.ToString()) continue;
                    okCars.Add(car);
                }
            }
            else
            {
                usingCar = d.UsingCar;
                if (Commons.SelectCar(usingCar, EnableBlockStandby) < 0)
                {
                    stuckReason = $"preassigned using car {usingCar.id} is not available";
                    return false;
                }
                fetchPlan = new SegmentPlan { usingCar = usingCar };
                if(!d.SkipPut) fetchPlan.fields["allow_destination_on_route"] = "true";
                fetchPlan.fields["CarLength"] = d.MaterialLength.ToString(CultureInfo.InvariantCulture);
                fetchPlan.fields["CarWidth"] = d.MaterialWidth.ToString(CultureInfo.InvariantCulture);
                fetchPlan.fields["action"] = "fetch";
                foreach (var kv in d.FetchPlanInfo) fetchPlan.fields[kv.Key] = kv.Value;
                fetchPlan.findLoop = d.SrcFindLoop;
                var carSiteId = usingCar.GetLastSite();
                fetchPlan.FindRoute(SimpleLib.GetSite(carSiteId), SimpleLib.GetSite(d.Src));
                return true;
            }

            if (okCars.Count == 0 && d.UsingCar == null)
            {
                stuckReason = $"no available car, {d.Src}->{d.Dst}";
                return false;
            }


            // 遍历可用车，计算每辆车到取货点的路径权重，选择路径最短的车
            var currentWeight = float.MaxValue;
            foreach (var car in okCars)
            {
                var mPlan = new SegmentPlan { usingCar = car };
                if (!d.SkipPut) mPlan.fields["allow_destination_on_route"] = "true";
                mPlan.fields["CarLength"] = d.MaterialLength.ToString(CultureInfo.InvariantCulture);
                mPlan.fields["CarWidth"] = d.MaterialWidth.ToString(CultureInfo.InvariantCulture);
                mPlan.fields["action"] = "fetch";
                foreach (var kv in d.FetchPlanInfo) mPlan.fields[kv.Key] = kv.Value;

                try
                {
                    var fs = car.GetLastSite();
                    if (fs == -1) continue;
                    mPlan.findLoop = d.SrcFindLoop;
                    var w = mPlan.FindRoute(SimpleLib.GetSite(fs), SimpleLib.GetSite(d.Src));
                    if (w < currentWeight)
                    {
                        currentWeight = w;
                        usingCar = car;
                        fetchPlan = mPlan;
                    }
                }
                catch (BasicHeuristics.PlanningConflictException sde)
                {
                    Diagnosis.Post($"D{d.Id} {car.name}({car.id}) conflict: {ExceptionFormatter.FormatEx(sde)}");
                }
                catch (Exception e)
                {
                    Diagnosis.Post($"D{d.Id} {car.name}({car.id}) exception: {ExceptionFormatter.FormatEx(e)}");
                }
            }

            if (usingCar == null)
            {
                stuckReason = $"no car route to fetch src {d.Src}";
                return false;
            }

            return true;
        }

        /// <summary>
        /// 规划放货路径：从取货点/当前载货点到放货点的路径，支持 DestinationOnRouteException 重试
        /// </summary>
        /// <param name="d">搬运任务</param>
        /// <param name="usingCar">执行任务的小车</param>
        /// <param name="prefetch">是否已完成取货（true 表示取货段已规划或已执行）</param>
        /// <param name="putPlan">输出：放货路径规划</param>
        /// <param name="escPlan">输出：逃逸路径（当前实现中为 null）</param>
        /// <param name="stuckReason">输出：若失败，淤积原因</param>
        /// <returns>true 表示成功规划放货路径</returns>
        private bool TryPlanPut(Delivery d, Car usingCar, bool prefetch, out SegmentPlan putPlan, out SegmentPlan escPlan, out string stuckReason)
        {
            putPlan = null;
            escPlan = null;
            stuckReason = null;

            putPlan = new SegmentPlan { usingCar = usingCar };
            putPlan.fields["action"] = "put";
            foreach (var kv in d.PutPlanInfo) putPlan.fields[kv.Key] = kv.Value;
            putPlan.fields["useMustCanGo"] = prefetch ? "true" : "false";
            putPlan.findLoop = !prefetch || d.DstFindLoop;

            // 确定放货路径起点：SkipFetch 时从载货站点取，否则从取货点 d.Src
            var srcSiteId = d.Src;
            if (d.SkipFetch)
            {
                srcSiteId = usingCar.status.holdingLocks.FirstOrDefault();
                if (srcSiteId == -1)
                {
                    stuckReason = "selected using car is not reset";
                    return false;
                }
            }

            if (d.SkipPut)
                return true;

            // 规划放货路径，最多重试 2 次以处理 DestinationOnRouteException
            for (var attempt = 0; attempt < 2; attempt++)
            {
                try
                {
                    putPlan.fields["allow_destination_on_route"] = "true";
                    putPlan.FindRoute(SimpleLib.GetSite(srcSiteId), SimpleLib.GetSite(d.Dst));
                    return true;
                }
                catch (BasicHeuristics.DestinationOnRouteException)
                {
                    //allowDestinationOnRoute = true;
                }
                catch (BasicHeuristics.PlanningConflictException dce)
                {
                    // 这里保持与原逻辑一致：遇到终点冲突先返回 false 让下一轮再试（推挤/避让可在子类扩展）
                    stuckReason = $"dest {d.Dst} conflict with other cars";
                    Diagnosis.Post($"D{d.Id} dest {d.Dst} conflict: {ExceptionFormatter.FormatEx(dce)}");
                    return false;
                }
                catch (Exception ex)
                {
                    stuckReason = $"no route {srcSiteId}->{d.Dst}: {ex.Message}";
                    return false;
                }
            }

            stuckReason = $"destination {d.Dst} on route, retry failed";
            return false;
         }

        /// <summary>
        /// 编译取货/放货/逃逸段为小车程序、设置小车标签（occupied/deliver/dest）、下发执行
        /// </summary>
        /// <param name="d">搬运任务</param>
        /// <param name="usingCar">执行任务的小车</param>
        /// <param name="fetchPlan">取货路径规划（可为 null）</param>
        /// <param name="putPlan">放货路径规划</param>
        /// <param name="escPlan">逃逸路径规划（可为 null）</param>
        /// <param name="allCode">输出：编译后的小车程序</param>
        /// <param name="stuckReason">输出：若失败，淤积原因</param>
        /// <returns>true 表示编译并下发成功</returns>
        private bool TryCompileAndDispatch(
            Delivery d,
            Car usingCar,
            SegmentPlan fetchPlan,
            SegmentPlan putPlan,
            SegmentPlan escPlan,
            out CarProgram allCode,
            out string stuckReason)
        {
            allCode = null;
            stuckReason = null;

            // 车可用性检查：若车已被占用则不能下发
            lock (Commons.PlanSession)
            {
                if (usingCar.tags.Contains("occupied"))
                {
                    stuckReason = $"{d.Id} selected {usingCar.id} but car not available";
                    return false;
                }
            }

            try
            {
                void AfterFetch(CarProgram.LetGo doLetGo)
                {
                    d.DoneFetch?.Invoke(d);
                    lock (d.SyncStatus) d.Putting = true;
                    PersistSingleDelivery(d);
                    doLetGo();
                }

                void AfterPut(CarProgram.LetGo doLetGo)
                {
                    d.FinishTime = DateTime.Now;
                    d.DonePut?.Invoke(d);
                    PersistSingleDelivery(d);
                    doLetGo();
                }

                var forecastPredicate = new Func<Site, bool>(site => site.fields.ContainsKey(DefineGiveWayType(usingCar)));

                // 根据是否有取货/放货/逃逸段，拼接编译为完整程序并追加避让预测
                if (escPlan == null)
                {
                    if (fetchPlan != null && !d.SkipPut)
                    {
                        allCode = fetchPlan.Compile($"D{d.Id}", false)
                            .Append(putPlan, beforeCall: AfterFetch)
                            .Forecast(Commons.GenerateEscapePlan(putPlan, forecastPredicate));
                    }
                    else
                    {
                        allCode = putPlan.Compile($"D{d.Id}",false)
                            .Forecast(Commons.GenerateEscapePlan(putPlan, forecastPredicate));
                    }
                }
                else
                {
                    if (fetchPlan != null)
                    {
                        allCode = fetchPlan.Compile($"D{d.Id}Esc", false)
                            .Append(putPlan, beforeCall: AfterFetch)
                            .Append(escPlan, beforeCall: AfterPut)
                            .Forecast(Commons.GenerateEscapePlan(putPlan, forecastPredicate));
                    }
                    else
                    {
                        allCode = putPlan.Compile($"D{d.Id}Esc", false)
                            .Append(escPlan, beforeCall: AfterPut)
                            .Forecast(Commons.GenerateEscapePlan(putPlan, forecastPredicate));
                    }
                }

                // 更新小车标签：标记为 occupied/deliver，设置 dest 为放货点或逃逸终点
                lock (Commons.PlanSession)
                {
                    usingCar.tags.Remove("pendingput");
                    usingCar.tags.Add("occupied", $"CDM_{d.Id}");
                    usingCar.tags.Remove("blocking");
                    usingCar.tags.Remove("idle");
                    usingCar.tags.Remove("charging");
                    usingCar.tags.Add("deliver", d.Id);
                    usingCar.tags.Remove("redirect");
                    if (escPlan == null)
                        usingCar.tags.Add("dest", d.Dst.ToString());
                    else
                        usingCar.tags.Add("dest", escPlan.segments.Last().id.ToString());
                }

                d.UsingCar = usingCar;
                stuckReason = $"{usingCar.id} running";
                return true;
            }
            catch (Exception ex)
            {
                // 编译失败：回滚标签，并记录
                Diagnosis.Log($"compile/dispatch failed D{d.Id} car {usingCar.id}: {ExceptionFormatter.FormatEx(ex)}", "error", true);
                lock (Commons.PlanSession)
                {
                    usingCar.tags.Remove("deliver");
                    usingCar.tags.Remove("occupied");
                    usingCar.tags.Remove("dest");
                    usingCar.tags.Add("idle", DateTime.Now.ToString(CultureInfo.CurrentCulture));
                }
                stuckReason = $"compile failed: {ex.Message}";
                return false;
            }
        }

        /// <summary>
        /// 异步执行搬运任务：标记任务激活、下发程序、等待完成、更新 JPH、回调、清理标签与持久化
        /// </summary>
        /// <param name="d">搬运任务</param>
        /// <param name="usingCar">执行任务的小车</param>
        /// <param name="fetchPlan">取货路径规划（可为 null）</param>
        /// <param name="escPlan">逃逸路径规划（可为 null）</param>
        /// <param name="allCode">已编译的小车程序</param>
        private async Task RunDeliveryAsync(
            Delivery d,
            Car usingCar,
            SegmentPlan fetchPlan,
            SegmentPlan escPlan,
            CarProgram allCode)
        {
            try
            {
                lock (d.SyncStatus) d.Active = true;
                d.StartTime = DateTime.Now;
                d.OnStart?.Invoke(d);
                PersistSingleDelivery(d);
                Commons.DeleteTag(tags, "FailCDMToStandby");
                Commons.DeleteTag(tags, "FailCDMToStandbyTime");
                if (fetchPlan != null) LeaveChargeAction(usingCar, fetchPlan.Source);

                try
                {
                    await allCode.Queue();
                }
                catch (Exception ex)
                {
                    // “被 Block() 触发的 not allowed to lock” 属于可预期分支：清理小车标签并返回，不视为错误
                    var msg = ex is AggregateException ae ? ae.InnerExceptions.FirstOrDefault()?.Message ?? ex.Message : ex.Message;
                    Diagnosis.Post($"runner ex: {msg}", "block", true);
                    if (msg.Contains("not allowed to lock"))
                    {
                        lock (d.SyncStatus) d.Active = false;
                        lock (Commons.PlanSession)
                        {
                            usingCar.tags.Remove("deliver");
                            usingCar.tags.Remove("occupied");
                            usingCar.tags.Remove("redirect");
                            usingCar.tags.Remove("dest");
                            usingCar.tags.Add("idle", DateTime.Now.ToString(CultureInfo.CurrentCulture));
                        }
                        return;
                    }

                    Diagnosis.Log($"Send Script to {usingCar.id} exception:{ExceptionFormatter.FormatEx(ex)}", "error", true);
                    lock (d.SyncStatus)
                    {
                        d.Active = false; 
                        if(d.Putting)
                            d.Error = true;
                        else
                        {
                            d.Suspended = true;
                        }
                    }
                    lock (Commons.PlanSession)
                    {
                        usingCar.tags.Remove("deliver");
                        usingCar.tags.Remove("occupied");
                        usingCar.tags.Remove("redirect");
                        usingCar.tags.Remove("dest");
                        usingCar.tags.Add("idle", DateTime.Now.ToString(CultureInfo.CurrentCulture));
                    }
                    d.Failed?.Invoke(d);
                    PersistSingleDelivery(d);
                    return;
                }

                //车体完成所有动作后续处理
                UpdateJphOnSuccess();  // 更新每小时作业数统计

                if (d.AppendedEscape != null)
                {
                    d.FinishTime = DateTime.Now;
                    d.DonePut?.Invoke(d);
                    PersistSingleDelivery(d);
                }
                d.AppendedEscape?.Invoke();

                lock (d.SyncStatus) d.Finished = true;

                lock (Commons.PlanSession)
                {
                    usingCar.tags.Remove("occupied");
                    usingCar.tags.Remove("deliver");
                    usingCar.tags.Remove("dest");
                    usingCar.tags.Add("idle", DateTime.Now.ToString(CultureInfo.CurrentCulture));
                }

                if (d.AppendedEscape == null)
                {
                    d.FinishTime = DateTime.Now;
                    d.DonePut?.Invoke(d);
                }
                d.DoneMission?.Invoke(d);
                RemoveSingleDeliveryFromPersist(d.Id);
            }
            catch (Exception ex)
            {
                Diagnosis.Post($"Delivery(refactored) {d.Id} {d.Src}->{d.Dst} fault:{ExceptionFormatter.FormatEx(ex)}", "ChainedDelivery");
                // 外层异常（如 OnStart/Persist 抛错）需重置任务状态并清理小车标签，避免僵尸任务与占车
                lock (d.SyncStatus) { d.Active = false; d.Error = true; }
                lock (Commons.PlanSession)
                {
                    usingCar.tags.Remove("deliver");
                    usingCar.tags.Remove("occupied");
                    usingCar.tags.Remove("redirect");
                    usingCar.tags.Remove("dest");
                    usingCar.tags.Add("idle", DateTime.Now.ToString(CultureInfo.CurrentCulture));
                }
                d.Failed?.Invoke(d);
                RemoveSingleDeliveryFromPersist(d.Id);
            }
        }

        private DateTime _stTime = DateTime.Now;
        private int _stPerformed;
        private double _prevJph;

        /// <summary>
        /// 任务成功后更新 JPH（每小时作业数）统计，采用滑动窗口指数平滑
        /// </summary>
        private void UpdateJphOnSuccess()
        {
            var stat = (DeliveryMissionStatus)status;
            stat.Performed += 1;
            var passTime = (DateTime.Now - _stTime).TotalHours;
            stat.Jph = _prevJph * (1 - passTime) + (stat.Performed - _stPerformed) * passTime;
            if (passTime > 1)
            {
                _stTime = DateTime.Now;
                _stPerformed = stat.Performed;
                _prevJph = stat.Jph;
            }
        }

        #endregion

        #region Car selection helpers / blocking (kept as compatible utilities)

        /// <summary>
        /// 判断小车是否满足任务指定的车型筛选条件（CarType/SelectorKey）
        /// </summary>
        /// <param name="car">待检查的小车</param>
        /// <param name="carType">任务要求的小车类型（空或 "Car" 表示不限制）</param>
        /// <returns>true 表示满足筛选条件</returns>
        private bool MeetSelectorKey(Car car, string carType)
        {
            if (car is DummyCar) return true;
            if (carType == "Car") return true;
            if (car.fields.ContainsKey("CarType") &&
                ((car.fields["CarType"] == carType) || car.fields["CarType"] == "Car"))
                return true;
            return false;
        }

        /// <summary>
        /// 命令指定小车进入阻塞状态：拦截当前执行、去避让点、重置调度，保持与旧实现一致的锁顺序以降低死锁风险。
        /// </summary>
        /// <param name="carId">小车ID</param>
        /// <param name="taskId">关联任务ID（仅用于日志）</param>
        /// <returns>true 表示命令下发成功</returns>
        public static bool CommandToBlock(int carId, int taskId = -1)
        {
            var car = (Car)SimpleLib.GetCar(carId); 
            int siteId;

            lock (TrafficControl.syncTrafficSequence)
            {
                if (car.status.pendingLocks.Length == 0)
                {
                    Diagnosis.Log($"cannot block {taskId}, destination already locked", "CommandToBlock", true);
                    return false;
                }
                if (car.status.holdingLocks == null || car.status.holdingLocks.Length == 0)
                {
                    Diagnosis.Log($"cannot block {taskId}, car {car.id} has no holdingLocks", "CommandToBlock", true);
                    return false;
                }
                siteId = car.status.holdingLocks.Last();
            }

            car.Intercept(success =>
            {
                if (!success) return;
                lock (Commons.PlanSession)
                {
                    car.NoSchedule(true);
                    car.tags.Add("occupied", "blocking");
                }
                UglyBlock(car);
                car.TrafficReset(SimpleLib.GetSite(siteId), true, false);
                lock (Commons.PlanSession)
                {
                    car.tags.Clear();
                    car.tags.Add("idle", DateTime.Now.ToString(CultureInfo.CurrentCulture));
                }
            });

            return true;
        }

        /// <summary>
        /// 将小车置为“吹散”状态：停止调度、清空站点、清空标签、设为不可调度
        /// </summary>
        private static void BlownCar(Car car)
        {
            car.NoSchedule();
            car.siteID = -1;
            car.tags.Clear();
            car.status.usage.AddUsage("base", new CarUsage.CarUsageInfo { scheduling = false, refreshing = false });
        }

        /// <summary>
        /// 执行阻塞逻辑：调用小车 reset、置为阻塞中、吹散、轮询等待恢复（最长约 60 秒）
        /// </summary>
        private static void UglyBlock(Car usingCar)
        {
            try
            {
                ((ClumsyCar)usingCar).Get("reset");
                ((ClumsyCar)usingCar).lstatus = "阻塞中";
                BlownCar(usingCar);
                const int maxWaitCount = 120;
                var cnt = 0;
                while (cnt < maxWaitCount)
                {
                    cnt++;
                    Thread.Sleep(500);
                    if (usingCar.lstatus.Contains("正常") && cnt > 2) break;
                }

                if (!usingCar.lstatus.Contains("正常"))
                    Diagnosis.Log($"block timeout, ucar:{usingCar.id}, lstatus:{usingCar.lstatus}", "block", true);
            }
            catch (Exception e)
            {
                Diagnosis.Log($"block fail, ucar:{usingCar?.id}, status:{usingCar?.status}, " +
                              $"programs:{usingCar?.status?.programs}, task:{usingCar?.status?.programs?.task}: {ExceptionFormatter.FormatEx(e)}", "block", true);
                try { Thread.Sleep(500); }
                catch
                {
                    // ignored
                }
            }
        }

        #endregion
    }
}
