// Copyright 2020-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/jetstream_errors.go and server/jetstream_errors_generated.go
// in the NATS server Go source.

using System.Text.Json.Serialization;

namespace ZB.MOM.NatsNet.Server;

// ---------------------------------------------------------------------------
// JsApiError
// ---------------------------------------------------------------------------

/// <summary>
/// Included in all JetStream API responses when there was an error.
/// Mirrors <c>ApiError</c> in server/jetstream_errors.go.
/// </summary>
public sealed class JsApiError
{
    [JsonPropertyName("code")]        public int    Code        { get; set; }
    [JsonPropertyName("err_code")]    public ushort ErrCode     { get; set; }
    [JsonPropertyName("description")] public string? Description { get; set; }

    public override string ToString() => $"{Description} ({ErrCode})";
}

// ---------------------------------------------------------------------------
// JsApiErrors — all 203 error code constants
// ---------------------------------------------------------------------------

/// <summary>
/// Pre-built <see cref="JsApiError"/> instances for all JetStream error codes.
/// Mirrors the <c>ApiErrors</c> map in server/jetstream_errors_generated.go.
/// </summary>
public static partial class JsApiErrors
{
    public delegate object? ErrorOption();

    // ---- Account ----
    public static readonly JsApiError AccountResourcesExceeded                = new() { Code = 400, ErrCode = 10002, Description = "resource limits exceeded for account" };

    // ---- Atomic Publish ----
    public static readonly JsApiError AtomicPublishContainsDuplicateMessage   = new() { Code = 400, ErrCode = 10201, Description = "atomic publish batch contains duplicate message id" };
    public static readonly JsApiError AtomicPublishDisabled                   = new() { Code = 400, ErrCode = 10174, Description = "atomic publish is disabled" };
    public static readonly JsApiError AtomicPublishIncompleteBatch            = new() { Code = 400, ErrCode = 10176, Description = "atomic publish batch is incomplete" };
    public static readonly JsApiError AtomicPublishInvalidBatchCommit         = new() { Code = 400, ErrCode = 10200, Description = "atomic publish batch commit is invalid" };
    public static readonly JsApiError AtomicPublishInvalidBatchID             = new() { Code = 400, ErrCode = 10179, Description = "atomic publish batch ID is invalid" };
    public static readonly JsApiError AtomicPublishMissingSeq                 = new() { Code = 400, ErrCode = 10175, Description = "atomic publish sequence is missing" };
    public static readonly JsApiError AtomicPublishTooLargeBatch              = new() { Code = 400, ErrCode = 10199, Description = "atomic publish batch is too large: {size}" };
    public static readonly JsApiError AtomicPublishUnsupportedHeaderBatch     = new() { Code = 400, ErrCode = 10177, Description = "atomic publish unsupported header used: {header}" };

    // ---- General ----
    public static readonly JsApiError BadRequest                              = new() { Code = 400, ErrCode = 10003, Description = "bad request" };

    // ---- Cluster ----
    public static readonly JsApiError ClusterIncomplete                       = new() { Code = 503, ErrCode = 10004, Description = "incomplete results" };
    public static readonly JsApiError ClusterNoPeers                         = new() { Code = 400, ErrCode = 10005, Description = "{err}" };
    public static readonly JsApiError ClusterNotActive                        = new() { Code = 500, ErrCode = 10006, Description = "JetStream not in clustered mode" };
    public static readonly JsApiError ClusterNotAssigned                      = new() { Code = 500, ErrCode = 10007, Description = "JetStream cluster not assigned to this server" };
    public static readonly JsApiError ClusterNotAvail                         = new() { Code = 503, ErrCode = 10008, Description = "JetStream system temporarily unavailable" };
    public static readonly JsApiError ClusterNotLeader                        = new() { Code = 500, ErrCode = 10009, Description = "JetStream cluster can not handle request" };
    public static readonly JsApiError ClusterPeerNotMember                    = new() { Code = 400, ErrCode = 10040, Description = "peer not a member" };
    public static readonly JsApiError ClusterRequired                         = new() { Code = 503, ErrCode = 10010, Description = "JetStream clustering support required" };
    public static readonly JsApiError ClusterServerMemberChangeInflight       = new() { Code = 400, ErrCode = 10202, Description = "cluster member change is in progress" };
    public static readonly JsApiError ClusterServerNotMember                  = new() { Code = 400, ErrCode = 10044, Description = "server is not a member of the cluster" };
    public static readonly JsApiError ClusterTags                             = new() { Code = 400, ErrCode = 10011, Description = "tags placement not supported for operation" };
    public static readonly JsApiError ClusterUnSupportFeature                 = new() { Code = 503, ErrCode = 10036, Description = "not currently supported in clustered mode" };

