// Copyright 2024-2026 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/msgtrace.go in the NATS server Go source.

using System.Text;
using ZB.MOM.NatsNet.Server.Internal;

namespace ZB.MOM.NatsNet.Server;

// ============================================================================
// Compression type enum — mirrors Go compressionType in server/client.go.
// Used by MsgTrace to indicate encoding of trace messages.
// ============================================================================

/// <summary>
/// Compression types used when sending trace events.
/// Mirrors Go <c>compressionType</c> iota in server/client.go.
/// </summary>
internal enum TraceCompressionType
{
    /// <summary>No compression. Mirrors Go <c>noCompression</c>.</summary>
    None = 0,

    /// <summary>Snappy/S2 compression. Mirrors Go <c>snappyCompression</c>.</summary>
    Snappy = 1,

    /// <summary>Gzip compression. Mirrors Go <c>gzipCompression</c>.</summary>
    Gzip = 2,

    /// <summary>Compression type not recognised. Mirrors Go <c>unsupportedCompression</c>.</summary>
    Unsupported = 3,
}

// ============================================================================
// Error strings — mirrors Go const block near bottom of msgtrace.go.
// ============================================================================

/// <summary>
/// Well-known error strings included in trace egress events.
/// Mirrors Go const block in server/msgtrace.go.
/// </summary>
public static class MsgTraceErrors
{
    /// <summary>Client does not support tracing and trace-only was requested.</summary>
    public const string TraceOnlyNoSupport   = "Not delivered because remote does not support message tracing";

    /// <summary>Client does not support tracing; delivered but no trace generated.</summary>
    public const string TraceNoSupport       = "Message delivered but remote does not support message tracing so no trace event generated from there";

    /// <summary>Not delivered due to echo suppression.</summary>
    public const string NoEcho              = "Not delivered because of no echo";

    /// <summary>Not delivered because the subject is publish-denied.</summary>
    public const string PubViolation        = "Not delivered because publish denied for this subject";

    /// <summary>Not delivered because the subscription denies this subject.</summary>
    public const string SubDeny             = "Not delivered because subscription denies this subject";

    /// <summary>Not delivered because the subscription is closed.</summary>
    public const string SubClosed           = "Not delivered because subscription is closed";

    /// <summary>Not delivered because the client is closed.</summary>
    public const string ClientClosed        = "Not delivered because client is closed";

    /// <summary>Not delivered because auto-unsubscribe limit exceeded.</summary>
    public const string AutoSubExceeded     = "Not delivered because auto-unsubscribe exceeded";

    /// <summary>Not delivered because fast producer is not stalled and consumer is slow.</summary>
    public const string FastProdNoStall     = "Not delivered because fast producer not stalled and consumer is slow";
}

// ============================================================================
// MsgTraceState — internal runtime state for a traced message
// Mirrors Go <c>msgTrace</c> struct in server/msgtrace.go.
// ============================================================================

/// <summary>
/// Runtime state object carried through message processing for a traced message.
/// Mirrors Go <c>msgTrace</c> struct in server/msgtrace.go.
/// </summary>
internal sealed class MsgTraceState
{
    /// <summary>
    /// Atomic counter used to co-ordinate sending between the main pipeline
    /// and the JetStream path (must reach 2 before the event is sent).
    /// Mirrors Go <c>ready int32</c>.
    /// </summary>
    private int _ready;

    /// <summary>Server reference, used to publish the trace event.</summary>
    public INatsServer? Server { get; set; }

    /// <summary>Account to which the trace event is published.</summary>
    public Account? Account { get; set; }

    /// <summary>
    /// Origin account name — set only when <see cref="Account"/> is null
    /// (account lookup failed). Mirrors Go <c>oan string</c>.
    /// </summary>
    public string OriginAccountName { get; set; } = string.Empty;

    /// <summary>Destination subject for the trace event.</summary>
    public string Dest { get; set; } = string.Empty;

    /// <summary>The top-level trace event being accumulated.</summary>
    public MsgTraceEvent Event { get; set; } = new();

    /// <summary>
    /// Reference to the JetStream event inside <see cref="Event.Events"/>,
    /// so it can be updated after the fact.
    /// Mirrors Go <c>js *MsgTraceJetStream</c>.
    /// </summary>
    public MsgTraceJetStream? Js { get; set; }

