///|
/// A message sent to a Discord channel.
pub(all) struct Message {
  id : MessageId
  channel_id : ChannelId
  author : User
  content : String
  timestamp : Timestamp
  edited_timestamp : Nullable[Timestamp]
  tts : Bool
  mention_everyone : Bool
  mentions : Array[User]
  mention_roles : Array[RoleId]
  mention_channels : Array[ChannelMention]?
  attachments : Array[Attachment]
  embeds : Array[Embed]
  reactions : Array[Reaction]?
  nonce : Nonce?
  pinned : Bool
  webhook_id : WebhookId?
  typ : MessageType
  activity : MessageActivity?
  application_id : ApplicationId?
  message_reference : MessageReference?
  flags : MessageFlags?
  referenced_message : Nullable[Message]?
  thread : Channel?
  components : Array[Component]?
  sticker_items : Array[StickerItem]?
  position : Int?
  role_subscription_data : RoleSubscriptionData?
  poll : Poll?
  call : MessageCall?
  application : Application?
  interaction_metadata : MessageInteractionMetadata?
  // Not modeled: the legacy message interaction field is deprecated in favor of interaction_metadata.
  resolved : ResolvedData?
  message_snapshots : Array[MessageSnapshot]?
  // Not modeled: message soundboard_sounds is not in the public documentation.
  // Not modeled: full message stickers are deprecated in favor of sticker_items.
  shared_client_theme : SharedClientTheme?
} derive (
  ToJson(fields(typ(rename="type"))),
  FromJson(fields(typ(rename="type"))),
  Debug,
)

///|
/// A guild message search response. All fields are optional because Discord
/// returns either completed results or an HTTP 202 indexing response. When
/// `retry_after` is `Some`, the search index was not ready and the caller
/// should retry after that many seconds.
pub(all) struct GuildMessageSearchResults {
  doing_deep_historical_index : Bool?
  documents_indexed : Int?
  total_results : Int?
  messages : Array[Array[Message]]?
  threads : Array[Channel]?
  members : Array[ThreadMember]?
  retry_after : Int?
  code : Int?
  message : String?
} derive(ToJson, FromJson, Debug)

///|
/// Metadata identifying the interaction that produced a message.
///
/// The optional fields cover the documented command, component, and modal
/// variants. `type` determines which subset is present.
pub(all) struct MessageInteractionMetadata {
  id : InteractionId
  typ : InteractionType
  user : User
  authorizing_integration_owners : Map[String, String]
  original_response_message_id : MessageId?
  target_user : User?
  target_message_id : MessageId?
  interacted_message_id : MessageId?
  triggering_interaction_metadata : MessageInteractionMetadata?
} derive (
  ToJson(fields(typ(rename="type"))),
  FromJson(fields(typ(rename="type"))),
  Debug,
)

///|
/// A forwarded message snapshot.
pub(all) struct MessageSnapshot {
  message : MessageSnapshotContent
} derive(ToJson, FromJson, Debug)

///|
/// The documented subset of a message retained in a forwarded snapshot.
pub(all) struct MessageSnapshotContent {
  typ : MessageType?
  content : String?
  embeds : Array[Embed]?
  attachments : Array[Attachment]?
  timestamp : Timestamp?
  edited_timestamp : Nullable[Timestamp]?
  flags : MessageFlags?
  mentions : Array[User]?
  mention_roles : Array[RoleId]?
  sticker_items : Array[StickerItem]?
  components : Array[Component]?
} derive (
  ToJson(fields(typ(rename="type"))),
  FromJson(fields(typ(rename="type"))),
  Debug,
)

///|
/// Theme colors shared by an activity-card message.
pub(all) struct SharedClientTheme {
  colors : Array[String]
  gradient_angle : Int
  base_mix : Int
  base_theme : Nullable[BaseTheme]?
} derive(ToJson, FromJson, Debug)

///|
/// The base client theme a `SharedClientTheme` gradient is mixed against.
pub(all) enum BaseTheme {
  Unset
  Dark
  Light
  Darker
  Midnight
  Unknown(Int)
} derive(Eq, Debug)

///|
pub fn BaseTheme::to_int(self : BaseTheme) -> Int {
  match self {
    Unset => 0
    Dark => 1
    Light => 2
    Darker => 3
    Midnight => 4
    Unknown(value) => value
  }
}

///|
/// One entry returned by the paginated channel-pins endpoint.
pub(all) struct MessagePin {
  pinned_at : Timestamp
  message : Message
} derive(ToJson, FromJson, Debug)

///|
/// One page from `GET /channels/{channel.id}/messages/pins`.
pub(all) struct ChannelPins {
  items : Array[MessagePin]
  has_more : Bool
} derive(ToJson, FromJson, Debug)

