using System;
using System.Drawing;
using System.Globalization;
using System.Numerics;
using System.Threading;
using ClumsyCore;
using ClumsyCore.DTools;
using ClumsyCore.Interfaces;
using ClumsyCore.Pilot;
using CommonUsage.Chassis;
using MultiWheelC.Control.Execution;
using MultiWheelC.StateEstimation;
using MyParking.Shared;

namespace MultiWheelC
{
    /// <summary>
    /// 统一读取轨迹实验的有符号横向偏移，并将车体局部偏移转换到世界坐标系。
    /// </summary>
    internal static class TrajectoryExperimentInput
    {
        private const double MaximumOffsetCentimeters = 30.0;

        /// <summary>
        /// 从Clumsy输入框读取车体左正右负的横向偏移，单位转换为m。
        /// </summary>
        public static bool TryReadLateralOffsetMeters(
            out double lateralOffsetMeters)
        {
            lateralOffsetMeters = 0.0;
            var input = UI.GetInput(
                "输入轨迹横向偏移（cm，左正右负，范围-30～30）：");
            var parsed = double.TryParse(
                input,
                NumberStyles.Float,
                CultureInfo.CurrentCulture,
                out var offsetCentimeters) ||
                double.TryParse(
                    input,
                    NumberStyles.Float,
                    CultureInfo.InvariantCulture,
                    out offsetCentimeters);

            if (!parsed ||
                double.IsNaN(offsetCentimeters) ||
                double.IsInfinity(offsetCentimeters) ||
                Math.Abs(offsetCentimeters) >
                    MaximumOffsetCentimeters)
            {
                Console.WriteLine(
                    "轨迹横向偏移必须是-30～30cm之间的有限数值，测试未启动。");
                return false;
            }

            lateralOffsetMeters =
                offsetCentimeters / 100.0;
            return true;
        }

        /// <summary>
        /// 沿初始车体左方向平移参考轨迹起点，同时保持世界坐标航向不变。
        /// </summary>
        public static Pose2D OffsetPoseLaterally(
            Pose2D poseInWorld,
            double lateralOffsetMeters)
        {
            var yawRadians = poseInWorld.YawRadians;
            return new Pose2D(
                poseInWorld.XMeters -
                    Math.Sin(yawRadians) *
                    lateralOffsetMeters,
                poseInWorld.YMeters +
                    Math.Cos(yawRadians) *
                    lateralOffsetMeters,
                yawRadians);
        }

        /// <summary>
        /// 生成带毫米偏移标识的实验轨迹名称。
        /// </summary>
        public static string BuildTrajectoryName(
            string baseName,
            double lateralOffsetMeters)
        {
            return baseName +
                   "_Offset" +
                   (lateralOffsetMeters * 1000.0)
                   .ToString("+0;-0;0", CultureInfo.InvariantCulture) +
                   "mm";
        }
    }