    /// <summary>Hop identifier received from the upstream server.</summary>
    public string Hop { get; set; } = string.Empty;

    /// <summary>Computed next-hop string (set during egress header generation).</summary>
    public string NextHop { get; set; } = string.Empty;

    /// <summary>When true, the message is not delivered — tracing only.</summary>
    public bool TraceOnly { get; set; }

    /// <summary>Compression type for sending the trace event.</summary>
    public TraceCompressionType CompressionType { get; set; } = TraceCompressionType.None;

    // -------------------------------------------------------------------------
    // Factory methods — mirrors Go new() methods on each concrete type.
    // -------------------------------------------------------------------------

    /// <summary>
    /// Creates a new <see cref="MsgTraceIngress"/> with the current timestamp.
    /// Mirrors Go <c>MsgTraceIngress.new()</c>.
    /// </summary>
    public static MsgTraceIngress NewIngress() => new()
    {
        Type      = MsgTraceType.Ingress,
        Timestamp = DateTime.UtcNow,
    };

    /// <summary>
    /// Creates a new <see cref="MsgTraceSubjectMapping"/> with the current timestamp.
    /// Mirrors Go <c>MsgTraceSubjectMapping.new()</c>.
    /// </summary>
    public static MsgTraceSubjectMapping NewSubjectMapping() => new()
    {
        Type      = MsgTraceType.SubjectMapping,
        Timestamp = DateTime.UtcNow,
    };

    /// <summary>
    /// Creates a new <see cref="MsgTraceStreamExport"/> with the current timestamp.
    /// Mirrors Go <c>MsgTraceStreamExport.new()</c>.
    /// </summary>
    public static MsgTraceStreamExport NewStreamExport() => new()
    {
        Type      = MsgTraceType.StreamExport,
        Timestamp = DateTime.UtcNow,
    };

    /// <summary>
    /// Creates a new <see cref="MsgTraceServiceImport"/> with the current timestamp.
    /// Mirrors Go <c>MsgTraceServiceImport.new()</c>.
    /// </summary>
    public static MsgTraceServiceImport NewServiceImport() => new()
    {
        Type      = MsgTraceType.ServiceImport,
        Timestamp = DateTime.UtcNow,
    };

    /// <summary>
    /// Creates a new <see cref="MsgTraceJetStream"/> with the current timestamp.
    /// Mirrors Go <c>MsgTraceJetStream.new()</c>.
    /// </summary>
    public static MsgTraceJetStream NewJetStream() => new()
    {
        Type      = MsgTraceType.JetStream,
        Timestamp = DateTime.UtcNow,
    };

    /// <summary>
    /// Creates a new <see cref="MsgTraceEgress"/> with the current timestamp.
    /// Mirrors Go <c>MsgTraceEgress.new()</c>.
    /// </summary>
    public static MsgTraceEgress NewEgress() => new()
    {
        Type      = MsgTraceType.Egress,
        Timestamp = DateTime.UtcNow,
    };

    // -------------------------------------------------------------------------
    // Instance helpers
    // -------------------------------------------------------------------------

    /// <summary>
    /// Returns <c>true</c> if trace-only mode is active (message must not
    /// be delivered to subscribers).
    /// Mirrors Go <c>msgTrace.traceOnly()</c>.
    /// </summary>
    public bool IsTraceOnly() => TraceOnly;

    /// <summary>
    /// Sets the ingress error field on the first event if it is an ingress.
    /// Mirrors Go <c>msgTrace.setIngressError()</c>.
    /// </summary>
    public void SetIngressError(string err)
    {
        if (Event.Ingress() is { } ingress)
            ingress.Error = err;
    }

    /// <summary>
    /// Appends a subject-mapping event.
    /// Mirrors Go <c>msgTrace.addSubjectMappingEvent()</c>.
    /// </summary>
    public void AddSubjectMappingEvent(string mappedSubject)
    {
        Event.Events.Add(new MsgTraceSubjectMapping
        {
            Type      = MsgTraceType.SubjectMapping,
            Timestamp = DateTime.UtcNow,
            MappedTo  = mappedSubject,
        });
    }