    // ---- Consumer ----
    public static readonly JsApiError ConsumerAckPolicyInvalid                = new() { Code = 400, ErrCode = 10181, Description = "consumer ack policy invalid" };
    public static readonly JsApiError ConsumerAckWaitNegative                 = new() { Code = 400, ErrCode = 10183, Description = "consumer ack wait needs to be positive" };
    public static readonly JsApiError ConsumerAlreadyExists                   = new() { Code = 400, ErrCode = 10148, Description = "consumer already exists" };
    public static readonly JsApiError ConsumerBackOffNegative                 = new() { Code = 400, ErrCode = 10184, Description = "consumer backoff needs to be positive" };
    public static readonly JsApiError ConsumerBadDurableName                  = new() { Code = 400, ErrCode = 10103, Description = "durable name can not contain '.', '*', '>'" };
    public static readonly JsApiError ConsumerConfigRequired                  = new() { Code = 400, ErrCode = 10078, Description = "consumer config required" };
    public static readonly JsApiError ConsumerCreateDurableAndNameMismatch    = new() { Code = 400, ErrCode = 10132, Description = "Consumer Durable and Name have to be equal if both are provided" };
    public static readonly JsApiError ConsumerCreateErr                       = new() { Code = 500, ErrCode = 10012, Description = "{err}" };
    public static readonly JsApiError ConsumerCreateFilterSubjectMismatch     = new() { Code = 400, ErrCode = 10131, Description = "Consumer create request did not match filtered subject from create subject" };
    public static readonly JsApiError ConsumerDeliverCycle                    = new() { Code = 400, ErrCode = 10081, Description = "consumer deliver subject forms a cycle" };
    public static readonly JsApiError ConsumerDeliverToWildcards              = new() { Code = 400, ErrCode = 10079, Description = "consumer deliver subject has wildcards" };
    public static readonly JsApiError ConsumerDescriptionTooLong              = new() { Code = 400, ErrCode = 10107, Description = "consumer description is too long, maximum allowed is {max}" };
    public static readonly JsApiError ConsumerDirectRequiresEphemeral         = new() { Code = 400, ErrCode = 10091, Description = "consumer direct requires an ephemeral consumer" };
    public static readonly JsApiError ConsumerDirectRequiresPush              = new() { Code = 400, ErrCode = 10090, Description = "consumer direct requires a push based consumer" };
    public static readonly JsApiError ConsumerDoesNotExist                    = new() { Code = 400, ErrCode = 10149, Description = "consumer does not exist" };
    public static readonly JsApiError ConsumerDuplicateFilterSubjects         = new() { Code = 400, ErrCode = 10136, Description = "consumer cannot have both FilterSubject and FilterSubjects specified" };
    public static readonly JsApiError ConsumerDurableNameNotInSubject         = new() { Code = 400, ErrCode = 10016, Description = "consumer expected to be durable but no durable name set in subject" };
    public static readonly JsApiError ConsumerDurableNameNotMatchSubject      = new() { Code = 400, ErrCode = 10017, Description = "consumer name in subject does not match durable name in request" };
    public static readonly JsApiError ConsumerDurableNameNotSet               = new() { Code = 400, ErrCode = 10018, Description = "consumer expected to be durable but a durable name was not set" };
    public static readonly JsApiError ConsumerEmptyFilter                     = new() { Code = 400, ErrCode = 10139, Description = "consumer filter in FilterSubjects cannot be empty" };
    public static readonly JsApiError ConsumerEmptyGroupName                  = new() { Code = 400, ErrCode = 10161, Description = "Group name cannot be an empty string" };
    public static readonly JsApiError ConsumerEphemeralWithDurableInSubject   = new() { Code = 400, ErrCode = 10019, Description = "consumer expected to be ephemeral but detected a durable name set in subject" };
    public static readonly JsApiError ConsumerEphemeralWithDurableName        = new() { Code = 400, ErrCode = 10020, Description = "consumer expected to be ephemeral but a durable name was set in request" };
    public static readonly JsApiError ConsumerExistingActive                  = new() { Code = 400, ErrCode = 10105, Description = "consumer already exists and is still active" };
    public static readonly JsApiError ConsumerFCRequiresPush                  = new() { Code = 400, ErrCode = 10089, Description = "consumer flow control requires a push based consumer" };
    public static readonly JsApiError ConsumerFilterNotSubset                 = new() { Code = 400, ErrCode = 10093, Description = "consumer filter subject is not a valid subset of the interest subjects" };
    public static readonly JsApiError ConsumerHBRequiresPush                  = new() { Code = 400, ErrCode = 10088, Description = "consumer idle heartbeat requires a push based consumer" };
    public static readonly JsApiError ConsumerInactiveThresholdExcess         = new() { Code = 400, ErrCode = 10153, Description = "consumer inactive threshold exceeds system limit of {limit}" };
    public static readonly JsApiError ConsumerInvalidDeliverSubject           = new() { Code = 400, ErrCode = 10112, Description = "invalid push consumer deliver subject" };
    public static readonly JsApiError ConsumerInvalidGroupName                = new() { Code = 400, ErrCode = 10162, Description = "Valid priority group name must match A-Z, a-z, 0-9, -_/=)+ and may not exceed 16 characters" };
    public static readonly JsApiError ConsumerInvalidPolicy                   = new() { Code = 400, ErrCode = 10094, Description = "{err}" };
    public static readonly JsApiError ConsumerInvalidPriorityGroup            = new() { Code = 400, ErrCode = 10160, Description = "Provided priority group does not exist for this consumer" };
    public static readonly JsApiError ConsumerInvalidReset                    = new() { Code = 400, ErrCode = 10204, Description = "invalid reset: {err}" };
    public static readonly JsApiError ConsumerInvalidSampling                 = new() { Code = 400, ErrCode = 10095, Description = "failed to parse consumer sampling configuration: {err}" };
    public static readonly JsApiError ConsumerMaxDeliverBackoff               = new() { Code = 400, ErrCode = 10116, Description = "max deliver is required to be > length of backoff values" };
    public static readonly JsApiError ConsumerMaxPendingAckExcess             = new() { Code = 400, ErrCode = 10121, Description = "consumer max ack pending exceeds system limit of {limit}" };
    public static readonly JsApiError ConsumerMaxPendingAckPolicyRequired     = new() { Code = 400, ErrCode = 10082, Description = "consumer requires ack policy for max ack pending" };
    public static readonly JsApiError ConsumerMaxRequestBatchExceeded         = new() { Code = 400, ErrCode = 10125, Description = "consumer max request batch exceeds server limit of {limit}" };
    public static readonly JsApiError ConsumerMaxRequestBatchNegative         = new() { Code = 400, ErrCode = 10114, Description = "consumer max request batch needs to be > 0" };
    public static readonly JsApiError ConsumerMaxRequestExpiresTooSmall       = new() { Code = 400, ErrCode = 10115, Description = "consumer max request expires needs to be >= 1ms" };
    public static readonly JsApiError ConsumerMaxWaitingNegative              = new() { Code = 400, ErrCode = 10087, Description = "consumer max waiting needs to be positive" };
    public static readonly JsApiError ConsumerMetadataLength                  = new() { Code = 400, ErrCode = 10135, Description = "consumer metadata exceeds maximum size of {limit}" };
    public static readonly JsApiError ConsumerMultipleFiltersNotAllowed       = new() { Code = 400, ErrCode = 10137, Description = "consumer with multiple subject filters cannot use subject based API" };
    public static readonly JsApiError ConsumerNameContainsPathSeparators      = new() { Code = 400, ErrCode = 10127, Description = "Consumer name can not contain path separators" };
    public static readonly JsApiError ConsumerNameExist                       = new() { Code = 400, ErrCode = 10013, Description = "consumer name already in use" };
    public static readonly JsApiError ConsumerNameTooLong                     = new() { Code = 400, ErrCode = 10102, Description = "consumer name is too long, maximum allowed is {max}" };
    public static readonly JsApiError ConsumerNotFound                        = new() { Code = 404, ErrCode = 10014, Description = "consumer not found" };
    public static readonly JsApiError ConsumerOffline                         = new() { Code = 500, ErrCode = 10119, Description = "consumer is offline" };
    public static readonly JsApiError ConsumerOfflineReason                   = new() { Code = 500, ErrCode = 10195, Description = "consumer is offline: {err}" };
    public static readonly JsApiError ConsumerOnMapped                        = new() { Code = 400, ErrCode = 10092, Description = "consumer direct on a mapped consumer" };
    public static readonly JsApiError ConsumerOverlappingSubjectFilters       = new() { Code = 400, ErrCode = 10138, Description = "consumer subject filters cannot overlap" };
    public static readonly JsApiError ConsumerPinnedTTLWithoutPriorityPolicyNone = new() { Code = 400, ErrCode = 10197, Description = "PinnedTTL cannot be set when PriorityPolicy is none" };
    public static readonly JsApiError ConsumerPriorityGroupWithPolicyNone     = new() { Code = 400, ErrCode = 10196, Description = "consumer can not have priority groups when policy is none" };
    public static readonly JsApiError ConsumerPriorityPolicyWithoutGroup      = new() { Code = 400, ErrCode = 10159, Description = "Setting PriorityPolicy requires at least one PriorityGroup to be set" };
    public static readonly JsApiError ConsumerPullNotDurable                  = new() { Code = 400, ErrCode = 10085, Description = "consumer in pull mode requires a durable name" };
    public static readonly JsApiError ConsumerPullRequiresAck                 = new() { Code = 400, ErrCode = 10084, Description = "consumer in pull mode requires explicit ack policy on workqueue stream" };
    public static readonly JsApiError ConsumerPullWithRateLimit               = new() { Code = 400, ErrCode = 10086, Description = "consumer in pull mode can not have rate limit set" };
    public static readonly JsApiError ConsumerPushMaxWaiting                  = new() { Code = 400, ErrCode = 10080, Description = "consumer in push mode can not set max waiting" };
    public static readonly JsApiError ConsumerPushWithPriorityGroup           = new() { Code = 400, ErrCode = 10178, Description = "priority groups can not be used with push consumers" };
    public static readonly JsApiError ConsumerReplacementWithDifferentName    = new() { Code = 400, ErrCode = 10106, Description = "consumer replacement durable config not the same" };
    public static readonly JsApiError ConsumerReplayPolicyInvalid             = new() { Code = 400, ErrCode = 10182, Description = "consumer replay policy invalid" };
    public static readonly JsApiError ConsumerReplicasExceedsStream           = new() { Code = 400, ErrCode = 10126, Description = "consumer config replica count exceeds parent stream" };
    public static readonly JsApiError ConsumerReplicasShouldMatchStream       = new() { Code = 400, ErrCode = 10134, Description = "consumer config replicas must match interest retention stream's replicas" };
    public static readonly JsApiError ConsumerSmallHeartbeat                  = new() { Code = 400, ErrCode = 10083, Description = "consumer idle heartbeat needs to be >= 100ms" };
    public static readonly JsApiError ConsumerStoreFailed                     = new() { Code = 500, ErrCode = 10104, Description = "error creating store for consumer: {err}" };
    public static readonly JsApiError ConsumerWQConsumerNotDeliverAll         = new() { Code = 400, ErrCode = 10101, Description = "consumer must be deliver all on workqueue stream" };
    public static readonly JsApiError ConsumerWQConsumerNotUnique             = new() { Code = 400, ErrCode = 10100, Description = "filtered consumer not unique on workqueue stream" };
    public static readonly JsApiError ConsumerWQMultipleUnfiltered            = new() { Code = 400, ErrCode = 10099, Description = "multiple non-filtered consumers not allowed on workqueue stream" };
    public static readonly JsApiError ConsumerWQRequiresExplicitAck           = new() { Code = 400, ErrCode = 10098, Description = "workqueue stream requires explicit ack" };
    public static readonly JsApiError ConsumerWithFlowControlNeedsHeartbeats  = new() { Code = 400, ErrCode = 10108, Description = "consumer with flow control also needs heartbeats" };

