using AyCode.Core.Serializers;
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Binaries;
using MemoryPack;
using System.Reflection;

namespace AyCode.Core.Benchmarks.Workloads.Scenarios;

/// <summary>
/// Per-engine options-formatting + selection helpers shared by all benchmark rows. Centralizes
/// the Options-column display string (so the .log / .LLM / console headers stay consistent), the
/// MemoryPack <c>WireMode</c>-aligned options selection (so AcBinary FastWire ↔ MemoryPack UTF-16
/// comparisons stay apples-to-apples), and the cached <see cref="AttrFlags"/> attribute-flag aggregation.
/// </summary>
public static class BenchmarkOptions
{
    /// <summary>
    /// Aggregated <see cref="AcBinarySerializableAttribute"/> feature flags across every type tagged with
    /// the attribute in the loaded assemblies. Cached on first access (single reflection scan at startup).
    /// Used by <see cref="BuildAcBinary"/> so the per-row Options column shows BOTH the configured
    /// options-level value AND the effective attribute-level enable flag — a feature flagged off at the
    /// type level overrides the options regardless of preset, and that asymmetry must surface in the log
    /// to avoid misreading a "RefHandling=OnlyId" / "Interning=All" line as actually active.
    /// Aggregation rule: if ALL tagged types have the feature enabled → <c>true</c>; if any tagged type
    /// disables it → <c>false</c> (a single disabling type suppresses the feature on the type-graph).
    /// </summary>
    public static readonly (bool refHandling, bool internString, bool metadata, bool idTracking, bool propertyFilter) AttrFlags
        = ScanAttributeFlags();

    private static (bool refHandling, bool internString, bool metadata, bool idTracking, bool propertyFilter) ScanAttributeFlags()
    {
        var attrs = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(a => { try { return a.GetTypes(); } catch { return Array.Empty<Type>(); } })
            .Select(t => t.GetCustomAttribute<AcBinarySerializableAttribute>())
            .Where(a => a != null)
            .ToList();

        if (attrs.Count == 0) return (false, false, false, false, false);

        return (
            refHandling:    attrs.All(a => a!.EnableRefHandlingFeature),
            internString:   attrs.All(a => a!.EnableInternStringFeature),
            metadata:       attrs.All(a => a!.EnableMetadataFeature),
            idTracking:     attrs.All(a => a!.EnableIdTrackingFeature),
            propertyFilter: attrs.All(a => a!.EnablePropertyFilterFeature));
    }

    /// <summary>
    /// Common Options-column formatter for every AcBinary serializer benchmark row. Renders the
    /// configured options-level value AND the effective attribute-level enable flag side-by-side
    /// (e.g. <c>Interning=All(opt) | False (attr)</c>) so attribute-suppressed features cannot
    /// silently mislead. Pass any benchmark-specific extras (e.g. <c>", BufferSize=4096B"</c>)
    /// in <paramref name="extra"/> — they are appended after the common fields.
    /// </summary>
    public static string BuildAcBinary(AcBinarySerializerOptions options, string extra = "")
    {
        // PropertyFilter: opt-side is "Set"/"None" depending on whether a callback is registered (the callback
        // itself isn't a meaningful display value); attr-side is the cross-type-aggregated bool (true = every
        // tagged type has the feature enabled, false = at least one type opted out via
        // [AcBinarySerializable(enablePropertyFilterFeature: false)] → SGen-emit + Runtime hot-loop both gate).
        var propFilterOpt = options.PropertyFilter == null ? "None" : "Set";

        return $"WireMode={options.WireMode}, " +
               $"RefHandling={options.ReferenceHandling}(opt) | {AttrFlags.refHandling} (attr), " +
               $"Interning={options.UseStringInterning}(opt) | {AttrFlags.internString} (attr), " +
               $"Metadata={options.UseMetadata}(opt) | {AttrFlags.metadata} (attr), " +
               $"PropertyFilter={propFilterOpt}(opt) | {AttrFlags.propertyFilter} (attr), " +
               $"SGen={options.UseGeneratedCode}, " +
               $"Compression={options.UseCompression}{extra}";
    }

    /// <summary>
    /// Returns MemoryPack serializer options aligned with the given <paramref name="wireMode"/> for a fair
    /// apples-to-apples wire-format comparison:
    /// <list type="bullet">
    ///   <item><see cref="WireMode.Compact"/> → <see cref="MemoryPackSerializerOptions.Default"/> (UTF-8) — both
    ///   engines encode UTF-8, comparison is purely about header / tier / dispatch overhead.</item>
    ///   <item><see cref="WireMode.Fast"/> → <see cref="MemoryPackSerializerOptions.Utf16"/> (UTF-16 raw memcpy) —
    ///   both engines write UTF-16 raw bytes, so wire-size and CPU comparison reflect the same string-encoding family.</item>
    /// </list>
    /// Without this alignment the FastWire vs MemPack-default comparison conflates two unrelated dimensions
    /// (UTF-16 raw vs UTF-8 encoded) and produces a misleading +40% wire-size delta that is structurally
    /// the encoding-family difference, NOT an AcBinary-specific overhead.
    /// </summary>
    public static MemoryPackSerializerOptions GetMemPack(WireMode wireMode) =>
        wireMode == WireMode.Fast
            ? MemoryPackSerializerOptions.Utf16
            : MemoryPackSerializerOptions.Default;
}