    /// <summary>
    /// 从当前Detour位姿开始执行新版控制器4m直线跟踪并保存实验数据。
    /// </summary>
    [MovementTest(name = "新版控制器：4m直线轨迹跟踪")]
    public class NewControllerStraight4mTest
        : MovementTest
    {
        private const float MillimetersPerMeter = 1000f;

        private readonly Painter _painter =
            UI.GetPainter("NewControllerStraight4m");

        private DriveTask _task;
        private TrackingExperimentRecorder _recorder;
        private IVehicleStateProvider _stateProvider;

        /// <summary>
        /// 获取或设置本次测试编号，用于区分重复实验CSV。
        /// </summary>
        public int TrialNumber = 1;

        /// <summary>
        /// 获取或设置4m直线的巡航参考速度，单位为m/s。
        /// </summary>
        public double CruiseSpeedMetersPerSecond = 0.40;

        /// <summary>
        /// 获取或设置参考速度加速度，单位为m/s²。
        /// </summary>
        public double AccelerationMetersPerSecondSquared = 0.20;

        /// <summary>
        /// 获取或设置参考速度减速度，单位为m/s²。
        /// </summary>
        public double DecelerationMetersPerSecondSquared = 0.08;

        /// <summary>
        /// 获取或设置离散轨迹点间距，单位为m。
        /// </summary>
        public double PointSpacingMeters = 0.02;

        /// <summary>
        /// 获取实验记录使用的轨迹基础名称，供同一套直线测试流程区分前进和倒车。
        /// </summary>
        protected virtual string ExperimentTrajectoryBaseName =>
            "ProfiledStraight4m";

        /// <summary>
        /// 获取直线主运动方向相对车头的夹角，单位为rad。
        /// </summary>
        protected virtual double MotionDirectionInBodyRadians =>
            0.0;

        /// <summary>
        /// 获取是否由生成后的轨迹自动推导底盘运动坐标系方向。
        /// </summary>
        protected virtual bool ResolveMotionDirectionFromTrajectory =>
            false;

        /// <summary>
        /// 获取轨迹完成后是否需要将舵轮主动恢复到车头方向。
        /// </summary>
        protected virtual bool ReturnWheelsForwardAfterCompletion =>
            false;

        /// <summary>
        /// 读取当前位姿、绘制离散轨迹并启动新版轨迹跟踪动作。
        /// </summary>
        public override void Test()
        {
            if (_task != null)
            {
                Console.WriteLine(
                    "新版4m直线轨迹测试已经在运行，请先停止当前测试。");
                return;
            }

            if (!TrajectoryExperimentInput
                    .TryReadLateralOffsetMeters(
                        out var lateralOffsetMeters))
            {
                return;
            }

            var chassis =
                PilotDefinition.Chassis as MultiWheelChassis;
            if (chassis == null)
            {
                Console.WriteLine(
                    "当前底盘不是MultiWheelChassis，无法执行新版轨迹测试。");
                return;
            }

            var stateProvider =
                ParkingVehicleStateProviderFactory.Create(
                    chassis);
            if (!stateProvider.TryGetState(
                    out var initialState))
            {
                Console.WriteLine(
                    "无法读取有效停车状态起点位姿：" +
                    stateProvider.LastFailureReason);
                _stateProvider = null;
                return;
            }

            _stateProvider = stateProvider;

            var trajectoryStartPose =
                TrajectoryExperimentInput.OffsetPoseLaterally(
                    initialState.PoseInWorld,
                    lateralOffsetMeters);
            var trajectory =
                TestTrajectoryFactory.CreateStraight4Meters(
                    trajectoryStartPose,
                    CruiseSpeedMetersPerSecond,
                    AccelerationMetersPerSecondSquared,
                    DecelerationMetersPerSecondSquared,
                    PointSpacingMeters,
                    MotionDirectionInBodyRadians);

            DrawTrajectory(trajectory);

            var referenceStart = ToMillimeterVector(
                trajectory.StartPoint.PoseInWorld);
            var referenceEnd = ToMillimeterVector(
                trajectory.EndPoint.PoseInWorld);
            var recorder =
                new TrackingExperimentRecorder(
                    controllerName: "NewStanleyPid",
                    trajectoryName:
                        TrajectoryExperimentInput.BuildTrajectoryName(
                            ExperimentTrajectoryBaseName,
                            lateralOffsetMeters),
                    trialNumber: TrialNumber,
                    referenceStart: referenceStart,
                    referenceEnd: referenceEnd,
                    referenceSpeed:
                        (float)CruiseSpeedMetersPerSecond,
                    sampleIntervalMs: 50,
                    referenceMotionFrameYawDegrees:
                        (float)AngleMath.RadiansToDegrees(
                            MotionDirectionInBodyRadians),
                    referenceAccelerationMetersPerSecondSquared:
                        (float)AccelerationMetersPerSecondSquared,
                    referenceDecelerationMetersPerSecondSquared:
                        (float)DecelerationMetersPerSecondSquared,
                    diagnosticChassis: chassis,
                    diagnosticStateProvider: stateProvider);
            _recorder = recorder;

            var controlPointRadiusMeters =
                chassis.ControlPointRadius /
                MillimetersPerMeter;
            var movement =
                new TrajectoryTrackingMovement
                {
                    Trajectory = trajectory,
                    StateProvider = _stateProvider,
                    MotionDirectionInBodyRadians =
                        ResolveMotionDirectionFromTrajectory
                            ? (double?)null
                            : MotionDirectionInBodyRadians,
                    ReturnWheelsForwardAfterCompletion =
                        ReturnWheelsForwardAfterCompletion,
                    CycleObserver = controller =>
                        RecordControlCycle(
                            recorder,
                            controller,
                            controlPointRadiusMeters,
                            _stateProvider as
                                WheelFeedbackVehicleStateProvider)
                };

            recorder.Start();

            try
            {
                _task = new DriveTask(movement.Get());
                _task.Wait();

                // 保留少量停车后原始Detour数据，并生成一帧处理后的静止状态。
                Thread.Sleep(400);
                recorder.UpdateCommand(0f, 0f);
                if (_stateProvider.TryGetState(
                        out var stoppedState))
                {
                    recorder.UpdateProcessedState(
                        stoppedState);
                }
            }
            finally
            {
                _task?.Stop();
                recorder.UpdateCommand(0f, 0f);
                recorder.StopAndSave();
                _painter.Clear();
                _task = null;
                _recorder = null;
                _stateProvider = null;
            }
        }

        /// <summary>
        /// 停止正在运行的测试、保存已有数据并清除Clumsy轨迹可视化。
        /// </summary>
        public override void TestStop()
        {
            _task?.Stop();
            _recorder?.UpdateCommand(0f, 0f);
            _recorder?.StopAndSave();
            _painter.Clear();
            _task = null;
            _recorder = null;
            _stateProvider = null;
        }

        /// <summary>
        /// 将离散轨迹点和相邻线段从SI单位转换为Clumsy毫米坐标后绘制。
        /// </summary>
        private void DrawTrajectory(
            Trajectory.Trajectory2D trajectory)
        {
            _painter.Clear();

            for (var index = 0;
                 index < trajectory.Count;
                 index++)
            {
                var point = ToMillimeterVector(
                    trajectory[index].PoseInWorld);
                _painter.DrawDot(
                    Color.Cyan,
                    point.X,
                    point.Y,
                    3f);

                if (index == 0)
                {
                    continue;
                }

                var previousPoint = ToMillimeterVector(
                    trajectory[index - 1].PoseInWorld);
                _painter.DrawLine(
                    Color.DeepSkyBlue,
                    previousPoint.X,
                    previousPoint.Y,
                    point.X,
                    point.Y,
                    width: 2);
            }

            var start = ToMillimeterVector(
                trajectory.StartPoint.PoseInWorld);
            var end = ToMillimeterVector(
                trajectory.EndPoint.PoseInWorld);
            _painter.DrawDot(
                Color.LimeGreen,
                start.X,
                start.Y,
                8f);
            _painter.DrawDot(
                Color.OrangeRed,
                end.X,
                end.Y,
                8f);
        }

        /// <summary>
        /// 将控制器本周期使用的状态和最终GCP命令同步给实验记录器。
        /// </summary>
        private static void RecordControlCycle(
            TrackingExperimentRecorder recorder,
            ParkingGeometricController controller,
            double controlPointRadiusMeters,
            WheelFeedbackVehicleStateProvider stateProvider)
        {
            if (controller.LastCycleTiming.HasValue)
            {
                recorder.RecordControlCycleTiming(
                    controller.LastCycleTiming.Value,
                    requestedCommand:
                        controller.LastRequestedCommand,
                    sentCommand:
                        controller.LastCommand);
            }

            if (controller.LastVehicleState.HasValue)
            {
                recorder.UpdateProcessedState(
                    controller.LastVehicleState.Value);
            }

            UpdateVelocityDiagnostics(
                recorder,
                stateProvider);

            if (!controller.LastCommand.HasValue)
            {
                return;
            }

            if (controller.LastProjection.HasValue &&
                controller.LastControlReferenceSpeedMetersPerSecond.HasValue)
            {
                var projection =
                    controller.LastProjection.Value;
                recorder.UpdateControlReference(
                    projection.ArcLengthMeters,
                    controller.LastControlReferenceSpeedMetersPerSecond.Value,
                    projection.LateralErrorMeters,
                    projection.HeadingErrorRadians,
                    projection.DistanceToTrajectoryMeters,
                    projection.RemainingDistanceMeters,
                    controller.LastCurvaturePreviewDistanceMeters ?? 0.0,
                    controller.LastFeedforwardCurvaturePerMeter ??
                    projection.ReferencePoint.CurvaturePerMeter);
            }

            var requestedCommand =
                controller.LastRequestedCommand ??
                controller.LastCommand.Value;
            var command = controller.LastCommand.Value;
            recorder.UpdateGcpCommand(
                requestedCommand.FrontAngleRadians,
                requestedCommand.RearAngleRadians,
                command.FrontAngleRadians,
                command.RearAngleRadians);
            var curvaturePerMeter = Math.Tan(
                command.FrontAngleRadians) /
                controlPointRadiusMeters;
            var angularSpeedRadiansPerSecond =
                command.SpeedMetersPerSecond *
                curvaturePerMeter;

            recorder.UpdateCommand(
                (float)command.SpeedMetersPerSecond,
                (float)angularSpeedRadiansPerSecond);
        }

        /// <summary>
        /// 将同一周期的Detour速度和轮速解算速度写入实验记录器。
        /// </summary>
        private static void UpdateVelocityDiagnostics(
            TrackingExperimentRecorder recorder,
            WheelFeedbackVehicleStateProvider stateProvider)
        {
            if (stateProvider == null ||
                !stateProvider.TryGetLatestVelocityDiagnostics(
                    out var detourBodyVx,
                    out var detourVelocityValid,
                    out var rawWheelBodyVx,
                    out var filteredWheelBodyVx,
                    out var rawWheelBodyVy,
                    out var filteredWheelBodyVy,
                    out var wheelVelocityValid))
            {
                return;
            }

            recorder.UpdateVelocityDiagnostics(
                detourBodyVx,
                detourVelocityValid,
                rawWheelBodyVx,
                filteredWheelBodyVx,
                rawWheelBodyVy,
                filteredWheelBodyVy,
                wheelVelocityValid);
        }

        /// <summary>
        /// 将Shared世界坐标系米制位姿转换为Clumsy绘图和旧记录器使用的毫米坐标。
        /// </summary>
        private static Vector2 ToMillimeterVector(
            Pose2D poseInWorld)
        {
            return new Vector2(
                (float)(
                    poseInWorld.XMeters *
                    MillimetersPerMeter),
                (float)(
                    poseInWorld.YMeters *
                    MillimetersPerMeter));
        }

    }