    /// <summary>
    /// Appends an egress event.
    /// Mirrors Go <c>msgTrace.addEgressEvent()</c>.
    /// </summary>
    public void AddEgressEvent(ClientConnection dc, Subscription? sub, string err)
    {
        var egress = new MsgTraceEgress
        {
            Type      = MsgTraceType.Egress,
            Timestamp = DateTime.UtcNow,
            Kind      = (int)dc.Kind,
            Cid       = dc.Cid,
            Name      = MsgTraceHelper.GetConnName(dc),
            Hop       = NextHop,
            Error     = string.IsNullOrEmpty(err) ? null : err,
        };
        NextHop = string.Empty;

        // For CLIENT connections, include subscription and queue info.
        if (dc.Kind == ClientKind.Client && sub is not null)
        {
            egress.Subscription = Encoding.UTF8.GetString(sub.Subject);
            if (sub.Queue is { Length: > 0 } q)
                egress.Queue = Encoding.UTF8.GetString(q);
        }

        // Include account name if different from the ingress account.
        if ((dc.Kind == ClientKind.Client || dc.Kind == ClientKind.Leaf) &&
            dc._account is Account dcAcc &&
            Event.Ingress() is { } ing)
        {
            var dcAccName = dcAcc.GetName();
            if (!string.Equals(dcAccName, ing.Account, StringComparison.Ordinal))
                egress.Account = dcAccName;
        }

        Event.Events.Add(egress);
    }

    /// <summary>
    /// Appends a stream-export event.
    /// Mirrors Go <c>msgTrace.addStreamExportEvent()</c>.
    /// </summary>
    public void AddStreamExportEvent(ClientConnection dc, string toSubject)
    {
        string accName;
        lock (dc)
        {
            accName = (dc._account as Account)?.GetName() ?? string.Empty;
        }

        Event.Events.Add(new MsgTraceStreamExport
        {
            Type      = MsgTraceType.StreamExport,
            Timestamp = DateTime.UtcNow,
            Account   = accName,
            To        = toSubject,
        });
    }

    /// <summary>
    /// Appends a service-import event.
    /// Mirrors Go <c>msgTrace.addServiceImportEvent()</c>.
    /// </summary>
    public void AddServiceImportEvent(string accName, string from, string to)
    {
        Event.Events.Add(new MsgTraceServiceImport
        {
            Type      = MsgTraceType.ServiceImport,
            Timestamp = DateTime.UtcNow,
            Account   = accName,
            From      = from,
            To        = to,
        });
    }

    /// <summary>
    /// Appends a JetStream event and retains a reference for later updates.
    /// Mirrors Go <c>msgTrace.addJetStreamEvent()</c>.
    /// </summary>
    public void AddJetStreamEvent(string streamName)
    {
        Js = new MsgTraceJetStream
        {
            Type      = MsgTraceType.JetStream,
            Timestamp = DateTime.UtcNow,
            Stream    = streamName,
        };
        Event.Events.Add(Js);
    }

    /// <summary>
    /// Updates the last JetStream event with subject and no-interest flag.
    /// Mirrors Go <c>msgTrace.updateJetStreamEvent()</c>.
    /// </summary>
    public void UpdateJetStreamEvent(string subject, bool noInterest)
    {
        if (Js is null) return;
        Js.Subject    = subject;
        Js.NoInterest = noInterest;
        Js.Timestamp  = DateTime.UtcNow;
    }

    /// <summary>
    /// Sends the trace event from the JetStream path.
    /// Mirrors Go <c>msgTrace.sendEventFromJetStream()</c>.
    /// </summary>
    public void SendEventFromJetStream(Exception? err)
    {
        if (Js is null) return;
        if (err is not null)
            Js.Error = err.Message;
        SendEvent();
    }

    /// <summary>
    /// Publishes the complete trace event.
    /// When there is a JetStream path, both the main and JetStream legs must
    /// call this before the event is sent (atomic counter reaching 2).
    /// Mirrors Go <c>msgTrace.sendEvent()</c>.
    /// </summary>
    public void SendEvent()
    {
        if (Js is not null)
        {
            var count = Interlocked.Increment(ref _ready);
            if (count != 2)
                return;
        }

        // Server.sendInternalAccountSysMsg is a runtime operation — the full
        // publish path is wired up when the NatsServer runtime is available.
        // Mirrors Go: t.srv.sendInternalAccountSysMsg(t.acc, t.dest, &t.event.Server, t.event, t.ct)
        _ = Server;
        _ = Account;
        _ = Dest;
        _ = CompressionType;
        _ = Event;
    }