    // ---- Resources ----
    public static readonly JsApiError InsufficientResources                   = new() { Code = 503, ErrCode = 10023, Description = "insufficient resources" };
    public static readonly JsApiError InvalidJSON                             = new() { Code = 400, ErrCode = 10025, Description = "invalid JSON: {err}" };
    public static readonly JsApiError MaximumConsumersLimit                   = new() { Code = 400, ErrCode = 10026, Description = "maximum consumers limit reached" };
    public static readonly JsApiError MaximumStreamsLimit                     = new() { Code = 400, ErrCode = 10027, Description = "maximum number of streams reached" };
    public static readonly JsApiError MemoryResourcesExceeded                 = new() { Code = 500, ErrCode = 10028, Description = "insufficient memory resources available" };

    // ---- Message Counter ----
    public static readonly JsApiError MessageCounterBroken                    = new() { Code = 400, ErrCode = 10172, Description = "message counter is broken" };
    public static readonly JsApiError MessageIncrDisabled                     = new() { Code = 400, ErrCode = 10168, Description = "message counters is disabled" };
    public static readonly JsApiError MessageIncrInvalid                      = new() { Code = 400, ErrCode = 10171, Description = "message counter increment is invalid" };
    public static readonly JsApiError MessageIncrMissing                      = new() { Code = 400, ErrCode = 10169, Description = "message counter increment is missing" };
    public static readonly JsApiError MessageIncrPayload                      = new() { Code = 400, ErrCode = 10170, Description = "message counter has payload" };

