using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Tests.TestModels;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;

namespace AyCode.Core.Serializers.Console;

/// <summary>
/// Configuration state for the benchmark application. Holds compile-time and runtime constants,
/// per-run mutable settings (WireMode, charset, iteration counts), and the attribute-flag
/// aggregation that drives the per-row Options column. Split out from <c>Program.cs</c> so the
/// entry-point file can focus on UX-flow and benchmark orchestration; everything in this class
/// is config / state — no benchmark logic. Single instance (static class) — the application is
/// a one-shot process, no multi-tenant state isolation needed.
/// </summary>
internal static class Configuration
{
    internal const string ResultsDirectory = @"H:\Applications\Aycode\Source\AyCode.Core\Test_Benchmark_Results\Benchmark";

#if AYCODE_NATIVEAOT
    internal const string BuildConfiguration = "NativeAOT";
#elif DEBUG
    internal const string BuildConfiguration = "Debug";
#else
    internal const string BuildConfiguration = "Release";
#endif

#if DEBUG
    internal static int WarmupIterations = 0;
    internal static int TestIterations = 1;
    internal static int BenchmarkSamples = 1;       // Debug: single sample, fast iteration
#else
    internal static int WarmupIterations = 5000; //10000 — per-phase (Ser AND Des get their own warmup separately)
    internal static int TestIterations = 1000; //1000
    internal static int BenchmarkSamples = 10;
#endif

    // Interactive settings: selected AcBinary wire mode for benchmark runs.
    // 1 = Compact, 2 = Fast
    internal static WireMode SelectedWireMode = WireMode.Compact;

    // Serializer name constants
    // Engine identifiers (used in Engine column + comparison logic)
    internal const string EngineAcBinary = "AcBinary";
    internal const string EngineMemoryPack = "MemoryPack";
#if !AYCODE_NATIVEAOT
    internal const string EngineMessagePack = "MessagePack";
#endif
    internal const string EngineSystemTextJson = "System.Text.Json";

    // IO mode identifiers (used in IO column + comparison logic)
    internal const string IoByteArray = "Byte[]";
    internal const string IoBufWrReuse = "BufWr reuse";
    internal const string IoBufWrNew = "BufWr new";
    internal const string IoString = "String";
    internal const string IoNamedPipe = "NamedPipe";
    internal const string IoNamedPipeRaw = "NamedPipe";
    internal const string IoInMemoryPipe = "Pipe(in-mem)";
    internal const string IoInMemoryRaw = "Pipe(in-mem)";

    // Single source of truth for the chunk size used by ALL pipe-related benchmarks (NamedPipe PipeChunk,
    // NamedPipe PipeRaw, in-memory Pipe, in-memory RawMem) AND the NamedPipe server's inBufferSize/outBufferSize.
    // Same value across both layers ensures apples-to-apples comparison: chunked-streaming chunk-on-wire size
    // matches the kernel pipe-buffer slot exactly. Tweak HERE when experimenting; do NOT scatter chunkSize
    // overrides across individual benchmark rows.
    internal const int PipeChunkSize = 4096;

    // Dispatch mode identifiers — describes how property access / type dispatch happens for a given run.
    // SGen   = compile-time source generator path (Unsafe.As<T> direct fields, slot-array wrapper lookup).
    // Runtime= reflection / compiled-delegate path.
    // Hybrid = SGen root with non-SGen child types reached via bridge methods. See docs/BINARY/BINARY_SGEN.md.
    internal const string ModeSGen = "SGen";
    internal const string ModeRuntime = "Runtime";
    internal const string ModeHybrid = "Hybrid";

    // Per-cell adaptive iteration target wall-clock duration. Each Ser/Des function calibrates its
    // own iteration count post-warmup so the sample batch lands in this range — equalizes the
    // per-sample window across cells of vastly different per-op cost (Small ~6 ns/op vs Large
    // ~140 µs/op). Below ~100 ms Stopwatch precision and OS preempt spikes start to dominate.
    internal const int TargetSampleMs = 250;

    // CV (coefficient of variation = stddev / mean) threshold above which a row's range is flagged
    // as "unstable" in the markdown output (⚠️ marker). 3% is a reasonable noise-floor expectation
    // for stabilized in-memory benchmarks; rows above it should be discounted when reading
    // sub-3% inter-engine deltas.
    internal const double UnstableCVThreshold = 0.03;

    // JIT-tier-promotion drain delay between warmup and measurement.
    // - JIT mode (RuntimeFeature.IsDynamicCodeCompiled == true): tiered JIT promotes hot methods
    //   in a background thread; we wait briefly for the queue to drain so the first measurement
    //   sample doesn't catch a Tier-0 → Tier-1 transition mid-flight.
    // - AOT mode (NativeAOT publish): no dynamic compilation happens; the sleep is pure noise.
    // 250ms (vs the historical 3000ms) is sufficient for a few-method working set under .NET 9's
    // tiered JIT — empirically the queue drains in <100ms for the bench's hot path.
    internal static int JitSleep => RuntimeFeature.IsDynamicCodeCompiled ? 250 : 0;

    // OptionsPreset values are passed per-instance (constructor argument), not constants —
    // each CreateSerializers call line specifies its own preset name (e.g. "FastMode", "NoIntern").

    internal static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false);

    /// <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 the benchmark's per-row Options-column formatter so the 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>
    internal 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>
    /// Returns a human-readable name for the currently-active <c>BenchmarkTestDataProvider.LongStringSuffix</c>
    /// charset. Returns "Custom" when the suffix doesn't match any of the predefined
    /// <see cref="CharsetSuffixes"/> constants. Used in menu state display, console run header, and
    /// the .LLM / .log output headers so per-charset bench files are self-documenting.
    /// </summary>
    internal static string GetCurrentCharsetName()
    {
        var s = BenchmarkTestDataProvider.LongStringSuffix;

        return s switch
        {
            CharsetSuffixes.Latin1FixAscii => "Latin1FixAscii",
            CharsetSuffixes.Latin1Short => "Latin1Short",
            CharsetSuffixes.Latin1Long => "Latin1Long",
            CharsetSuffixes.CjkBmp => "CjkBmp",
            CharsetSuffixes.Cyrillic => "Cyrillic",
            CharsetSuffixes.Mixed => "Mixed",
            _ => "Custom"
        };
    }
}
