using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MultiWheelC.TrajectoryPlanning.CoarsePath.Search;
using MultiWheelC.TrajectoryPlanning.CoarsePath.Vehicle;
using MultiWheelC.TrajectoryPlanning.Utils;

namespace MultiWheelC.TrajectoryPlanning.CoarsePath.Output;

/// <summary>
/// 将回溯原语装配为调用方可消费的稠密路径和包含式方向分段。
/// 装配器保留原语边界的换向双点，其余相邻重复位姿会被删除。
/// </summary>
public sealed class CoarsePathAssembler
{
    private const double DuplicateTolerance = 1e-8d;
    private readonly FootprintCollisionChecker _collisionChecker;

    /// <summary>创建使用默认连续车体检查器的路径装配器。</summary>
    public CoarsePathAssembler()
        : this(new FootprintCollisionChecker())
    {
    }

    /// <summary>创建使用指定连续车体检查器的路径装配器。</summary>
    public CoarsePathAssembler(FootprintCollisionChecker collisionChecker)
    {
        _collisionChecker = collisionChecker ?? throw new ArgumentNullException(nameof(collisionChecker));
    }

    /// <summary>
    /// 从已回溯的恒曲率原语构造稠密路径。
    /// 参数：backtrackedPath 提供原语顺序；request 提供地图和车辆；path、segments 为成功时的只读输出。
    /// 返回：首点可通过连续车体检查、每个原语积分点数据一致且方向分段完整覆盖时为 true；否则返回 false 且输出为空。
    /// </summary>
    public bool TryAssemble(BacktrackedPath backtrackedPath, PlanningRequest request,
        out IReadOnlyList<CoarsePathPoint> path, out IReadOnlyList<PathSegment> segments, out string failureReason)
    {
        path = EmptyPath();
        segments = EmptySegments();
        failureReason = string.Empty;
        if (backtrackedPath == null || request == null || request.Map == null || request.Vehicle == null ||
            !IsFinitePose(backtrackedPath.Start) || !IsTravelDirection(backtrackedPath.StartDirection) ||
            !NumericGuard.IsFinite(backtrackedPath.StartCurvaturePerMeter))
        {
            failureReason = "路径装配输入无效。";
            return false;
        }

        if (!_collisionChecker.IsPoseCollisionFree(backtrackedPath.Start, request.Map, request.Vehicle, 0d, out double startClearanceMeters))
        {
            failureReason = "回溯路径起点未通过连续车体检查。";
            return false;
        }

        var points = new List<CoarsePathPoint>();
        TravelDirection currentDirection = backtrackedPath.Primitives.Count > 0
            ? backtrackedPath.Primitives[0].Direction
            : backtrackedPath.StartDirection;
        double currentCurvature = request.StartVehicleCurvature;
        double normalizedStartHeading = AngleMath.NormalizeRadians(backtrackedPath.Start.Heading);
        if (!NumericGuard.IsFinite(normalizedStartHeading))
        {
            failureReason = "回溯路径起点航向无效。";
            return false;
        }

        points.Add(new CoarsePathPoint(backtrackedPath.Start.X, backtrackedPath.Start.Y, normalizedStartHeading,
            backtrackedPath.Start.Heading, 0d, currentDirection, currentCurvature, startClearanceMeters, false,
            CoarsePathPointSource.Start));

        for (int primitiveIndex = 0; primitiveIndex < backtrackedPath.Primitives.Count; primitiveIndex++)
        {
            MotionPrimitive primitive = backtrackedPath.Primitives[primitiveIndex];
            if (!IsValidPrimitive(primitive))
            {
                failureReason = "回溯路径包含无效原语。";
                return false;
            }

            CoarsePathPoint lastPoint = points[points.Count - 1];
            if (primitive.Direction != lastPoint.Direction)
            {
                // 换向处的旧方向终点和新方向起点必须共存，二者位置、航向、弧长完全相同。
                points.Add(new CoarsePathPoint(lastPoint.X, lastPoint.Y, lastPoint.Heading, lastPoint.UnwrappedHeading,
                    lastPoint.ArcLength, primitive.Direction, primitive.CurvaturePerMeter, lastPoint.BodyClearance,
                    true, CoarsePathPointSource.MotionPrimitive));
            }

            Pose2D previousPose = primitive.Start;
            for (int pointIndex = 0; pointIndex < primitive.Points.Count; pointIndex++)
            {
                Pose2D pose = primitive.Points[pointIndex];
                double bodyClearanceMeters = primitive.BodyClearancesMeters[pointIndex];
                if (!IsFinitePose(pose) || !IsValidClearance(bodyClearanceMeters))
                {
                    failureReason = "原语积分点或净空无效。";
                    return false;
                }

                CoarsePathPoint previousPoint = points[points.Count - 1];
                double arcIncrementMeters = CalculateArcIncrement(previousPose, pose, primitive.CurvaturePerMeter);
                if (!NumericGuard.IsFinite(arcIncrementMeters) || arcIncrementMeters <= 0d)
                {
                    failureReason = "原语积分点未产生正弧长。";
                    return false;
                }

                if (IsSamePose(previousPoint, pose))
                {
                    // 非换向情况下不允许重复采样点泄露到对外路径。
                    previousPose = pose;
                    continue;
                }

                double normalizedHeading = AngleMath.NormalizeRadians(pose.Heading);
                double headingDelta = AngleMath.ShortestSignedDifference(previousPoint.Heading, normalizedHeading);
                if (!NumericGuard.IsFinite(normalizedHeading) || !NumericGuard.IsFinite(headingDelta))
                {
                    failureReason = "原语积分点航向无法展开。";
                    return false;
                }

                CoarsePathPointSource source = primitive.IsGoalTruncation && pointIndex == primitive.Points.Count - 1
                    ? CoarsePathPointSource.GoalTruncation
                    : CoarsePathPointSource.MotionPrimitive;
                points.Add(new CoarsePathPoint(pose.X, pose.Y, normalizedHeading,
                    previousPoint.UnwrappedHeading + headingDelta, previousPoint.ArcLength + arcIncrementMeters,
                    primitive.Direction, primitive.CurvaturePerMeter, bodyClearanceMeters, false, source));
                previousPose = pose;
            }
        }

        if (points.Count == 0)
        {
            failureReason = "路径装配未产生起点。";
            return false;
        }

        path = new ReadOnlyCollection<CoarsePathPoint>(points);
        segments = BuildSegments(points);
        return true;
    }