    // ---- Message Schedules ----
    public static readonly JsApiError MessageSchedulesDisabled                = new() { Code = 400, ErrCode = 10188, Description = "message schedules is disabled" };
    public static readonly JsApiError MessageSchedulesPatternInvalid          = new() { Code = 400, ErrCode = 10189, Description = "message schedules pattern is invalid" };
    public static readonly JsApiError MessageSchedulesRollupInvalid           = new() { Code = 400, ErrCode = 10192, Description = "message schedules invalid rollup" };
    public static readonly JsApiError MessageSchedulesSourceInvalid           = new() { Code = 400, ErrCode = 10203, Description = "message schedules source is invalid" };
    public static readonly JsApiError MessageSchedulesTTLInvalid              = new() { Code = 400, ErrCode = 10191, Description = "message schedules invalid per-message TTL" };
    public static readonly JsApiError MessageSchedulesTargetInvalid           = new() { Code = 400, ErrCode = 10190, Description = "message schedules target is invalid" };

    // ---- Message TTL ----
    public static readonly JsApiError MessageTTLDisabled                      = new() { Code = 400, ErrCode = 10166, Description = "per-message TTL is disabled" };
    public static readonly JsApiError MessageTTLInvalid                       = new() { Code = 400, ErrCode = 10165, Description = "invalid per-message TTL" };