    /// <summary>
    /// 从当前Detour位姿开始，沿车体后方执行新版控制器4m直线倒车跟踪并保存实验数据。
    /// </summary>
    [MovementTest(name = "新版控制器：4m直线倒车轨迹跟踪")]
    public sealed class NewControllerReverseStraight4mTest
        : NewControllerStraight4mTest
    {
        /// <summary>
        /// 使用负参考速度，使轨迹工厂沿车尾方向生成轨迹并触发倒车控制语义。
        /// </summary>
        public NewControllerReverseStraight4mTest()
        {
            CruiseSpeedMetersPerSecond = -0.40;
        }

        /// <summary>
        /// 将倒车实验与前进直线实验的CSV名称明确区分。
        /// </summary>
        protected override string ExperimentTrajectoryBaseName =>
            "ProfiledReverseStraight4m";
    }

    /// <summary>
    /// 将舵轮准备到车体左前45°，以0.4m/s跟踪4m直线，停车后再恢复车头方向。
    /// </summary>
    [MovementTest(name = "新版控制器：45°蟹行4m直线轨迹跟踪")]
    public sealed class NewControllerCrab45Straight4mTest
        : NewControllerStraight4mTest
    {
        /// <summary>
        /// 使用车体左前45°作为本次直线轨迹的固定运动方向。
        /// </summary>
        protected override double MotionDirectionInBodyRadians =>
            Math.PI / 4.0;

        /// <summary>
        /// 只用45°定义参考轨迹，底盘β由轨迹切线和车身参考航向自动推导。
        /// </summary>
        protected override bool ResolveMotionDirectionFromTrajectory =>
            true;

        /// <summary>
        /// 蟹行轨迹正常完成后主动将四个舵轮恢复到车头方向。
        /// </summary>
        protected override bool ReturnWheelsForwardAfterCompletion =>
            true;

        /// <summary>
        /// 将45°蟹行实验与普通前进和倒车实验的CSV名称明确区分。
        /// </summary>
        protected override string ExperimentTrajectoryBaseName =>
            "ProfiledCrab45Straight4m";
    }

