///|
/// A poll attached to a Discord message.
pub(all) struct Poll {
  question : PollMedia
  answers : Array[PollAnswer]
  expiry : Nullable[Timestamp]
  allow_multiselect : Bool
  layout_type : PollLayoutType
  results : PollResults?
} derive(ToJson, FromJson, Debug)

///|
/// Text and emoji content used by a poll question or answer.
pub(all) struct PollMedia {
  text : String?
  emoji : Emoji?
} derive(ToJson, FromJson, Debug)

///|
/// A selectable answer in a poll.
pub(all) struct PollAnswer {
  answer_id : Int
  poll_media : PollMedia
} derive(ToJson, FromJson, Debug)

///|
/// Aggregated results for a poll.
pub(all) struct PollResults {
  is_finalized : Bool
  answer_counts : Array[PollAnswerCount]
} derive(ToJson, FromJson, Debug)

///|
/// Vote count and current-user state for one poll answer.
pub(all) struct PollAnswerCount {
  id : Int
  count : Int
  me_voted : Bool
} derive(ToJson, FromJson, Debug)

///|
/// Users returned by the poll answer-voters endpoint.
pub(all) struct PollAnswerVoters {
  users : Array[User]
} derive(ToJson, FromJson, Debug)

///|
/// Poll definition attached when creating a message or executing a webhook.
/// Answers carry their media directly; Discord assigns the answer ids.
/// `duration` is the number of hours the poll stays open (up to 32 days,
/// default 24).
pub(all) struct PollCreateRequest {
  question : PollMedia
  answers : Array[PollMedia]
  duration : Int?
  allow_multiselect : Bool?
  layout_type : PollLayoutType?
} derive(Debug)

///|
/// Request-side poll media omits absent fields instead of sending nulls.
fn poll_media_request_json(media : PollMedia) -> Json {
  ObjBuilder().opt("text", media.text).opt("emoji", media.emoji).build()
}

///|
pub impl ToJson for PollCreateRequest with fn to_json(self) {
  ObjBuilder()
  .field("question", poll_media_request_json(self.question))
  .field(
    "answers",
    self.answers.map(media => {
      ({ "poll_media": poll_media_request_json(media) } : Json)
    }),
  )
  .opt("duration", self.duration)
  .opt("allow_multiselect", self.allow_multiselect)
  .opt("layout_type", self.layout_type)
  .build()
}

///|
/// The presentation layout used by a poll.
pub(all) enum PollLayoutType {
  Default
  Unknown(Int)
} derive(Eq, Debug)

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

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

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

///|
pub impl @json.FromJson for PollLayoutType with fn from_json(json, path) {
  match json {
    Number(value, ..) => PollLayoutType::from_int(value.to_int())
    _ =>
      raise JsonDecodeError(
        (path, "expected the presentation layout used by a poll (number)"),
      )
  }
}