namespace DodoSSH.Api.Features.Teams;

/// <summary>
/// A team create or membership change was structurally unacceptable.
/// </summary>
/// <remarks>
/// The message is returned to the caller. Keep it about the shape of their own request, and never
/// about accounts or teams they cannot already see — "no such user" is safe when the caller supplied
/// the id from a directory lookup they just made, and is an enumeration oracle everywhere else.
/// </remarks>
internal sealed class TeamInvalidException(string message) : Exception(message);

/// <summary>The requested slug is already in use.</summary>
/// <remarks>
/// Its own type because it is the one create failure the caller could not have predicted from their
/// own input, and the only one whose remedy is choosing a different value rather than fixing one.
/// </remarks>
internal sealed class TeamSlugTakenException(string message) : Exception(message);

/// <summary>The change would leave a team with no owner.</summary>
/// <remarks>
/// Refused rather than allowed: a team with no owner has nobody who can appoint one, so the only
/// route back would be an operator editing the database by hand. The deliberate way through it is a
/// transfer, which moves ownership and the outgoing owner's demotion together.
/// </remarks>
internal sealed class LastTeamOwnerException(string message) : Exception(message);

/// <summary>The team still owns vaults, so it cannot be archived.</summary>
/// <remarks>
/// Its own type because the remedy is neither fixing the request nor picking another value: archiving
/// would hide vaults from every member including the ones holding keys to them, and nothing in this
/// product deletes a vault, so there is no sequence of calls that turns this refusal into a success
/// today. Saying that plainly is better than a flag that hides somebody's data.
/// </remarks>
internal sealed class TeamNotEmptyException(string message) : Exception(message);

/// <summary>The address given to an add has no account on this server.</summary>
/// <remarks>
/// Separate from <see cref="TeamInvalidException"/> because it is the one refusal on this path that is
/// not about the request: the request was well formed and named somebody who is not here. Folded into
/// the general code it would be indistinguishable from a rejected role, and a client wanting to say
/// "ask them to sign in first" would have to guess which of the two it had.
/// <para>
/// It is the end of the road rather than a step on it. Membership is only ever granted to an account
/// that exists — see <c>docs/adr/0009-team-access-model.md</c> — so there is nothing else for a client
/// to try, and the honest answer is to name the address and say what has to happen before it can be
/// added.
/// </para>
/// </remarks>
internal sealed class NoSuchAccountException(string message) : Exception(message);

/// <summary>
/// A vault key grant was rejected.
/// </summary>
/// <remarks>
/// Never about the wrapped key's contents. The server cannot open it, so a grant sealing garbage is
/// accepted here and fails at the recipient as a tag failure, with the signature naming who issued
/// it. See docs/crypto.md §6.
/// </remarks>
internal sealed class VaultGrantInvalidException(string message) : Exception(message);
