using System.Text.Json.Serialization;

namespace Flyshot.Core.Domain;

/// <summary>
/// Represents the stable planning result returned to orchestration, SDKs, and monitoring layers.
/// </summary>
public sealed class TrajectoryResult
{
    /// <summary>
    /// Initializes a validated trajectory result.
    /// </summary>
    public TrajectoryResult(
        string programName,
        PlanningMethod method,
        bool isValid,
        TimeSpan duration,
        IEnumerable<ShotEvent> shotEvents,
        IEnumerable<TrajectoryDoEvent> triggerTimeline,
        IEnumerable<TrajectoryArtifact> artifacts,
        string? failureReason,
        bool usedCache,
        int originalWaypointCount,
        int plannedWaypointCount,
        int triggerSampleIndexOffsetCycles = 0,
        IEnumerable<IReadOnlyList<double>>? denseJointTrajectory = null,
        FlyshotPreparedExecution? preparedFlyshotExecution = null,
        IEnumerable<IReadOnlyList<double>>? denseCartesianTrajectory = null)
    {
        if (string.IsNullOrWhiteSpace(programName))
        {
            throw new ArgumentException("Program name is required.", nameof(programName));
        }

        if (duration < TimeSpan.Zero)
        {
            throw new ArgumentOutOfRangeException(nameof(duration), "Duration must be zero or positive.");
        }

        if (originalWaypointCount < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(originalWaypointCount), "Original waypoint count must be zero or positive.");
        }

        if (plannedWaypointCount < originalWaypointCount)
        {
            throw new ArgumentOutOfRangeException(nameof(plannedWaypointCount), "Planned waypoint count must be greater than or equal to the original waypoint count.");
        }

        if (triggerSampleIndexOffsetCycles < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(triggerSampleIndexOffsetCycles), "Trigger sample index offset cycles must be zero or positive.");
        }

        ArgumentNullException.ThrowIfNull(shotEvents);
        ArgumentNullException.ThrowIfNull(triggerTimeline);
        ArgumentNullException.ThrowIfNull(artifacts);

        // Materialize once so the result remains stable after the planner hands it off.
        var copiedShotEvents = shotEvents.ToArray();
        var copiedTriggerTimeline = triggerTimeline.ToArray();
        var copiedArtifacts = artifacts.ToArray();
        var copiedDenseJointTrajectory = denseJointTrajectory?.Select(static row => row.ToArray()).ToArray();
        var copiedDenseCartesianTrajectory = denseCartesianTrajectory?.Select(static row => row.ToArray()).ToArray();

        ProgramName = programName;
        Method = method;
        IsValid = isValid;
        Duration = duration;
        ShotEvents = copiedShotEvents;
        TriggerTimeline = copiedTriggerTimeline;
        Artifacts = copiedArtifacts;
        FailureReason = failureReason;
        UsedCache = usedCache;
        OriginalWaypointCount = originalWaypointCount;
        PlannedWaypointCount = plannedWaypointCount;
        TriggerSampleIndexOffsetCycles = triggerSampleIndexOffsetCycles;
        DenseJointTrajectory = copiedDenseJointTrajectory;
        DenseCartesianTrajectory = copiedDenseCartesianTrajectory;
        PreparedFlyshotExecution = preparedFlyshotExecution;
    }

    /// <summary>
    /// Gets the source program name.
    /// </summary>
    [JsonPropertyName("programName")]
    public string ProgramName { get; }

    /// <summary>
    /// Gets the method that produced the result.
    /// </summary>
    [JsonPropertyName("method")]
    public PlanningMethod Method { get; }

    /// <summary>
    /// Gets a value indicating whether the result can be executed.
    /// </summary>
    [JsonPropertyName("isValid")]
    public bool IsValid { get; }

    /// <summary>
    /// Gets the final trajectory duration.
    /// </summary>
    [JsonPropertyName("duration")]
    public TimeSpan Duration { get; }

    /// <summary>
    /// Gets the sampled shot events exported for monitoring and reports.
    /// </summary>
    [JsonPropertyName("shotEvents")]
    public IReadOnlyList<ShotEvent> ShotEvents { get; }

    /// <summary>
    /// Gets the trigger timeline that the runtime will inject into servo execution.
    /// </summary>
    [JsonPropertyName("triggerTimeline")]
    public IReadOnlyList<TrajectoryDoEvent> TriggerTimeline { get; }

    /// <summary>
    /// Gets the exported trajectory artifacts associated with the result.
    /// </summary>
    [JsonPropertyName("artifacts")]
    public IReadOnlyList<TrajectoryArtifact> Artifacts { get; }

    /// <summary>
    /// Gets the failure reason when the result is invalid.
    /// </summary>
    [JsonPropertyName("failureReason")]
    public string? FailureReason { get; }

    /// <summary>
    /// Gets a value indicating whether the result reused cached planning data.
    /// </summary>
    [JsonPropertyName("usedCache")]
    public bool UsedCache { get; }

    /// <summary>
    /// Gets the teach waypoint count before adaptive point insertion.
    /// </summary>
    [JsonPropertyName("originalWaypointCount")]
    public int OriginalWaypointCount { get; }

    /// <summary>
    /// Gets the final waypoint count after planner preprocessing.
    /// </summary>
    [JsonPropertyName("plannedWaypointCount")]
    public int PlannedWaypointCount { get; }

    /// <summary>
    /// Gets the configured command-sample offset applied after trigger binding picks the best sample.
    /// </summary>
    [JsonPropertyName("triggerSampleIndexOffsetCycles")]
    public int TriggerSampleIndexOffsetCycles { get; }

    /// <summary>
    /// Gets the dense joint trajectory samples where each row is [time, j1, j2, ...].
    /// Null when dense sampling was not performed (e.g. simulation fallback).
    /// </summary>
    [JsonPropertyName("denseJointTrajectory")]
    public IReadOnlyList<IReadOnlyList<double>>? DenseJointTrajectory { get; }

    /// <summary>
    /// 获取稠密直角坐标轨迹采样点，每行格式为 [time, x, y, z, w, p, r]。
    /// Null 表示本结果不是直角流式运动。
    /// </summary>
    [JsonPropertyName("denseCartesianTrajectory")]
    public IReadOnlyList<IReadOnlyList<double>>? DenseCartesianTrajectory { get; }

    /// <summary>
    /// Gets the prepared flyshot execution queue when the flyshot chain has already built the final 8ms send sequence.
    /// </summary>
    [JsonIgnore]
    public FlyshotPreparedExecution? PreparedFlyshotExecution { get; }
}

