using AyCode.Core.Benchmarks.Workloads.Scenarios;

namespace AyCode.Core.Benchmarks.Reporting;

/// <summary>
/// Per-cell benchmark result row. Populated by the benchmark execution loop (Console-side
/// <c>BenchmarkLoop.RunBenchmarksForTestData</c> / BDN-side <c>BdnSummaryAdapter</c>); consumed by the
/// output formatters in <c>BenchmarkReportWriter</c> (console table + .log + .LLM file writers).
/// Pure DTO — no behaviour.
/// </summary>
public sealed class BenchmarkResult
{
    public string TestDataName { get; set; } = "";
    public BenchmarkEngine Engine { get; set; }
    public BenchmarkIoMode IoMode { get; set; }
    public BenchmarkDispatchMode DispatchMode { get; set; }
    public string OptionsPreset { get; set; } = "";

    /// <summary>
    /// CLR type name of the order graph serialised in this row (e.g. <c>"TestOrder_All_False"</c>,
    /// <c>"TestOrder_All_True"</c>). Captured from <see cref="ISerializerBenchmark.OrderTypeName"/> in
    /// the runner loop; surfaced in the SERIALIZER OPTIONS section of every output
    /// (.log, .LLM, console) so the reader can correlate each preset with its TestOrder variant
    /// without inflating the per-row tables with an extra column.
    /// </summary>
    public string OrderTypeName { get; set; } = "";

    /// <summary>True if Serialize() captures a full round-trip and Deserialize() is a no-op
    /// (single-use streaming transports like NamedPipe). Excluded from "Fastest Serialize" / "Fastest Deserialize"
    /// winners rankings; still ranked in "Fastest Round-trip". Display-side: Ser µs/op / SerAlloc / Des µs/op / DesAlloc
    /// all show "N/A" since they were never measured separately; RT µs/op / RT Alloc carry the full round-trip values.</summary>
    public bool IsRoundTripOnly { get; set; }

    /// <summary>Synthesized display name for backwards compatibility / single-string-row scenarios. Includes DispatchMode so SGen and Runtime variants of the same preset don't collide in grouping (e.g. SUMMARY: WINNERS).</summary>
    public string SerializerName => $"{Engine.ToDisplay()} ({IoMode.ToDisplay()}, {OptionsPreset}, {DispatchMode.ToDisplay()})";

    public string? OptionsDescription { get; set; }
    public int SerializedSize { get; set; }
    public double SerializeTimeMs { get; set; }
    public double DeserializeTimeMs { get; set; }

    // Per-sample min/max alongside the median (median is the *Time*Ms field above). Surfaces
    // inter-sample range — the visible noise floor for the row. 0 when the operation was skipped
    // (mode != "all"/"ser"/"des") or when a single-sample fast path was used (min == max == median).
    public double SerializeTimeMinMs { get; set; }
    public double SerializeTimeMaxMs { get; set; }
    public double DeserializeTimeMinMs { get; set; }
    public double DeserializeTimeMaxMs { get; set; }

    // Sample-population stddev (ms). Used by FormatMicrosWithRange to compute CV (stddev/mean)
    // and emit the ⚠️ marker on rows above Configuration.UnstableCVThreshold. 0 in single-sample mode.
    public double SerializeTimeStdDevMs { get; set; }
    public double DeserializeTimeStdDevMs { get; set; }

    // Per-row adaptive iteration count (post-CalibrateIterations). Each Ser and Des function calibrates
    // independently to land its sample window at ~Configuration.TargetSampleMs; per-op µs is then iter-independent
    // (`SerializeTimeMs / SerializeIterations * 1000`). For round-trip-only rows (NamedPipe etc.),
    // RoundTripIterations carries the calibrated iter count; SerializeIterations and DeserializeIterations
    // stay 0 (Ser and Des are not separately measurable on those rows).
    //
    // BDN-sourced rows (populated by <c>BdnSummaryAdapter</c>) follow a different convention: per-op time
    // is stored directly in <c>*TimeMs</c> with <c>Iterations = 1</c>, so the same <c>TimeMs / Iterations * 1000</c>
    // formula yields per-op µs. The actual BDN N count is NOT preserved on these rows — consumers that
    // read <c>SerializeIterations</c> as a nominal loop count (e.g. "bytes allocated over N iterations")
    // will misinterpret BDN rows. For the raw N, read the BDN-native artifacts under
    // <c>Test_Benchmark_Results/Benchmark/BDN/</c>.
    public int SerializeIterations { get; set; }
    public int DeserializeIterations { get; set; }
    public int RoundTripIterations { get; set; }

    public long SerializeAllocBytesPerOp { get; set; }
    public long DeserializeAllocBytesPerOp { get; set; }
    public long SetupSerializeAllocBytes { get; set; }
    public long SetupDeserializeAllocBytes { get; set; }

    /// <summary>Total round-trip time. For in-memory benchmarks: synthesized so that
    /// <c>RoundTripTimeMs / RoundTripIterations</c> yields the correct <c>SerPerOp + DesPerOp</c> µs/op
    /// (necessary because Ser and Des may have different iter counts post-calibration).
    /// For round-trip-only benchmarks (NamedPipe etc.): the directly-measured pipe round-trip time.</summary>
    public double RoundTripTimeMs { get; set; }

    // Round-trip min/max + stddev — only populated for round-trip-only benchmarks (NamedPipe etc.) where
    // RT is directly measured. For in-memory rows RT = Ser + Des, which has no single-sample
    // distribution; surface Ser/Des range separately instead.
    public double RoundTripTimeMinMs { get; set; }
    public double RoundTripTimeMaxMs { get; set; }
    public double RoundTripTimeStdDevMs { get; set; }

    /// <summary>Total round-trip allocation per op. For in-memory benchmarks: <c>SerializeAlloc + DeserializeAlloc</c>.
    /// For round-trip-only benchmarks: process-wide allocation measured via <see cref="GC.GetTotalAllocatedBytes"/>
    /// (covers ALL threads — client, server-drain, channel internals — not just the caller).</summary>
    public long RoundTripAllocBytesPerOp { get; set; }
}