    private static IReadOnlyList<PathSegment> BuildSegments(IReadOnlyList<CoarsePathPoint> points)
    {
        var segments = new List<PathSegment>();
        int startIndex = 0;
        TravelDirection direction = points[0].Direction;
        for (int index = 1; index < points.Count; index++)
        {
            if (points[index].Direction == direction) continue;
            segments.Add(new PathSegment(segments.Count, direction, startIndex, index - 1,
                points[startIndex].IsGearSwitchPoint, true));
            startIndex = index;
            direction = points[index].Direction;
        }

        segments.Add(new PathSegment(segments.Count, direction, startIndex, points.Count - 1,
            points[startIndex].IsGearSwitchPoint, false));
        return new ReadOnlyCollection<PathSegment>(segments);
    }

    private static double CalculateArcIncrement(Pose2D from, Pose2D to, double curvaturePerMeter)
    {
        if (!IsFinitePose(from) || !IsFinitePose(to) || !NumericGuard.IsFinite(curvaturePerMeter)) return double.NaN;
        if (Math.Abs(curvaturePerMeter) < 1e-12d)
        {
            double deltaX = to.X - from.X;
            double deltaY = to.Y - from.Y;
            return Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
        }

        double headingDelta = AngleMath.ShortestSignedDifference(from.Heading, to.Heading);
        return Math.Abs(headingDelta / curvaturePerMeter);
    }

    private static bool IsValidPrimitive(MotionPrimitive primitive)
    {
        return primitive != null && primitive.Start != null && primitive.Points != null && primitive.BodyClearancesMeters != null &&
            primitive.Points.Count == primitive.BodyClearancesMeters.Count && primitive.Points.Count > 0 &&
            IsTravelDirection(primitive.Direction) && NumericGuard.IsFinite(primitive.CurvaturePerMeter) &&
            NumericGuard.IsPositiveFinite(primitive.ActualLengthMeters);
    }

    private static bool IsSamePose(CoarsePathPoint point, Pose2D pose)
    {
        return Math.Abs(point.X - pose.X) <= DuplicateTolerance && Math.Abs(point.Y - pose.Y) <= DuplicateTolerance &&
            Math.Abs(AngleMath.ShortestSignedDifference(point.Heading, pose.Heading)) <= DuplicateTolerance;
    }

    private static bool IsFinitePose(Pose2D pose)
    {
        return pose != null && NumericGuard.IsFinite(pose.X) && NumericGuard.IsFinite(pose.Y) && NumericGuard.IsFinite(pose.Heading);
    }

    private static bool IsTravelDirection(TravelDirection direction)
    {
        return direction == TravelDirection.Forward || direction == TravelDirection.Reverse;
    }

    private static bool IsValidClearance(double clearanceMeters)
    {
        return !double.IsNaN(clearanceMeters) && clearanceMeters >= 0d;
    }

    private static IReadOnlyList<CoarsePathPoint> EmptyPath()
    {
        return new ReadOnlyCollection<CoarsePathPoint>(new List<CoarsePathPoint>());
    }

    private static IReadOnlyList<PathSegment> EmptySegments()
    {
        return new ReadOnlyCollection<PathSegment>(new List<PathSegment>());
    }
}