    /// <summary>
    /// Sets the Nats-Trace-Origin-Account header on <paramref name="msg"/> when
    /// the trace account differs from <paramref name="acc"/>.
    /// Mirrors Go <c>msgTrace.setOriginAccountHeaderIfNeeded()</c>.
    /// </summary>
    public byte[] SetOriginAccountHeaderIfNeeded(ClientConnection c, Account acc, byte[] msg)
    {
        string oan;
        if (Account is not null)
        {
            if (Account == acc) return msg;
            oan = Account.GetName();
        }
        else if (!string.Equals(OriginAccountName, acc.GetName(), StringComparison.Ordinal))
        {
            oan = OriginAccountName;
        }
        else
        {
            return msg;
        }

        return NatsMessageHeaders.GenHeader(msg, MsgTraceHeaders.MsgTraceOriginAccount, oan);
    }

    /// <summary>
    /// Increments the hop counter and writes the Nats-Trace-Hop header.
    /// Mirrors Go <c>msgTrace.setHopHeader()</c>.
    /// </summary>
    public byte[] SetHopHeader(ClientConnection c, byte[] msg)
    {
        Event.Hops++;
        NextHop = Hop.Length > 0
            ? $"{Hop}.{Event.Hops}"
            : $"{Event.Hops}";
        return NatsMessageHeaders.GenHeader(msg, MsgTraceHeaders.MsgTraceHop, NextHop);
    }
}

// ============================================================================
// MsgTraceHelper — static helpers mirroring Go free functions in msgtrace.go.
// ============================================================================

/// <summary>
/// Static helpers for message tracing that mirror the free functions and
/// client methods in Go's server/msgtrace.go.
/// </summary>
internal static class MsgTraceHelper
{
    /// <summary>
    /// Header name for the Accept-Encoding header, used to detect the
    /// compression preference of an incoming connection.
    /// Mirrors Go <c>acceptEncodingHeader</c> const in client.go.
    /// </summary>
    internal const string AcceptEncodingHeader = "Accept-Encoding";

    // -------------------------------------------------------------------------
    // getTraceAs — generic type assertion helper
    // -------------------------------------------------------------------------

    /// <summary>
    /// Returns <paramref name="e"/> cast to <typeparamref name="T"/>, or null.
    /// Mirrors Go generic <c>getTraceAs[T]</c> in server/msgtrace.go.
    /// </summary>
    public static T? GetTraceAs<T>(IMsgTrace e) where T : class, IMsgTrace
        => e as T;

    // -------------------------------------------------------------------------
    // getConnName
    // -------------------------------------------------------------------------

    /// <summary>
    /// Returns the remote-server name for ROUTER/GATEWAY/LEAF connections,
    /// or the client's connection name for CLIENT connections.
    /// Mirrors Go <c>getConnName(c *client)</c> in server/msgtrace.go.
    /// </summary>
    public static string GetConnName(ClientConnection c)
    {
        return c.Kind switch
        {
            ClientKind.Router  when c.Route?.RemoteName is { Length: > 0 } rn   => rn,
            ClientKind.Gateway when c.Gateway?.RemoteName is { Length: > 0 } gn  => gn,
            ClientKind.Leaf    when c.Leaf?.RemoteServer is { Length: > 0 } ls   => ls,
            _                                                                     => c.Opts.Name,
        };
    }

    // -------------------------------------------------------------------------
    // getCompressionType
    // -------------------------------------------------------------------------

    /// <summary>
    /// Maps the Accept-Encoding header value to an internal compression type.
    /// Mirrors Go <c>getCompressionType(cts string) compressionType</c>.
    /// </summary>
    public static TraceCompressionType GetCompressionType(string cts)
    {
        if (string.IsNullOrEmpty(cts))
            return TraceCompressionType.None;

        cts = cts.ToLowerInvariant();
        if (cts.Contains("snappy") || cts.Contains("s2"))
            return TraceCompressionType.Snappy;
        if (cts.Contains("gzip"))
            return TraceCompressionType.Gzip;

        return TraceCompressionType.Unsupported;
    }