/// <summary>
/// Describes a trigger event that the runtime must inject into the servo timeline.
/// </summary>
public sealed class TrajectoryDoEvent
{
    /// <summary>
    /// Initializes a validated runtime trigger event.
    /// </summary>
    public TrajectoryDoEvent(
        int waypointIndex,
        double triggerTime,
        int offsetCycles,
        int holdCycles,
        IoAddressGroup addressGroup,
        IEnumerable<double>? referenceJointsDegrees = null)
    {
        if (waypointIndex < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(waypointIndex), "Waypoint index must be zero or positive.");
        }

        if (triggerTime < 0.0)
        {
            throw new ArgumentOutOfRangeException(nameof(triggerTime), "Trigger time must be zero or positive.");
        }

        if (holdCycles < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(holdCycles), "Hold cycles must be zero or positive.");
        }

        WaypointIndex = waypointIndex;
        TriggerTime = triggerTime;
        OffsetCycles = offsetCycles;
        HoldCycles = holdCycles;
        AddressGroup = addressGroup ?? throw new ArgumentNullException(nameof(addressGroup));
        ReferenceJointsDegrees = referenceJointsDegrees?.ToArray();
    }

    /// <summary>
    /// Gets the original teach-waypoint index that requested the event.
    /// </summary>
    [JsonPropertyName("waypointIndex")]
    public int WaypointIndex { get; }

    /// <summary>
    /// Gets the theoretical trigger time before discretization.
    /// </summary>
    [JsonPropertyName("triggerTime")]
    public double TriggerTime { get; }

    /// <summary>
    /// Gets the configured offset in servo cycles.
    /// </summary>
    [JsonPropertyName("offsetCycles")]
    public int OffsetCycles { get; }

    /// <summary>
    /// Gets the configured hold duration in servo cycles.
    /// </summary>
    [JsonPropertyName("holdCycles")]
    public int HoldCycles { get; }

    /// <summary>
    /// Gets the IO address group to fire.
    /// </summary>
    [JsonPropertyName("addressGroup")]
    public IoAddressGroup AddressGroup { get; }

    /// <summary>
    /// Gets the teach waypoint joints converted to degrees.
    /// This is used to select the closest real send sample inside the trigger time window.
    /// </summary>
    [JsonPropertyName("referenceJointsDegrees")]
    public IReadOnlyList<double>? ReferenceJointsDegrees { get; }
}

/// <summary>
/// Describes an exported artifact generated from a planned trajectory.
/// </summary>
public sealed class TrajectoryArtifact
{
    /// <summary>
    /// Initializes a validated trajectory artifact descriptor.
    /// </summary>
    public TrajectoryArtifact(TrajectoryArtifactKind kind, string logicalName, string relativePath)
    {
        if (string.IsNullOrWhiteSpace(logicalName))
        {
            throw new ArgumentException("Logical artifact name is required.", nameof(logicalName));
        }

        if (string.IsNullOrWhiteSpace(relativePath))
        {
            throw new ArgumentException("Relative artifact path is required.", nameof(relativePath));
        }

        Kind = kind;
        LogicalName = logicalName;
        RelativePath = relativePath;
    }

    /// <summary>
    /// Gets the exported artifact kind.
    /// </summary>
    [JsonPropertyName("kind")]
    public TrajectoryArtifactKind Kind { get; }

    /// <summary>
    /// Gets the logical artifact file name.
    /// </summary>
    [JsonPropertyName("logicalName")]
    public string LogicalName { get; }

    /// <summary>
    /// Gets the artifact path relative to the service output root.
    /// </summary>
    [JsonPropertyName("relativePath")]
    public string RelativePath { get; }
}

/// <summary>
/// Identifies the exported trajectory artifact category.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum TrajectoryArtifactKind
{
    /// <summary>
    /// Represents sparse joint teach points.
    /// </summary>
    JointTeachTrajectory,

    /// <summary>
    /// Represents sparse Cartesian teach points.
    /// </summary>
    CartesianTeachTrajectory,

    /// <summary>
    /// Represents dense joint trajectory samples.
    /// </summary>
    JointDenseTrajectory,

    /// <summary>
    /// Represents dense Cartesian trajectory samples.
    /// </summary>
    CartesianDenseTrajectory,

    /// <summary>
    /// Represents the exported shot-event mapping.
    /// </summary>
    ShotEventTimeline
}