///|
pub fn BaseTheme::from_int(value : Int) -> BaseTheme {
  match value {
    0 => Unset
    1 => Dark
    2 => Light
    3 => Darker
    4 => Midnight
    value => Unknown(value)
  }
}

///|
pub impl ToJson for BaseTheme with fn to_json(self) {
  Json::number(self.to_int().to_double())
}

///|
pub impl @json.FromJson for BaseTheme with fn from_json(json, path) {
  match json {
    Number(value, ..) => BaseTheme::from_int(value.to_int())
    _ => raise JsonDecodeError((path, "expected base theme (number)"))
  }
}

///|
/// A client nonce represented as either its string or integer wire form.
pub(all) enum Nonce {
  Str(String)
  Num(Int64)
} derive(Eq, Debug)

///|
pub impl ToJson for Nonce with fn to_json(self) {
  match self {
    Str(value) => Json::string(value)
    Num(value) => Json::number(value.to_double(), repr=value.to_string())
  }
}

///|
pub impl @json.FromJson for Nonce with fn from_json(json, path) {
  match json {
    String(value) => Str(value)
    Number(_, repr=Some(repr)) =>
      Num(
        @string.parse_int64(repr.to_string()) catch {
          _ =>
            raise JsonDecodeError(
              (path, "expected a message nonce (integer), got \{repr}"),
            )
        },
      )
    Number(value, repr=None) => Num(value.to_int64())
    _ =>
      raise JsonDecodeError(
        (path, "expected a message nonce (string or integer)"),
      )
  }
}

///|
/// A channel referenced directly in message content.
pub(all) struct ChannelMention {
  id : ChannelId
  guild_id : GuildId
  typ : ChannelType
  name : String
} derive (
  ToJson(fields(typ(rename="type"))),
  FromJson(fields(typ(rename="type"))),
  Debug,
)

///|
/// Aggregate reaction state for one emoji on a message.
pub(all) struct Reaction {
  count : Int
  count_details : ReactionCountDetails
  me : Bool
  me_burst : Bool
  emoji : Emoji
  burst_colors : Array[String]
} derive(ToJson, FromJson, Debug)

///|
/// Normal and burst reaction counts for a reaction.
pub(all) struct ReactionCountDetails {
  burst : Int
  normal : Int
} derive(ToJson, FromJson, Debug)

///|
/// Whether a reaction is a normal or a burst (super) reaction, as selected
/// by the `type` query parameter of the reaction endpoints.
pub(all) enum ReactionType {
  Normal
  Burst
  Unknown(Int)
} derive(Eq, Debug)

///|
pub fn ReactionType::to_int(self : ReactionType) -> Int {
  match self {
    Normal => 0
    Burst => 1
    Unknown(value) => value
  }
}

///|
pub fn ReactionType::from_int(value : Int) -> ReactionType {
  match value {
    0 => Normal
    1 => Burst
    value => Unknown(value)
  }
}

///|
pub impl ToJson for ReactionType with fn to_json(self) {
  Json::number(self.to_int().to_double())
}

///|
pub impl @json.FromJson for ReactionType with fn from_json(json, path) {
  match json {
    Number(value, ..) => ReactionType::from_int(value.to_int())
    _ => raise JsonDecodeError((path, "expected a reaction type (number)"))
  }
}

///|
/// Rich Presence activity metadata attached to a message.
pub(all) struct MessageActivity {
  typ : MessageActivityType
  party_id : String?
} derive (
  ToJson(fields(typ(rename="type"))),
  FromJson(fields(typ(rename="type"))),
  Debug,
)

///|
/// Origin identifiers and delivery behavior for a message reference.
pub(all) struct MessageReference {
  typ : MessageReferenceType?
  message_id : MessageId?
  channel_id : ChannelId?
  guild_id : GuildId?
  fail_if_not_exists : Bool?
} derive (
  ToJson(fields(typ(rename="type"))),
  FromJson(fields(typ(rename="type"))),
  Debug,
)

///|
/// Participant and end-time data for a call message.
pub(all) struct MessageCall {
  participants : Array[UserId]
  ended_timestamp : Nullable[Timestamp]?
} derive(ToJson, FromJson, Debug)

///|
/// Subscription tier data attached to a role purchase message.
pub(all) struct RoleSubscriptionData {
  role_subscription_listing_id : SkuId
  tier_name : String
  total_months_subscribed : Int
  is_renewal : Bool
} derive(ToJson, FromJson, Debug)