    // -------------------------------------------------------------------------
    // isMsgTraceEnabled
    // -------------------------------------------------------------------------

    /// <summary>
    /// Returns the active <see cref="MsgTraceState"/> for this connection (or null),
    /// and whether trace-only mode is active.
    /// Mirrors Go <c>client.isMsgTraceEnabled()</c>.
    /// </summary>
    public static (MsgTraceState? Trace, bool TraceOnly) IsMsgTraceEnabled(ClientConnection c)
    {
        var t = c.ParseCtx.Pa.Trace as MsgTraceState;
        if (t is null) return (null, false);
        return (t, t.IsTraceOnly());
    }

    // -------------------------------------------------------------------------
    // msgTraceSupport
    // -------------------------------------------------------------------------

    /// <summary>
    /// Returns <c>true</c> when the connection supports message tracing.
    /// CLIENT connections always support tracing; ROUTER/GATEWAY/LEAF only
    /// do when their negotiated protocol is high enough.
    /// Mirrors Go <c>client.msgTraceSupport()</c>.
    /// </summary>
    public static bool MsgTraceSupport(ClientConnection c)
        => c.Kind == ClientKind.Client || c.Opts.Protocol >= ServerProtocol.MsgTraceProto;

    // -------------------------------------------------------------------------
    // sample
    // -------------------------------------------------------------------------

    /// <summary>
    /// Returns <c>true</c> when the message should be included in a sample.
    /// Any value outside [1..99] is treated as 100% sampling.
    /// Mirrors Go <c>sample(sampling int) bool</c>.
    /// </summary>
    public static bool Sample(int sampling)
    {
        if (sampling <= 0 || sampling >= 100)
            return true;
        return Random.Shared.Next(100) <= sampling;
    }

    // -------------------------------------------------------------------------
    // genHeaderMapIfTraceHeadersPresent
    // -------------------------------------------------------------------------

    /// <summary>
    /// Parses a raw NATS message header block and returns all header key/value
    /// pairs if the block contains either <c>Nats-Trace-Dest</c> or an enabled
    /// <c>traceparent</c> header.
    ///
    /// The returned flag is <c>true</c> (external) when only the W3C traceparent
    /// header triggered the trace; <c>false</c> when the native Nats-Trace-Dest
    /// header was present.
    ///
    /// Returns an empty map when neither trace header is present, or when
    /// <c>Nats-Trace-Dest</c> has the sentinel "trace disabled" value.
    ///
    /// Mirrors Go <c>genHeaderMapIfTraceHeadersPresent</c> in server/msgtrace.go.
    /// </summary>
    public static (Dictionary<string, List<string>> Headers, bool External) GenHeaderMapIfTraceHeadersPresent(
        byte[] hdr)
    {
        var empty = (new Dictionary<string, List<string>>(), false);

        if (hdr.Length == 0)
            return empty;

        var hdrLineBuf = Encoding.ASCII.GetBytes(NatsHeaderConstants.HdrLine);
        if (!hdr.AsSpan().StartsWith(hdrLineBuf))
            return empty;

        var traceDestBuf         = Encoding.ASCII.GetBytes(MsgTraceHeaders.MsgTraceDest);
        var traceDestDisabledBuf = Encoding.ASCII.GetBytes(MsgTraceHeaders.MsgTraceDestDisabled);
        var traceParentBuf       = Encoding.ASCII.GetBytes(MsgTraceHeaders.TraceParentHdr);

        bool traceDestFound   = false;
        bool traceParentFound = false;

        var keys = new List<ReadOnlyMemory<byte>>(16);
        var vals = new List<ReadOnlyMemory<byte>>(16);

        int i = hdrLineBuf.Length;
        while (i < hdr.Length)
        {
            int del = hdr.AsSpan(i).IndexOf((byte)':');
            if (del < 0) break;

            var key = hdr.AsMemory(i, del);
            i += del + 1;

            // Skip leading whitespace in value.
            while (i < hdr.Length && (hdr[i] == ' ' || hdr[i] == '\t'))
                i++;

            int valStart = i;
            int nl = hdr.AsSpan(valStart).IndexOf("\r\n"u8);
            if (nl < 0) break;

            int valEnd = valStart + nl;
            while (valEnd > valStart && (hdr[valEnd - 1] == ' ' || hdr[valEnd - 1] == '\t'))
                valEnd--;

            var val = hdr.AsMemory(valStart, valEnd - valStart);

            if (key.Length > 0 && val.Length > 0)
            {
                // Check for Nats-Trace-Dest.
                if (!traceDestFound && key.Span.SequenceEqual(traceDestBuf))
                {
                    if (val.Span.SequenceEqual(traceDestDisabledBuf))
                        return empty;
                    traceDestFound = true;
                }
                // Check for traceparent (case-insensitive).
                else if (!traceParentFound &&
                         Encoding.ASCII.GetString(key.Span).Equals(
                             "traceparent", StringComparison.OrdinalIgnoreCase))
                {
                    // Sampling bit is the last-flag byte of "version-traceId-parentId-flags".
                    var valStr = Encoding.ASCII.GetString(val.Span);
                    var parts  = valStr.Split('-');
                    if (parts.Length == 4 && parts[3].Length == 2 &&
                        int.TryParse(parts[3],
                            System.Globalization.NumberStyles.HexNumber,
                            null, out var hexVal) &&
                        (hexVal & 0x1) == 0x1)
                    {
                        traceParentFound = true;
                    }
                }

                keys.Add(key);
                vals.Add(val);
            }

            i = valStart + nl + 2; // skip past CRLF
        }

        if (!traceDestFound && !traceParentFound)
            return empty;

        var map = new Dictionary<string, List<string>>(StringComparer.Ordinal);
        for (int k = 0; k < keys.Count; k++)
        {
            var kStr = Encoding.ASCII.GetString(keys[k].Span);
            var vStr = Encoding.ASCII.GetString(vals[k].Span);
            if (!map.TryGetValue(kStr, out var list))
            {
                list = [];
                map[kStr] = list;
            }
            list.Add(vStr);
        }

        bool isExternal = !traceDestFound && traceParentFound;
        return (map, isExternal);
    }

