using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MultiWheelC.TrajectoryPlanning.PathSmoothing.Output.Comparison;

namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Output.Visualization;

/// <summary>SVG 和 PNG 共享的不可变路径平滑报告图形模型。</summary>
public sealed class SmoothingFigureModel
{
    internal SmoothingFigureModel(
        string scenarioId,
        string scenarioLabel,
        double worldXMinMeters,
        double worldXMaxMeters,
        double worldYMinMeters,
        double worldYMaxMeters,
        double pathPanelX,
        double pathPanelY,
        double pathPanelWidth,
        double pathPanelHeight,
        double curvaturePanelX,
        double curvaturePanelY,
        double curvaturePanelWidth,
        double curvaturePanelHeight,
        double metricsPanelX,
        double metricsPanelY,
        double metricsPanelWidth,
        double metricsPanelHeight,
        IReadOnlyList<SmoothingFigureObstacle> obstacles,
        IReadOnlyList<SmoothingFigureSeries> series,
        IReadOnlyList<SmoothingFigureMetricRow> metricRows,
        SmoothingFigurePoint start,
        SmoothingFigurePoint goal)
    {
        ScenarioId = scenarioId ?? string.Empty;
        ScenarioLabel = scenarioLabel ?? string.Empty;
        WorldXMinMeters = worldXMinMeters;
        WorldXMaxMeters = worldXMaxMeters;
        WorldYMinMeters = worldYMinMeters;
        WorldYMaxMeters = worldYMaxMeters;
        PathPanelX = pathPanelX;
        PathPanelY = pathPanelY;
        PathPanelWidth = pathPanelWidth;
        PathPanelHeight = pathPanelHeight;
        CurvaturePanelX = curvaturePanelX;
        CurvaturePanelY = curvaturePanelY;
        CurvaturePanelWidth = curvaturePanelWidth;
        CurvaturePanelHeight = curvaturePanelHeight;
        MetricsPanelX = metricsPanelX;
        MetricsPanelY = metricsPanelY;
        MetricsPanelWidth = metricsPanelWidth;
        MetricsPanelHeight = metricsPanelHeight;
        Obstacles = Copy(obstacles);
        Series = Copy(series);
        MetricRows = Copy(metricRows);
        Start = start ?? throw new ArgumentNullException(nameof(start));
        Goal = goal ?? throw new ArgumentNullException(nameof(goal));
    }

    public string ScenarioId { get; }
    public string ScenarioLabel { get; }
    public double FigureWidthPoints => IeeeFigureStyle.FigureWidthPoints;
    public double FigureHeightPoints => IeeeFigureStyle.FigureHeightPoints;
    public string PathPanelLabel => "(a)";
    public string CurvaturePanelLabel => "(b)";
    public string MetricsPanelLabel => "(c)";
    public double WorldXMinMeters { get; }
    public double WorldXMaxMeters { get; }
    public double WorldYMinMeters { get; }
    public double WorldYMaxMeters { get; }
    public double PathPanelX { get; }
    public double PathPanelY { get; }
    public double PathPanelWidth { get; }
    public double PathPanelHeight { get; }
    public double CurvaturePanelX { get; }
    public double CurvaturePanelY { get; }
    public double CurvaturePanelWidth { get; }
    public double CurvaturePanelHeight { get; }
    public double MetricsPanelX { get; }
    public double MetricsPanelY { get; }
    public double MetricsPanelWidth { get; }
    public double MetricsPanelHeight { get; }
    public double PathScaleX { get; internal set; }
    public double PathScaleY { get; internal set; }
    public IReadOnlyList<SmoothingFigureObstacle> Obstacles { get; }
    public IReadOnlyList<SmoothingFigureSeries> Series { get; }
    public IReadOnlyList<SmoothingFigureLegendEntry> LegendEntries => BuildLegend(Series);
    public IReadOnlyList<SmoothingFigureMetricRow> MetricRows { get; }
    public SmoothingFigurePoint Start { get; }
    public SmoothingFigurePoint Goal { get; }