    /// <summary>
    /// 从当前Detour位姿开始执行“3m直线—左半圆—3m直线”新版控制器跟踪实验。
    /// </summary>
    [MovementTest(name = "新版控制器：直线-左半圆-直线轨迹跟踪")]
    public class NewControllerStraightSemicircleStraightTest
        : MovementTest
    {
        private const float MillimetersPerMeter = 1000f;

        private readonly Painter _painter =
            UI.GetPainter(
                "NewControllerStraightSemicircleStraight");

        private DriveTask _task;
        private TrackingExperimentRecorder _recorder;
        private IVehicleStateProvider _stateProvider;

        /// <summary>
        /// 获取或设置本次测试编号，用于区分重复实验CSV。
        /// </summary>
        public int TrialNumber = 1;

        /// <summary>
        /// 获取或设置半圆前后两段直线的长度，单位为m。
        /// </summary>
        public double StraightLengthMeters = 3.0;

        /// <summary>
        /// 获取或设置左转半圆的转弯半径，单位为m。
        /// </summary>
        public double TurnRadiusMeters = 2.0;

        /// <summary>
        /// 获取或设置直线与等曲率转弯之间的曲率过渡长度，单位为m。
        /// </summary>
        public double CurvatureTransitionLengthMeters = 0.70;

        /// <summary>
        /// 获取或设置两段直线的最大参考速度，单位为m/s。
        /// </summary>
        public double StraightMaximumSpeedMetersPerSecond = 0.40;

        /// <summary>
        /// 获取或设置半圆段的最大参考速度，单位为m/s。
        /// </summary>
        public double SemicircleMaximumSpeedMetersPerSecond = 0.30;

        /// <summary>
        /// 获取或设置参考速度加速度，单位为m/s²。
        /// </summary>
        public double AccelerationMetersPerSecondSquared = 0.20;

        /// <summary>
        /// 获取或设置参考速度减速度，单位为m/s²。
        /// </summary>
        public double DecelerationMetersPerSecondSquared = 0.08;

        /// <summary>
        /// 获取或设置离散轨迹点间距，单位为m。
        /// </summary>
        public double PointSpacingMeters = 0.02;

        /// <summary>
        /// 获取组合轨迹主运动方向相对车头的夹角，单位为rad。
        /// </summary>
        protected virtual double MotionDirectionInBodyRadians =>
            0.0;

        /// <summary>
        /// 获取是否由生成后的轨迹自动推导底盘运动坐标系方向。
        /// </summary>
        protected virtual bool ResolveMotionDirectionFromTrajectory =>
            false;

        /// <summary>
        /// 获取轨迹完成后是否需要将舵轮主动恢复到车头方向。
        /// </summary>
        protected virtual bool ReturnWheelsForwardAfterCompletion =>
            false;

        /// <summary>
        /// 获取实验记录使用的轨迹基础名称。
        /// </summary>
        protected virtual string ExperimentTrajectoryBaseName =>
            "ProfiledStraightSmoothLeftTurnStraight";

        /// <summary>
        /// 读取当前位姿、绘制组合轨迹并启动新版轨迹跟踪动作。
        /// </summary>
        public override void Test()
        {
            if (_task != null)
            {
                Console.WriteLine(
                    "新版直线-左半圆-直线测试已经在运行，请先停止当前测试。");
                return;
            }

            if (!TrajectoryExperimentInput
                    .TryReadLateralOffsetMeters(
                        out var lateralOffsetMeters))
            {
                return;
            }

            var chassis =
                PilotDefinition.Chassis as MultiWheelChassis;
            if (chassis == null)
            {
                Console.WriteLine(
                    "当前底盘不是MultiWheelChassis，无法执行新版轨迹测试。");
                return;
            }

            var stateProvider =
                ParkingVehicleStateProviderFactory.Create(
                    chassis);
            if (!stateProvider.TryGetState(
                    out var initialState))
            {
                Console.WriteLine(
                    "无法读取有效停车状态起点位姿：" +
                    stateProvider.LastFailureReason);
                _stateProvider = null;
                return;
            }

            _stateProvider = stateProvider;

            var trajectoryStartPose =
                TrajectoryExperimentInput.OffsetPoseLaterally(
                    initialState.PoseInWorld,
                    lateralOffsetMeters);
            var trajectory =
                TestTrajectoryFactory
                    .CreateStraightLeftSemicircleStraight(
                        trajectoryStartPose,
                        StraightLengthMeters,
                        TurnRadiusMeters,
                        CurvatureTransitionLengthMeters,
                        StraightMaximumSpeedMetersPerSecond,
                        SemicircleMaximumSpeedMetersPerSecond,
                        AccelerationMetersPerSecondSquared,
                        DecelerationMetersPerSecondSquared,
                        PointSpacingMeters,
                        MotionDirectionInBodyRadians);

            DrawTrajectory(trajectory);

            var referenceStart = ToMillimeterVector(
                trajectory.StartPoint.PoseInWorld);
            var referenceEnd = ToMillimeterVector(
                trajectory.EndPoint.PoseInWorld);
            var recorder =
                new TrackingExperimentRecorder(
                    controllerName: "NewStanleyPid",
                    trajectoryName:
                        TrajectoryExperimentInput.BuildTrajectoryName(
                            ExperimentTrajectoryBaseName,
                            lateralOffsetMeters),
                    trialNumber: TrialNumber,
                    referenceStart: referenceStart,
                    referenceEnd: referenceEnd,
                    referenceSpeed:
                        (float)StraightMaximumSpeedMetersPerSecond,
                    sampleIntervalMs: 50,
                    referenceMotionFrameYawDegrees:
                        (float)AngleMath.RadiansToDegrees(
                            MotionDirectionInBodyRadians),
                    referenceAccelerationMetersPerSecondSquared:
                        (float)AccelerationMetersPerSecondSquared,
                    referenceDecelerationMetersPerSecondSquared:
                        (float)DecelerationMetersPerSecondSquared,
                    diagnosticChassis: chassis,
                    diagnosticStateProvider: stateProvider);
            _recorder = recorder;

            var controlPointRadiusMeters =
                chassis.ControlPointRadius /
                MillimetersPerMeter;
            var movement =
                new TrajectoryTrackingMovement
                {
                    Trajectory = trajectory,
                    StateProvider = _stateProvider,
                    MotionDirectionInBodyRadians =
                        ResolveMotionDirectionFromTrajectory
                            ? (double?)null
                            : MotionDirectionInBodyRadians,
                    ReturnWheelsForwardAfterCompletion =
                        ReturnWheelsForwardAfterCompletion,
                    CycleObserver = controller =>
                        RecordControlCycle(
                            recorder,
                            controller,
                            controlPointRadiusMeters,
                            _stateProvider as
                                WheelFeedbackVehicleStateProvider)
                };

            recorder.Start();

            try
            {
                _task = new DriveTask(movement.Get());
                _task.Wait();

                // 保留少量停车后原始Detour数据，并生成一帧处理后的静止状态。
                Thread.Sleep(400);
                recorder.UpdateCommand(0f, 0f);
                if (_stateProvider.TryGetState(
                        out var stoppedState))
                {
                    recorder.UpdateProcessedState(
                        stoppedState);
                }
            }
            finally
            {
                _task?.Stop();
                recorder.UpdateCommand(0f, 0f);
                recorder.StopAndSave();
                _painter.Clear();
                _task = null;
                _recorder = null;
                _stateProvider = null;
            }
        }

        /// <summary>
        /// 停止组合轨迹测试、保存已有数据并清除Clumsy轨迹可视化。
        /// </summary>
        public override void TestStop()
        {
            _task?.Stop();
            _recorder?.UpdateCommand(0f, 0f);
            _recorder?.StopAndSave();
            _painter.Clear();
            _task = null;
            _recorder = null;
            _stateProvider = null;
        }

        /// <summary>
        /// 将组合轨迹的离散点和相邻线段转换为Clumsy毫米坐标后绘制。
        /// </summary>
        private void DrawTrajectory(
            Trajectory.Trajectory2D trajectory)
        {
            _painter.Clear();

            for (var index = 0;
                 index < trajectory.Count;
                 index++)
            {
                var point = ToMillimeterVector(
                    trajectory[index].PoseInWorld);
                _painter.DrawDot(
                    Color.Cyan,
                    point.X,
                    point.Y,
                    3f);

                if (index == 0)
                {
                    continue;
                }

                var previousPoint = ToMillimeterVector(
                    trajectory[index - 1].PoseInWorld);
                _painter.DrawLine(
                    Color.DeepSkyBlue,
                    previousPoint.X,
                    previousPoint.Y,
                    point.X,
                    point.Y,
                    width: 2);
            }

            var start = ToMillimeterVector(
                trajectory.StartPoint.PoseInWorld);
            var end = ToMillimeterVector(
                trajectory.EndPoint.PoseInWorld);
            _painter.DrawDot(
                Color.LimeGreen,
                start.X,
                start.Y,
                8f);
            _painter.DrawDot(
                Color.OrangeRed,
                end.X,
                end.Y,
                8f);
        }

        /// <summary>
        /// 将组合轨迹控制周期的状态、参考量和最终GCP命令同步给实验记录器。
        /// </summary>
        private static void RecordControlCycle(
            TrackingExperimentRecorder recorder,
            ParkingGeometricController controller,
            double controlPointRadiusMeters,
            WheelFeedbackVehicleStateProvider stateProvider)
        {
            if (controller.LastCycleTiming.HasValue)
            {
                recorder.RecordControlCycleTiming(
                    controller.LastCycleTiming.Value,
                    requestedCommand:
                        controller.LastRequestedCommand,
                    sentCommand:
                        controller.LastCommand);
            }

            if (controller.LastVehicleState.HasValue)
            {
                recorder.UpdateProcessedState(
                    controller.LastVehicleState.Value);
            }

            UpdateVelocityDiagnostics(
                recorder,
                stateProvider);

            if (!controller.LastCommand.HasValue)
            {
                return;
            }

            if (controller.LastProjection.HasValue &&
                controller.LastControlReferenceSpeedMetersPerSecond.HasValue)
            {
                var projection =
                    controller.LastProjection.Value;
                recorder.UpdateControlReference(
                    projection.ArcLengthMeters,
                    controller.LastControlReferenceSpeedMetersPerSecond.Value,
                    projection.LateralErrorMeters,
                    projection.HeadingErrorRadians,
                    projection.DistanceToTrajectoryMeters,
                    projection.RemainingDistanceMeters,
                    controller.LastCurvaturePreviewDistanceMeters ?? 0.0,
                    controller.LastFeedforwardCurvaturePerMeter ??
                    projection.ReferencePoint.CurvaturePerMeter);
            }

            var requestedCommand =
                controller.LastRequestedCommand ??
                controller.LastCommand.Value;
            var command = controller.LastCommand.Value;
            recorder.UpdateGcpCommand(
                requestedCommand.FrontAngleRadians,
                requestedCommand.RearAngleRadians,
                command.FrontAngleRadians,
                command.RearAngleRadians);
            var curvaturePerMeter = Math.Tan(
                command.FrontAngleRadians) /
                controlPointRadiusMeters;
            var angularSpeedRadiansPerSecond =
                command.SpeedMetersPerSecond *
                curvaturePerMeter;

            recorder.UpdateCommand(
                (float)command.SpeedMetersPerSecond,
                (float)angularSpeedRadiansPerSecond);
        }

        /// <summary>
        /// 将同一周期的Detour速度和轮速解算速度写入实验记录器。
        /// </summary>
        private static void UpdateVelocityDiagnostics(
            TrackingExperimentRecorder recorder,
            WheelFeedbackVehicleStateProvider stateProvider)
        {
            if (stateProvider == null ||
                !stateProvider.TryGetLatestVelocityDiagnostics(
                    out var detourBodyVx,
                    out var detourVelocityValid,
                    out var rawWheelBodyVx,
                    out var filteredWheelBodyVx,
                    out var rawWheelBodyVy,
                    out var filteredWheelBodyVy,
                    out var wheelVelocityValid))
            {
                return;
            }

            recorder.UpdateVelocityDiagnostics(
                detourBodyVx,
                detourVelocityValid,
                rawWheelBodyVx,
                filteredWheelBodyVx,
                rawWheelBodyVy,
                filteredWheelBodyVy,
                wheelVelocityValid);
        }

        /// <summary>
        /// 将Shared世界坐标系米制位姿转换为Clumsy绘图和记录器使用的毫米坐标。
        /// </summary>
        private static Vector2 ToMillimeterVector(
            Pose2D poseInWorld)
        {
            return new Vector2(
                (float)(
                    poseInWorld.XMeters *
                    MillimetersPerMeter),
                (float)(
                    poseInWorld.YMeters *
                    MillimetersPerMeter));
        }
    }