    // -------------------------------------------------------------------------
    // initAndSendIngressErrEvent
    // -------------------------------------------------------------------------

    /// <summary>
    /// Creates a minimal trace state and immediately sends the ingress-error
    /// event. Used when an error is detected early (e.g. max-payload exceeded)
    /// before normal message processing begins.
    /// Mirrors Go <c>client.initAndSendIngressErrEvent()</c>.
    /// </summary>
    public static void InitAndSendIngressErrEvent(
        ClientConnection c,
        byte[] hdr,
        string dest,
        Exception? ingressError)
    {
        if (ingressError is null) return;

        var ct    = GetAcceptEncodingCompressionType(hdr);
        var trace = new MsgTraceState
        {
            Server          = c.Server,
            Account         = c._account as Account,
            Dest            = dest,
            CompressionType = ct,
            Event           = new MsgTraceEvent
            {
                Request = new MsgTraceRequest { MsgSize = c.ParseCtx.Pa.Size },
                Events  =
                [
                    new MsgTraceIngress
                    {
                        Type      = MsgTraceType.Ingress,
                        Timestamp = DateTime.UtcNow,
                        Kind      = (int)c.Kind,
                        Cid       = c.Cid,
                        Name      = GetConnName(c),
                        Error     = ingressError.Message,
                    },
                ],
            },
        };

        trace.SendEvent();
    }

    // -------------------------------------------------------------------------
    // Private helpers
    // -------------------------------------------------------------------------

    /// <summary>
    /// Extracts the Accept-Encoding value from a raw header block and maps it
    /// to a <see cref="TraceCompressionType"/>.
    /// Mirrors Go <c>getAcceptEncoding(hdr []byte)</c> in server/client.go.
    /// </summary>
    private static TraceCompressionType GetAcceptEncodingCompressionType(byte[] hdr)
    {
        if (hdr.Length == 0) return TraceCompressionType.None;
        var value = NatsMessageHeaders.GetHeader(AcceptEncodingHeader, hdr);
        if (value is null || value.Length == 0) return TraceCompressionType.None;
        return GetCompressionType(Encoding.ASCII.GetString(value));
    }
}
