namespace DodoSSH.Contracts;

/// <summary>
/// Stable machine-readable error codes returned in the <c>code</c> extension of an
/// RFC 9457 ProblemDetails response.
/// </summary>
/// <remarks>
/// These live in Contracts so the client switches on constants rather than parsing prose.
/// The values are part of the public contract: add freely, never rename or repurpose.
/// </remarks>
public static class ProblemCodes
{
    /// <summary>The base URI that every problem <c>type</c> is formed under.</summary>
    public const string TypeBaseUri = "https://dodossh.dev/problems/";

    /// <summary>A push operation's <c>expectedVersion</c> did not match the stored row.</summary>
    public const string VaultConflict = "vault-conflict";

    /// <summary>The caller is authenticated but lacks the required permission.</summary>
    public const string Forbidden = "forbidden";

    /// <summary>The sync cursor was malformed, or failed its integrity tag.</summary>
    public const string InvalidCursor = "invalid-cursor";

    /// <summary>An <c>Idempotency-Key</c> was reused with a different request body.</summary>
    public const string IdempotencyKeyReuse = "idempotency-key-reuse";

    /// <summary>The caller has not yet enrolled a public key, so no vault is reachable.</summary>
    public const string EnrollmentRequired = "enrollment-required";

    /// <summary>Enrollment was attempted for a user who already holds a different current key.</summary>
    public const string AlreadyEnrolled = "already-enrolled";

    /// <summary>
    /// The enrollment request was structurally invalid: a bad key length, mismatched KDF
    /// parameters, a statement that does not describe the caller, or a vault id already in use.
    /// </summary>
    public const string InvalidEnrollment = "invalid-enrollment";

    /// <summary>
    /// The identity-provider token did not bind the supplied keys: a bad signature, the wrong
    /// subject or audience, an expired token, or a <c>nonce</c> that is not the statement's hash.
    /// </summary>
    public const string IdentityBindingInvalid = "identity-binding-invalid";

    /// <summary>
    /// A device registration was structurally invalid: a public key of the wrong length, a missing or
    /// oversized wrap, or a blank name.
    /// </summary>
    /// <remarks>
    /// Distinct from <see cref="InvalidEnrollment"/> even though the rules overlap, because the two are
    /// different requests and a client showing "your enrollment was rejected" when somebody added a
    /// fingerprint reader would be describing the wrong thing entirely.
    /// </remarks>
    public const string InvalidDeviceRegistration = "invalid-device-registration";

    /// <summary>
    /// The request body could not be read at all: malformed JSON, or a property the server does not
    /// know.
    /// </summary>
    /// <remarks>
    /// A contract mismatch rather than a rejected value. The request never reached a handler, so no
    /// field-level detail is offered and none should be inferred from its absence. Compare the request
    /// against the <c>DodoSSH.Contracts</c> assembly for the server version <c>GET /api/v1/meta</c>
    /// reports.
    /// </remarks>
    public const string MalformedRequest = "malformed-request";

    /// <summary>
    /// This deployment does not push vault changes over a socket.
    /// </summary>
    /// <remarks>
    /// Not a failure to recover from: synchronising on a timer is the supported behaviour and the
    /// socket only ever made it early. A client that meets this stops dialling and keeps polling. See
    /// ADR 0012.
    /// </remarks>
    public const string EventsUnavailable = "events-unavailable";

    /// <summary>The relay refused the requested target. Never states why, to avoid a probe oracle.</summary>
    public const string RelayTargetRejected = "relay-target-rejected";

    /// <summary>The relay ticket is expired, already used, or not valid for this node.</summary>
    public const string RelayTicketInvalid = "relay-ticket-invalid";

    /// <summary>A per-user or per-node relay session limit was reached.</summary>
    public const string RelayLimitReached = "relay-limit-reached";

    /// <summary>The client is older than the server's <c>minClientVersion</c>.</summary>
    public const string ClientTooOld = "client-too-old";

    /// <summary>A push batch exceeded the operation count or payload size cap.</summary>
    public const string PushBatchTooLarge = "push-batch-too-large";

    /// <summary>
    /// A team create or membership change was structurally invalid: a blank name, a slug that is
    /// not URL-safe, an unknown role, or an account that does not exist here.
    /// </summary>
    public const string InvalidTeam = "invalid-team";

    /// <summary>
    /// The requested slug is already in use.
    /// </summary>
    /// <remarks>
    /// Its own code rather than folded into <see cref="InvalidTeam"/>, because it is the one create
    /// failure the caller could not have predicted from their own input and the only one whose
    /// remedy is "pick a different one" rather than "fix what you typed".
    /// </remarks>
    public const string TeamSlugTaken = "team-slug-taken";

    /// <summary>
    /// The change would leave a team with no owner.
    /// </summary>
    /// <remarks>
    /// Refused rather than allowed, because a team with no owner has nobody who can appoint one —
    /// and the only route back would be an operator editing the database by hand. The way past it is
    /// <c>POST /api/v1/teams/{teamId}/owner</c>, which moves ownership and the outgoing owner's
    /// demotion in one transaction; a client that gets this code can offer that.
    /// </remarks>
    public const string LastTeamOwner = "last-team-owner";

    /// <summary>
    /// A team cannot be archived while it still owns vaults.
    /// </summary>
    /// <remarks>
    /// Its own code because the remedy is neither "fix what you typed" nor "pick another value": it is to
    /// deal with the vaults first, by deleting each of them — <c>DELETE /api/v1/vaults/{vaultId}</c> — which
    /// asks about each one on the way. Archiving anyway would hide vaults from every member including the
    /// ones holding keys to them, which is the same removal without anybody having been asked.
    /// </remarks>
    public const string TeamNotEmpty = "team-not-empty";

    /// <summary>
    /// An address given to <c>POST /api/v1/teams/{teamId}/members</c> has no account on this server.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Its own code rather than folded into <see cref="InvalidTeam"/> because it is the one add failure
    /// that is not about the request: the request was fine and named somebody who is not here. A client
    /// that could not tell this apart from a rejected role would have to say "check what you typed"
    /// about an address that was typed correctly.
    /// </para>
    /// <para>
    /// <b>Nothing follows it.</b> This used to be the signal to invite the address instead; there are
    /// no invitations, and a membership is only ever granted to an account somebody named, so the whole
    /// of what a client can do with this is say that the person has to sign in here once. See
    /// <see cref="AddTeamMemberRequest"/>.
    /// </para>
    /// <para>
    /// It answers whether an address has an account here, which is a real disclosure and a bounded one:
    /// only an admin or owner of the team the add names reaches it — the endpoint checks that first —
    /// and it is the same fact the member list would show them a moment later.
    /// </para>
    /// </remarks>
    public const string NoSuchAccount = "no-such-account";

    /// <summary>
    /// A vault key grant was rejected: a fingerprint or wrap of the wrong size, a generation that is
    /// not the vault's current one, or a recipient who cannot reach the vault in the first place.
    /// </summary>
    /// <remarks>
    /// Never a statement about the wrapped key's <em>contents</em>. The server cannot open it, so a
    /// grant containing garbage is accepted here and surfaces at the recipient as a tag failure,
    /// with the signature naming who issued it. See docs/crypto.md §6.
    /// </remarks>
    public const string InvalidVaultGrant = "invalid-vault-grant";
}