    // ---- Mirror ----
    public static readonly JsApiError MirrorConsumerSetupFailed               = new() { Code = 500, ErrCode = 10029, Description = "{err}" };
    public static readonly JsApiError MirrorInvalidStreamName                 = new() { Code = 400, ErrCode = 10142, Description = "mirrored stream name is invalid" };
    public static readonly JsApiError MirrorInvalidSubjectFilter              = new() { Code = 400, ErrCode = 10151, Description = "mirror transform source: {err}" };
    public static readonly JsApiError MirrorInvalidTransformDestination       = new() { Code = 400, ErrCode = 10154, Description = "mirror transform: {err}" };
    public static readonly JsApiError MirrorMaxMessageSizeTooBig              = new() { Code = 400, ErrCode = 10030, Description = "stream mirror must have max message size >= source" };
    public static readonly JsApiError MirrorMultipleFiltersNotAllowed         = new() { Code = 400, ErrCode = 10150, Description = "mirror with multiple subject transforms cannot also have a single subject filter" };
    public static readonly JsApiError MirrorOverlappingSubjectFilters         = new() { Code = 400, ErrCode = 10152, Description = "mirror subject filters can not overlap" };
    public static readonly JsApiError MirrorWithAtomicPublish                 = new() { Code = 400, ErrCode = 10198, Description = "stream mirrors can not also use atomic publishing" };
    public static readonly JsApiError MirrorWithCounters                      = new() { Code = 400, ErrCode = 10173, Description = "stream mirrors can not also calculate counters" };
    public static readonly JsApiError MirrorWithFirstSeq                      = new() { Code = 400, ErrCode = 10143, Description = "stream mirrors can not have first sequence configured" };
    public static readonly JsApiError MirrorWithMsgSchedules                  = new() { Code = 400, ErrCode = 10186, Description = "stream mirrors can not also schedule messages" };
    public static readonly JsApiError MirrorWithSources                       = new() { Code = 400, ErrCode = 10031, Description = "stream mirrors can not also contain other sources" };
    public static readonly JsApiError MirrorWithStartSeqAndTime               = new() { Code = 400, ErrCode = 10032, Description = "stream mirrors can not have both start seq and start time configured" };
    public static readonly JsApiError MirrorWithSubjectFilters                = new() { Code = 400, ErrCode = 10033, Description = "stream mirrors can not contain filtered subjects" };
    public static readonly JsApiError MirrorWithSubjects                      = new() { Code = 400, ErrCode = 10034, Description = "stream mirrors can not contain subjects" };

    // ---- Misc ----
    public static readonly JsApiError NoAccount                               = new() { Code = 503, ErrCode = 10035, Description = "account not found" };
    public static readonly JsApiError NoLimits                                = new() { Code = 400, ErrCode = 10120, Description = "no JetStream default or applicable tiered limit present" };
    public static readonly JsApiError NoMessageFound                          = new() { Code = 404, ErrCode = 10037, Description = "no message found" };
    public static readonly JsApiError NotEmptyRequest                         = new() { Code = 400, ErrCode = 10038, Description = "expected an empty request payload" };
    public static readonly JsApiError NotEnabled                              = new() { Code = 503, ErrCode = 10076, Description = "JetStream not enabled" };
    public static readonly JsApiError NotEnabledForAccount                    = new() { Code = 503, ErrCode = 10039, Description = "JetStream not enabled for account" };
    public static readonly JsApiError Pedantic                                = new() { Code = 400, ErrCode = 10157, Description = "pedantic mode: {err}" };
    public static readonly JsApiError PeerRemap                               = new() { Code = 503, ErrCode = 10075, Description = "peer remap failed" };

    // ---- RAFT ----
    public static readonly JsApiError RaftGeneralErr                          = new() { Code = 500, ErrCode = 10041, Description = "{err}" };

    // ---- Replicas ----
    public static readonly JsApiError ReplicasCountCannotBeNegative           = new() { Code = 400, ErrCode = 10133, Description = "replicas count cannot be negative" };
    public static readonly JsApiError RequiredApiLevel                        = new() { Code = 412, ErrCode = 10185, Description = "JetStream minimum api level required" };

    // ---- Restore ----
    public static readonly JsApiError RestoreSubscribeFailed                  = new() { Code = 500, ErrCode = 10042, Description = "JetStream unable to subscribe to restore snapshot {subject}: {err}" };

    // ---- Sequence ----
    public static readonly JsApiError SequenceNotFound                        = new() { Code = 400, ErrCode = 10043, Description = "sequence {seq} not found" };
    public static readonly JsApiError SnapshotDeliverSubjectInvalid           = new() { Code = 400, ErrCode = 10015, Description = "deliver subject not valid" };