///|
/// The system or user event category represented by a message.
pub(all) enum MessageType {
  Default
  RecipientAdd
  RecipientRemove
  Call
  ChannelNameChange
  ChannelIconChange
  ChannelPinnedMessage
  UserJoin
  GuildBoost
  GuildBoostTier1
  GuildBoostTier2
  GuildBoostTier3
  ChannelFollowAdd
  GuildDiscoveryDisqualified
  GuildDiscoveryRequalified
  GuildDiscoveryGracePeriodInitialWarning
  GuildDiscoveryGracePeriodFinalWarning
  ThreadCreated
  Reply
  ChatInputCommand
  ThreadStarterMessage
  GuildInviteReminder
  ContextMenuCommand
  AutoModerationAction
  RoleSubscriptionPurchase
  InteractionPremiumUpsell
  StageStart
  StageEnd
  StageSpeaker
  StageRaiseHand
  StageTopic
  GuildApplicationPremiumSubscription
  GuildIncidentAlertModeEnabled
  GuildIncidentAlertModeDisabled
  GuildIncidentReportRaid
  GuildIncidentReportFalseAlarm
  PurchaseNotification
  PollResult
  Unknown(Int)
} derive(Eq, Debug)

///|
pub fn MessageType::to_int(self : MessageType) -> Int {
  match self {
    Default => 0
    RecipientAdd => 1
    RecipientRemove => 2
    Call => 3
    ChannelNameChange => 4
    ChannelIconChange => 5
    ChannelPinnedMessage => 6
    UserJoin => 7
    GuildBoost => 8
    GuildBoostTier1 => 9
    GuildBoostTier2 => 10
    GuildBoostTier3 => 11
    ChannelFollowAdd => 12
    GuildDiscoveryDisqualified => 14
    GuildDiscoveryRequalified => 15
    GuildDiscoveryGracePeriodInitialWarning => 16
    GuildDiscoveryGracePeriodFinalWarning => 17
    ThreadCreated => 18
    Reply => 19
    ChatInputCommand => 20
    ThreadStarterMessage => 21
    GuildInviteReminder => 22
    ContextMenuCommand => 23
    AutoModerationAction => 24
    RoleSubscriptionPurchase => 25
    InteractionPremiumUpsell => 26
    StageStart => 27
    StageEnd => 28
    StageSpeaker => 29
    StageRaiseHand => 30
    StageTopic => 31
    GuildApplicationPremiumSubscription => 32
    GuildIncidentAlertModeEnabled => 36
    GuildIncidentAlertModeDisabled => 37
    GuildIncidentReportRaid => 38
    GuildIncidentReportFalseAlarm => 39
    PurchaseNotification => 44
    PollResult => 46
    Unknown(value) => value
  }
}

///|
pub fn MessageType::from_int(value : Int) -> MessageType {
  match value {
    0 => Default
    1 => RecipientAdd
    2 => RecipientRemove
    3 => Call
    4 => ChannelNameChange
    5 => ChannelIconChange
    6 => ChannelPinnedMessage
    7 => UserJoin
    8 => GuildBoost
    9 => GuildBoostTier1
    10 => GuildBoostTier2
    11 => GuildBoostTier3
    12 => ChannelFollowAdd
    14 => GuildDiscoveryDisqualified
    15 => GuildDiscoveryRequalified
    16 => GuildDiscoveryGracePeriodInitialWarning
    17 => GuildDiscoveryGracePeriodFinalWarning
    18 => ThreadCreated
    19 => Reply
    20 => ChatInputCommand
    21 => ThreadStarterMessage
    22 => GuildInviteReminder
    23 => ContextMenuCommand
    24 => AutoModerationAction
    25 => RoleSubscriptionPurchase
    26 => InteractionPremiumUpsell
    27 => StageStart
    28 => StageEnd
    29 => StageSpeaker
    30 => StageRaiseHand
    31 => StageTopic
    32 => GuildApplicationPremiumSubscription
    36 => GuildIncidentAlertModeEnabled
    37 => GuildIncidentAlertModeDisabled
    38 => GuildIncidentReportRaid
    39 => GuildIncidentReportFalseAlarm
    44 => PurchaseNotification
    46 => PollResult
    value => Unknown(value)
  }
}

///|
pub impl ToJson for MessageType with fn to_json(self) {
  Json::number(self.to_int().to_double())
}

///|
pub impl @json.FromJson for MessageType with fn from_json(json, path) {
  match json {
    Number(value, ..) => MessageType::from_int(value.to_int())
    _ => raise JsonDecodeError((path, "expected a message type (number)"))
  }
}

///|
/// The Rich Presence action represented by a message activity.
pub(all) enum MessageActivityType {
  Join
  Spectate
  Listen
  JoinRequest
  Unknown(Int)
} derive(Eq, Debug)

///|
pub fn MessageActivityType::to_int(self : MessageActivityType) -> Int {
  match self {
    Join => 1
    Spectate => 2
    Listen => 3
    JoinRequest => 5
    Unknown(value) => value
  }
}

///|
pub fn MessageActivityType::from_int(value : Int) -> MessageActivityType {
  match value {
    1 => Join
    2 => Spectate
    3 => Listen
    5 => JoinRequest
    value => Unknown(value)
  }
}