    /// <summary>
    /// 将舵轮准备到车体左前45°，跟踪直线—左半圆—直线轨迹，并在停车后恢复车头方向。
    /// </summary>
    [MovementTest(name = "新版控制器：45°蟹行直线-左半圆-直线轨迹跟踪")]
    public sealed class NewControllerCrab45StraightSemicircleStraightTest
        : NewControllerStraightSemicircleStraightTest
    {
        /// <summary>
        /// 使用车体左前45°作为组合轨迹的固定运动方向。
        /// </summary>
        protected override double MotionDirectionInBodyRadians =>
            Math.PI / 4.0;

        /// <summary>
        /// 只用45°定义参考轨迹，底盘β由整段轨迹自动推导并检查一致性。
        /// </summary>
        protected override bool ResolveMotionDirectionFromTrajectory =>
            true;

        /// <summary>
        /// 蟹行组合轨迹正常完成后主动将四个舵轮恢复到车头方向。
        /// </summary>
        protected override bool ReturnWheelsForwardAfterCompletion =>
            true;

        /// <summary>
        /// 将45°蟹行组合实验与普通组合轨迹实验的CSV名称明确区分。
        /// </summary>
        protected override string ExperimentTrajectoryBaseName =>
            "ProfiledCrab45StraightSmoothLeftTurnStraight";
    }
}