    // ---- Source ----
    public static readonly JsApiError SourceConsumerSetupFailed               = new() { Code = 500, ErrCode = 10045, Description = "{err}" };
    public static readonly JsApiError SourceDuplicateDetected                 = new() { Code = 400, ErrCode = 10140, Description = "duplicate source configuration detected" };
    public static readonly JsApiError SourceInvalidStreamName                 = new() { Code = 400, ErrCode = 10141, Description = "sourced stream name is invalid" };
    public static readonly JsApiError SourceInvalidSubjectFilter              = new() { Code = 400, ErrCode = 10145, Description = "source transform source: {err}" };
    public static readonly JsApiError SourceInvalidTransformDestination       = new() { Code = 400, ErrCode = 10146, Description = "source transform: {err}" };
    public static readonly JsApiError SourceMaxMessageSizeTooBig              = new() { Code = 400, ErrCode = 10046, Description = "stream source must have max message size >= target" };
    public static readonly JsApiError SourceMultipleFiltersNotAllowed         = new() { Code = 400, ErrCode = 10144, Description = "source with multiple subject transforms cannot also have a single subject filter" };
    public static readonly JsApiError SourceOverlappingSubjectFilters         = new() { Code = 400, ErrCode = 10147, Description = "source filters can not overlap" };
    public static readonly JsApiError SourceWithMsgSchedules                  = new() { Code = 400, ErrCode = 10187, Description = "stream source can not also schedule messages" };

    // ---- Storage ----
    public static readonly JsApiError StorageResourcesExceeded                = new() { Code = 500, ErrCode = 10047, Description = "insufficient storage resources available" };

