///|
/// A mention category Discord may derive directly from message content.
pub(all) enum AllowedMentionType {
  Roles
  Users
  Everyone
} derive(Eq, Debug)

///|
pub impl ToJson for AllowedMentionType with fn to_json(self) {
  Json::string(
    match self {
      Roles => "roles"
      Users => "users"
      Everyone => "everyone"
    },
  )
}

///|
/// An invalid allowed-mentions configuration rejected during construction.
pub(all) suberror AllowedMentionsError {
  ParseAndExplicit(typ~ : AllowedMentionType)
  TooManyRoles(count~ : Int)
  TooManyUsers(count~ : Int)
} derive(Eq, Debug)

///|
/// Controls which mentions Discord resolves and notifies for an outgoing
/// message.
///
/// Use `AllowedMentions(...)` so parse categories and explicit ID lists cannot
/// conflict. The stored arrays are copied during construction to preserve that
/// invariant if callers later mutate their input arrays.
pub struct AllowedMentions {
  priv parse : Array[AllowedMentionType]?
  priv roles : Array[RoleId]?
  priv users : Array[UserId]?
  priv replied_user : Bool?
} derive(Debug)

///|
/// Construct a validated allowed-mentions object.
///
/// ```mbt check
/// test "allow selected users without parsing every user mention" {
///   let user_id : @model.UserId = Id(42UL)
///   let mentions = @model.AllowedMentions(
///     parse=[Roles],
///     users=[user_id],
///     replied_user=false,
///   )
///   json_inspect(mentions, content={
///     "parse": ["roles"],
///     "users": ["42"],
///     "replied_user": false,
///   })
/// }
/// ```
pub fn AllowedMentions::AllowedMentions(
  parse? : Array[AllowedMentionType],
  roles? : Array[RoleId],
  users? : Array[UserId],
  replied_user? : Bool,
) -> AllowedMentions raise AllowedMentionsError {
  if roles is Some(ids) && ids.length() > 100 {
    raise TooManyRoles(count=ids.length())
  }
  if users is Some(ids) && ids.length() > 100 {
    raise TooManyUsers(count=ids.length())
  }
  if parse is Some(types) {
    if roles is Some(_) && types.contains(Roles) {
      raise ParseAndExplicit(typ=Roles)
    }
    if users is Some(_) && types.contains(Users) {
      raise ParseAndExplicit(typ=Users)
    }
  }
  {
    parse: parse.map(types => types.copy()),
    roles: roles.map(ids => ids.copy()),
    users: users.map(ids => ids.copy()),
    replied_user,
  }
}

///|
/// Suppress every mention parsed from message content.
pub fn AllowedMentions::none() -> AllowedMentions {
  { parse: Some([]), roles: None, users: None, replied_user: None, }
}

///|
/// Allow user and role mentions while suppressing `@everyone` and `@here`.
/// This is the HTTP client's default for ordinary channel messages.
pub fn AllowedMentions::safe_default() -> AllowedMentions {
  { parse: Some([Users, Roles]), roles: None, users: None, replied_user: None, }
}

///|
pub impl ToJson for AllowedMentions with fn to_json(self) {
  let fields : Map[String, Json] = Map([])
  if self.parse is Some(parse) {
    fields["parse"] = parse.to_json()
  }
  if self.roles is Some(roles) {
    fields["roles"] = roles.to_json()
  }
  if self.users is Some(users) {
    fields["users"] = users.to_json()
  }
  if self.replied_user is Some(replied_user) {
    fields["replied_user"] = replied_user.to_json()
  }
  Json::object(fields)
}