///|
pub impl ToJson for MessageActivityType with fn to_json(self) {
  Json::number(self.to_int().to_double())
}

///|
pub impl @json.FromJson for MessageActivityType with fn from_json(json, path) {
  match json {
    Number(value, ..) => MessageActivityType::from_int(value.to_int())
    _ => raise JsonDecodeError((path, "expected a message activity (number)"))
  }
}

///|
/// The reference mode used for a reply or forwarded message.
pub(all) enum MessageReferenceType {
  Default
  Forward
  Unknown(Int)
} derive(Eq, Debug)

///|
pub fn MessageReferenceType::to_int(self : MessageReferenceType) -> Int {
  match self {
    Default => 0
    Forward => 1
    Unknown(value) => value
  }
}

///|
pub fn MessageReferenceType::from_int(value : Int) -> MessageReferenceType {
  match value {
    0 => Default
    1 => Forward
    value => Unknown(value)
  }
}

///|
pub impl ToJson for MessageReferenceType with fn to_json(self) {
  Json::number(self.to_int().to_double())
}

///|
pub impl @json.FromJson for MessageReferenceType with fn from_json(json, path) {
  match json {
    Number(value, ..) => MessageReferenceType::from_int(value.to_int())
    _ => raise JsonDecodeError((path, "expected a message reference (number)"))
  }
}

///|
/// Bit flags describing delivery and presentation behavior of a message.
pub(all) struct MessageFlags(UInt64) derive(Eq)

///|
pub fn MessageFlags::none() -> MessageFlags {
  MessageFlags(0)
}

///|
pub fn MessageFlags::from_bits(bits : UInt64) -> MessageFlags {
  MessageFlags(bits)
}

///|
pub fn MessageFlags::bits(self : MessageFlags) -> UInt64 {
  self.0
}

///|
pub fn MessageFlags::contains(
  self : MessageFlags,
  other : MessageFlags,
) -> Bool {
  (self.0 & other.0) == other.0
}

///|
pub impl BitOr for MessageFlags with fn lor(a, b) {
  MessageFlags(a.0 | b.0)
}

///|
pub impl BitAnd for MessageFlags with fn land(a, b) {
  MessageFlags(a.0 & b.0)
}

///|
pub impl Debug for MessageFlags with fn to_repr(self) {
  Repr(self.0)
}

///|
pub impl ToJson for MessageFlags with fn to_json(self) {
  Json::number(self.0.to_double(), repr=self.0.to_string())
}

///|
pub impl @json.FromJson for MessageFlags with fn from_json(json, path) {
  match json {
    Number(_, repr=Some(repr)) =>
      MessageFlags(
        @string.parse_uint64(repr.to_string()) catch {
          _ =>
            raise JsonDecodeError(
              (path, "expected message flags (number), got \{repr}"),
            )
        },
      )
    Number(value, repr=None) => MessageFlags(value.to_uint64())
    _ => raise JsonDecodeError((path, "expected message flags (number)"))
  }
}

///|
pub fn MessageFlags::crossposted() -> MessageFlags {
  MessageFlags(1UL << 0)
}

///|
pub fn MessageFlags::is_crosspost() -> MessageFlags {
  MessageFlags(1UL << 1)
}

///|
pub fn MessageFlags::suppress_embeds() -> MessageFlags {
  MessageFlags(1UL << 2)
}

///|
pub fn MessageFlags::source_message_deleted() -> MessageFlags {
  MessageFlags(1UL << 3)
}

///|
pub fn MessageFlags::urgent() -> MessageFlags {
  MessageFlags(1UL << 4)
}

///|
pub fn MessageFlags::has_thread() -> MessageFlags {
  MessageFlags(1UL << 5)
}

///|
pub fn MessageFlags::ephemeral() -> MessageFlags {
  MessageFlags(1UL << 6)
}

///|
pub fn MessageFlags::loading() -> MessageFlags {
  MessageFlags(1UL << 7)
}

///|
pub fn MessageFlags::failed_to_mention_some_roles_in_thread() -> MessageFlags {
  MessageFlags(1UL << 8)
}

///|
pub fn MessageFlags::should_show_link_not_discord_warning() -> MessageFlags {
  MessageFlags(1UL << 10)
}

///|
pub fn MessageFlags::suppress_notifications() -> MessageFlags {
  MessageFlags(1UL << 12)
}

///|
pub fn MessageFlags::is_voice_message() -> MessageFlags {
  MessageFlags(1UL << 13)
}

///|
pub fn MessageFlags::has_snapshot() -> MessageFlags {
  MessageFlags(1UL << 14)
}

///|
pub fn MessageFlags::is_components_v2() -> MessageFlags {
  MessageFlags(1UL << 15)
}