    // ---- Stream ----
    public static readonly JsApiError StreamAssignment                        = new() { Code = 500, ErrCode = 10048, Description = "{err}" };
    public static readonly JsApiError StreamCreate                            = new() { Code = 500, ErrCode = 10049, Description = "{err}" };
    public static readonly JsApiError StreamDelete                            = new() { Code = 500, ErrCode = 10050, Description = "{err}" };
    public static readonly JsApiError StreamDuplicateMessageConflict          = new() { Code = 409, ErrCode = 10158, Description = "duplicate message id is in process" };
    public static readonly JsApiError StreamExpectedLastSeqPerSubjectInvalid  = new() { Code = 400, ErrCode = 10193, Description = "missing sequence for expected last sequence per subject" };
    public static readonly JsApiError StreamExpectedLastSeqPerSubjectNotReady = new() { Code = 503, ErrCode = 10163, Description = "expected last sequence per subject temporarily unavailable" };
    public static readonly JsApiError StreamExternalApiOverlap                = new() { Code = 400, ErrCode = 10021, Description = "stream external api prefix {prefix} must not overlap with {subject}" };
    public static readonly JsApiError StreamExternalDelPrefixOverlaps         = new() { Code = 400, ErrCode = 10022, Description = "stream external delivery prefix {prefix} overlaps with stream subject {subject}" };
    public static readonly JsApiError StreamGeneralError                      = new() { Code = 500, ErrCode = 10051, Description = "{err}" };
    public static readonly JsApiError StreamHeaderExceedsMaximum              = new() { Code = 400, ErrCode = 10097, Description = "header size exceeds maximum allowed of 64k" };
    public static readonly JsApiError StreamInfoMaxSubjects                   = new() { Code = 500, ErrCode = 10117, Description = "subject details would exceed maximum allowed" };
    public static readonly JsApiError StreamInvalidConfig                     = new() { Code = 500, ErrCode = 10052, Description = "{err}" };
    public static readonly JsApiError StreamInvalid                           = new() { Code = 500, ErrCode = 10096, Description = "stream not valid" };
    public static readonly JsApiError StreamInvalidExternalDeliverySubj       = new() { Code = 400, ErrCode = 10024, Description = "stream external delivery prefix {prefix} must not contain wildcards" };
    public static readonly JsApiError StreamLimits                            = new() { Code = 500, ErrCode = 10053, Description = "{err}" };
    public static readonly JsApiError StreamMaxBytesRequired                  = new() { Code = 400, ErrCode = 10113, Description = "account requires a stream config to have max bytes set" };
    public static readonly JsApiError StreamMaxStreamBytesExceeded            = new() { Code = 400, ErrCode = 10122, Description = "stream max bytes exceeds account limit max stream bytes" };
    public static readonly JsApiError StreamMessageExceedsMaximum             = new() { Code = 400, ErrCode = 10054, Description = "message size exceeds maximum allowed" };
    public static readonly JsApiError StreamMinLastSeq                        = new() { Code = 412, ErrCode = 10180, Description = "min last sequence" };
    public static readonly JsApiError StreamMirrorNotUpdatable                = new() { Code = 400, ErrCode = 10055, Description = "stream mirror configuration can not be updated" };
    public static readonly JsApiError StreamMismatch                          = new() { Code = 400, ErrCode = 10056, Description = "stream name in subject does not match request" };
    public static readonly JsApiError StreamMoveAndScale                      = new() { Code = 400, ErrCode = 10123, Description = "can not move and scale a stream in a single update" };
    public static readonly JsApiError StreamMoveInProgress                    = new() { Code = 400, ErrCode = 10124, Description = "stream move already in progress: {msg}" };
    public static readonly JsApiError StreamMoveNotInProgress                 = new() { Code = 400, ErrCode = 10129, Description = "stream move not in progress" };
    public static readonly JsApiError StreamMsgDeleteFailed                   = new() { Code = 500, ErrCode = 10057, Description = "{err}" };
    public static readonly JsApiError StreamNameContainsPathSeparators        = new() { Code = 400, ErrCode = 10128, Description = "Stream name can not contain path separators" };
    public static readonly JsApiError StreamNameExist                         = new() { Code = 400, ErrCode = 10058, Description = "stream name already in use with a different configuration" };
    public static readonly JsApiError StreamNameExistRestoreFailed            = new() { Code = 400, ErrCode = 10130, Description = "stream name already in use, cannot restore" };
    public static readonly JsApiError StreamNotFound                          = new() { Code = 404, ErrCode = 10059, Description = "stream not found" };
    public static readonly JsApiError StreamNotMatch                          = new() { Code = 400, ErrCode = 10060, Description = "expected stream does not match" };
    public static readonly JsApiError StreamOffline                           = new() { Code = 500, ErrCode = 10118, Description = "stream is offline" };
    public static readonly JsApiError StreamOfflineReason                     = new() { Code = 500, ErrCode = 10194, Description = "stream is offline: {err}" };
    public static readonly JsApiError StreamPurgeFailed                       = new() { Code = 500, ErrCode = 10110, Description = "{err}" };
    public static readonly JsApiError StreamReplicasNotSupported              = new() { Code = 500, ErrCode = 10074, Description = "replicas > 1 not supported in non-clustered mode" };
    public static readonly JsApiError StreamReplicasNotUpdatable              = new() { Code = 400, ErrCode = 10061, Description = "Replicas configuration can not be updated" };
    public static readonly JsApiError StreamRestore                           = new() { Code = 500, ErrCode = 10062, Description = "restore failed: {err}" };
    public static readonly JsApiError StreamRollupFailed                      = new() { Code = 500, ErrCode = 10111, Description = "{err}" };
    public static readonly JsApiError StreamSealed                            = new() { Code = 400, ErrCode = 10109, Description = "invalid operation on sealed stream" };
    public static readonly JsApiError StreamSequenceNotMatch                  = new() { Code = 503, ErrCode = 10063, Description = "expected stream sequence does not match" };
    public static readonly JsApiError StreamSnapshot                          = new() { Code = 500, ErrCode = 10064, Description = "snapshot failed: {err}" };
    public static readonly JsApiError StreamStoreFailed                       = new() { Code = 503, ErrCode = 10077, Description = "{err}" };
    public static readonly JsApiError StreamSubjectOverlap                    = new() { Code = 400, ErrCode = 10065, Description = "subjects overlap with an existing stream" };
    public static readonly JsApiError StreamTemplateCreate                    = new() { Code = 500, ErrCode = 10066, Description = "{err}" };
    public static readonly JsApiError StreamTemplateDelete                    = new() { Code = 500, ErrCode = 10067, Description = "{err}" };
    public static readonly JsApiError StreamTemplateNotFound                  = new() { Code = 404, ErrCode = 10068, Description = "template not found" };
    public static readonly JsApiError StreamTooManyRequests                   = new() { Code = 429, ErrCode = 10167, Description = "too many requests" };
    public static readonly JsApiError StreamTransformInvalidDestination       = new() { Code = 400, ErrCode = 10156, Description = "stream transform: {err}" };
    public static readonly JsApiError StreamTransformInvalidSource            = new() { Code = 400, ErrCode = 10155, Description = "stream transform source: {err}" };
    public static readonly JsApiError StreamUpdate                            = new() { Code = 500, ErrCode = 10069, Description = "{err}" };
    public static readonly JsApiError StreamWrongLastMsgID                    = new() { Code = 400, ErrCode = 10070, Description = "wrong last msg ID: {id}" };
    public static readonly JsApiError StreamWrongLastSequenceConstant         = new() { Code = 400, ErrCode = 10164, Description = "wrong last sequence" };
    public static readonly JsApiError StreamWrongLastSequence                 = new() { Code = 400, ErrCode = 10071, Description = "wrong last sequence: {seq}" };