    internal SmoothingFigureModel WithAdditionalSeries(SmoothingFigureSeries series)
    {
        if (series == null) throw new ArgumentNullException(nameof(series));
        var combined = new List<SmoothingFigureSeries>(Series.Count + 1);
        for (int index = 0; index < Series.Count; index++)
        {
            if (Series[index].Key == series.Key)
                throw new ArgumentException("Figure series keys must be unique.", nameof(series));
            combined.Add(Series[index]);
        }
        combined.Add(series);
        var copy = new SmoothingFigureModel(
            ScenarioId, ScenarioLabel, WorldXMinMeters, WorldXMaxMeters, WorldYMinMeters, WorldYMaxMeters,
            PathPanelX, PathPanelY, PathPanelWidth, PathPanelHeight,
            CurvaturePanelX, CurvaturePanelY, CurvaturePanelWidth, CurvaturePanelHeight,
            MetricsPanelX, MetricsPanelY, MetricsPanelWidth, MetricsPanelHeight,
            Obstacles, combined, MetricRows, Start, Goal)
        {
            PathScaleX = PathScaleX,
            PathScaleY = PathScaleY,
        };
        return copy;
    }

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

    private static IReadOnlyList<SmoothingFigureLegendEntry> BuildLegend(IReadOnlyList<SmoothingFigureSeries> series)
    {
        var legend = new List<SmoothingFigureLegendEntry>(series == null ? 0 : series.Count);
        if (series != null)
        {
            for (int index = 0; index < series.Count; index++)
                legend.Add(new SmoothingFigureLegendEntry(series[index].Label, series[index].Color, series[index].DashArray));
        }
        return new ReadOnlyCollection<SmoothingFigureLegendEntry>(legend);
    }
}

public sealed class SmoothingFigurePoint
{
    public SmoothingFigurePoint(double xMeters, double yMeters, double arcLengthMeters, double vehicleCurvaturePerMeter)
    {
        X = xMeters; Y = yMeters; ArcLength = arcLengthMeters; VehicleCurvature = vehicleCurvaturePerMeter;
    }
    public double X { get; }
    public double Y { get; }
    public double ArcLength { get; }
    public double VehicleCurvature { get; }
}

public sealed class SmoothingFigureObstacle
{
    public SmoothingFigureObstacle(double xMeters, double yMeters, double widthMeters, double heightMeters)
    {
        X = xMeters; Y = yMeters; Width = widthMeters; Height = heightMeters;
    }
    public double X { get; }
    public double Y { get; }
    public double Width { get; }
    public double Height { get; }
}

public sealed class SmoothingFigureSeries
{
    internal SmoothingFigureSeries(SmoothingMethod? method, string key, string label, PathSmoothingStatus status, string color, string dashArray,
        bool isRawPathBaseline, IReadOnlyList<SmoothingFigurePoint> points, IReadOnlyList<SmoothingFigurePoint> violationMarkers)
    {
        Method = method; Key = key ?? string.Empty; Label = label ?? string.Empty; Status = status; Color = color ?? string.Empty;
        DashArray = dashArray ?? string.Empty; IsRawPathBaseline = isRawPathBaseline; Points = Copy(points); ViolationMarkers = Copy(violationMarkers);
    }
    public SmoothingMethod? Method { get; }
    public string Key { get; }
    public string Label { get; }
    public PathSmoothingStatus Status { get; }
    public string Color { get; }
    public string DashArray { get; }
    public bool IsRawPathBaseline { get; }
    public bool IsCurveVisible => Points.Count > 0;
    public IReadOnlyList<SmoothingFigurePoint> Points { get; }
    public IReadOnlyList<SmoothingFigurePoint> ViolationMarkers { get; }
    private static IReadOnlyList<T> Copy<T>(IReadOnlyList<T> source)
    {
        var copy = new List<T>(source == null ? 0 : source.Count);
        if (source != null) for (int index = 0; index < source.Count; index++) copy.Add(source[index]);
        return new ReadOnlyCollection<T>(copy);
    }
}

public sealed class SmoothingFigureLegendEntry
{
    internal SmoothingFigureLegendEntry(string label, string color, string dashArray) { Label = label ?? string.Empty; Color = color ?? string.Empty; DashArray = dashArray ?? string.Empty; }
    public string Label { get; }
    public string Color { get; }
    public string DashArray { get; }
}

public sealed class SmoothingFigureMetricRow
{
    internal SmoothingFigureMetricRow(string method, string label, PathSmoothingStatus status, PathQualityMetrics metrics,
        SmoothingTimingSummary timing)
    {
        Method = method ?? string.Empty; Label = label ?? string.Empty; Status = status; Metrics = metrics ?? new PathQualityMetrics();
        Timing = timing;
    }
    public string Method { get; }
    public string Label { get; }
    public PathSmoothingStatus Status { get; }
    public PathQualityMetrics Metrics { get; }
    public SmoothingTimingSummary Timing { get; }
}
