using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MultiWheelC.TrajectoryPlanning.PathSmoothing;
using MultiWheelC.TrajectoryPlanning.Utils;

namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Processing;

/// <summary>按单一方向段的弧长线性插值并保留精确锚点的确定性重采样器。</summary>
public sealed class ArcLengthResampler
{
    private const double Tolerance = 1e-10d;

    /// <summary>以目标间距重采样单一方向段；段首/段末精确锚点始终原样保留。</summary>
    /// <param name="points">同一方向段内按严格递增局部弧长排序的点集合；位置和弧长单位为 m，航向为 rad。</param>
    /// <param name="spacingMeters">目标采样间距，单位 m，必须为有限正数。</param>
    /// <param name="resampled">成功时为只读重采样点集合；失败时为空只读集合。</param>
    /// <param name="reason">失败时描述非法点、退化几何或间距；成功时为空字符串。</param>
    /// <returns>输入属于一个有效同方向区间且可安全插值时为 <see langword="true"/>；否则为 <see langword="false"/>。</returns>
    public bool TryResample(
        IReadOnlyList<SmoothingPoint2D> points,
        double spacingMeters,
        out IReadOnlyList<SmoothingPoint2D> resampled,
        out string reason)
    {
        resampled = EmptyPoints();
        reason = string.Empty;
        if (points == null || points.Count == 0 || !NumericGuard.IsPositiveFinite(spacingMeters))
        {
            reason = "重采样点集或采样间距无效。";
            return false;
        }

        for (int index = 0; index < points.Count; index++)
        {
            if (!IsValidPoint(points[index]))
            {
                reason = "重采样输入包含非法数值。";
                return false;
            }
            if (index == 0) continue;

            SmoothingPoint2D previous = points[index - 1];
            SmoothingPoint2D current = points[index];
            if (current.ArcLength <= previous.ArcLength + Tolerance)
            {
                reason = "同一方向段的弧长必须严格增加。";
                return false;
            }
            double distance = Distance(previous, current);
            if (!NumericGuard.IsFinite(distance) || distance <= Tolerance)
            {
                reason = "同一方向段中不允许重复位姿或数值溢出的距离。";
                return false;
            }
        }

        if (points.Count == 1)
        {
            resampled = CopyReadOnly(points);
            return true;
        }

        var output = new List<SmoothingPoint2D> { points[0] };
        double firstArc = points[0].ArcLength;
        double finalArc = points[points.Count - 1].ArcLength;
        int rightIndex = 1;
        for (double targetArc = firstArc + spacingMeters;
             targetArc < finalArc - Tolerance;
             targetArc += spacingMeters)
        {
            while (rightIndex < points.Count - 1 && points[rightIndex].ArcLength < targetArc)
                rightIndex++;

            SmoothingPoint2D left = points[rightIndex - 1];
            SmoothingPoint2D right = points[rightIndex];
            double ratio = (targetArc - left.ArcLength) / (right.ArcLength - left.ArcLength);
            double unwrappedHeading = left.UnwrappedHeading +
                ratio * (right.UnwrappedHeading - left.UnwrappedHeading);
            output.Add(new SmoothingPoint2D(
                left.X + ratio * (right.X - left.X),
                left.Y + ratio * (right.Y - left.Y),
                targetArc,
                AngleMath.NormalizeRadians(unwrappedHeading),
                unwrappedHeading,
                Math.Min(left.BodyClearance, right.BodyClearance),
                false,
                SmoothedPathPointSource.Interpolated));
        }

        // Appending the original object, rather than interpolating at the final arc, preserves the exact endpoint.
        output.Add(points[points.Count - 1]);
        resampled = new ReadOnlyCollection<SmoothingPoint2D>(output);
        return true;
    }

    /// <summary>重采样完整预处理方向段并保留段编号、方向和换向拓扑标记。</summary>
    /// <param name="segment">待重采样的单一前进或倒车方向段。</param>
    /// <param name="spacingMeters">目标局部弧长间距，单位 m。</param>
    /// <param name="resampled">成功时为新不可变预处理方向段；失败时为 <see langword="null"/>。</param>
    /// <param name="reason">失败原因；成功时为空字符串。</param>
    /// <returns>底层点集重采样成功时为 <see langword="true"/>；否则为 <see langword="false"/>。</returns>
    public bool TryResample(
        PreparedDirectionSegment segment,
        double spacingMeters,
        out PreparedDirectionSegment resampled,
        out string reason)
    {
        resampled = null;
        reason = string.Empty;
        if (segment == null)
        {
            reason = "待重采样方向段为空。";
            return false;
        }
        if (!TryResample(segment.Points, spacingMeters, out IReadOnlyList<SmoothingPoint2D> points, out reason))
            return false;

        resampled = new PreparedDirectionSegment(
            segment.SegmentIndex,
            segment.Direction,
            points,
            segment.StartsAtGearSwitch,
            segment.EndsAtGearSwitch,
            segment.StartVehicleCurvaturePerMeter);
        return true;
    }

    private static bool IsValidPoint(SmoothingPoint2D point)
    {
        return point != null && NumericGuard.IsFinite(point.X) && NumericGuard.IsFinite(point.Y) &&
            NumericGuard.IsFinite(point.ArcLength) && NumericGuard.IsFinite(point.Heading) &&
            NumericGuard.IsFinite(point.UnwrappedHeading) && NumericGuard.IsFinite(point.BodyClearance) &&
            point.BodyClearance >= 0d;
    }

    private static double Distance(SmoothingPoint2D left, SmoothingPoint2D right)
    {
        double x = right.X - left.X;
        double y = right.Y - left.Y;
        return Math.Sqrt(x * x + y * y);
    }

    private static IReadOnlyList<T> CopyReadOnly<T>(IReadOnlyList<T> source)
    {
        var copy = new List<T>(source.Count);
        for (int index = 0; index < source.Count; index++) copy.Add(source[index]);
        return new ReadOnlyCollection<T>(copy);
    }

    private static IReadOnlyList<SmoothingPoint2D> EmptyPoints()
    {
        return new ReadOnlyCollection<SmoothingPoint2D>(new List<SmoothingPoint2D>());
    }
}