    // ---- Temp storage ----
    public static readonly JsApiError TempStorageFailed                       = new() { Code = 500, ErrCode = 10072, Description = "JetStream unable to open temp storage for restore" };
    public static readonly JsApiError TemplateNameNotMatchSubject             = new() { Code = 400, ErrCode = 10073, Description = "template name in subject does not match request" };

    // ---------------------------------------------------------------------------
    // Lookup by ErrCode
    // ---------------------------------------------------------------------------

    private static readonly Dictionary<ushort, JsApiError> _byErrCode;

    static JsApiErrors()
    {
        _byErrCode = new Dictionary<ushort, JsApiError>();
        foreach (var field in typeof(JsApiErrors).GetFields(
            System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
        {
            if (field.GetValue(null) is JsApiError e)
                _byErrCode.TryAdd(e.ErrCode, e);
        }
    }

    /// <summary>
    /// Returns the pre-built <see cref="JsApiError"/> for the given err_code, or null if not found.
    /// </summary>
    public static JsApiError? ForErrCode(ushort errCode) =>
        _byErrCode.TryGetValue(errCode, out var e) ? e : null;

    /// <summary>
    /// Returns true if the given <see cref="JsApiError"/> matches one or more of the supplied err_codes.
    /// Mirrors <c>IsNatsErr</c> in server/jetstream_errors.go.
    /// </summary>
    public static bool IsNatsError(JsApiError? err, params ushort[] errCodes)
    {
        return IsNatsErr(err, errCodes);
    }

    /// <summary>
    /// Returns true if <paramref name="err"/> is a <see cref="JsApiError"/> and matches one of the supplied IDs.
    /// Unknown IDs are ignored, matching Go's map-based lookup behavior.
    /// </summary>
    public static bool IsNatsErr(object? err, params ushort[] ids)
    {
        if (err is not JsApiError ce)
            return false;

        foreach (var id in ids)
        {
            var ae = ForErrCode(id);
            if (ae != null && ce.ErrCode == ae.ErrCode)
                return true;
        }

        return false;
    }

    /// <summary>
    /// Formats an API error string exactly as Go <c>ApiError.Error()</c>.
    /// </summary>
    public static string Error(JsApiError? err) => err?.ToString() ?? string.Empty;

    /// <summary>
    /// Creates an option that causes constructor helpers to return the provided
    /// <see cref="JsApiError"/> when present.
    /// Mirrors Go <c>Unless</c>.
    /// </summary>
    public static ErrorOption Unless(object? err) => () => err;

    /// <summary>
    /// Mirrors Go <c>NewJSRestoreSubscribeFailedError</c>.
    /// </summary>
    public static JsApiError NewJSRestoreSubscribeFailedError(Exception err, string subject, params ErrorOption[] opts)
    {
        if (ParseOpts(opts) is JsApiError overridden)
            return Clone(overridden);

        return NewWithTags(RestoreSubscribeFailed, "{subject}", subject, "{err}", err);
    }

    /// <summary>
    /// Mirrors Go <c>NewJSStreamRestoreError</c>.
    /// </summary>
    public static JsApiError NewJSStreamRestoreError(Exception err, params ErrorOption[] opts)
    {
        if (ParseOpts(opts) is JsApiError overridden)
            return Clone(overridden);

        return NewWithTags(StreamRestore, "{err}", err);
    }

    /// <summary>
    /// Mirrors Go <c>NewJSPeerRemapError</c>.
    /// </summary>
    public static JsApiError NewJSPeerRemapError(params ErrorOption[] opts)
    {
        if (ParseOpts(opts) is JsApiError overridden)
            return Clone(overridden);

        return Clone(PeerRemap);
    }

    private static object? ParseOpts(params ErrorOption[] opts)
    {
        object? value = null;

        foreach (var opt in opts)
            value = opt();

        return value;
    }

    private static JsApiError Clone(JsApiError source) => new()
    {
        Code = source.Code,
        ErrCode = source.ErrCode,
        Description = source.Description,
    };

    private static string[] ToReplacerArgs(params object?[] replacements)
    {
        var args = new List<string>(replacements.Length);
        string key = string.Empty;

        for (var i = 0; i < replacements.Length; i++)
        {
            if (i % 2 == 0)
            {
                key = replacements[i] as string
                    ?? throw new InvalidOperationException("Replacement keys must be strings.");
                continue;
            }

            var value = replacements[i] switch
            {
                string s => s,
                Exception ex => ex.Message,
                null => string.Empty,
                var other => other.ToString() ?? string.Empty,
            };

            args.Add(key);
            args.Add(value);
        }

        return args.ToArray();
    }

    private static JsApiError NewWithTags(JsApiError source, params object?[] replacements)
    {
        var clone = Clone(source);
        var description = clone.Description ?? string.Empty;
        var args = ToReplacerArgs(replacements);

        for (var i = 0; i + 1 < args.Length; i += 2)
            description = description.Replace(args[i], args[i + 1], StringComparison.Ordinal);

        clone.Description = description;
        return clone;
    }
}
