//! Types for the `m.poll.response` event.

use std::{ops::Deref, vec};

use ruma_common::OwnedEventId;
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};

use super::{start::PollContentBlock, validate_selections, PollResponseData};
use crate::relation::Reference;

/// The payload for a poll response event.
///
/// This is the event content that should be sent for room versions that support extensible events.
/// As of Matrix 1.7, none of the stable room versions (1 through 10) support extensible events.
///
/// To send a poll response event for a room version that does not support extensible events, use
/// [`UnstablePollResponseEventContent`].
///
/// [`UnstablePollResponseEventContent`]: super::unstable_response::UnstablePollResponseEventContent
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#[ruma_event(type = "m.poll.response", kind = MessageLike)]
pub struct PollResponseEventContent {
    /// The user's selection.
    #[serde(rename = "m.selections")]
    pub selections: SelectionsContentBlock,

    /// Whether this message is automated.
    #[cfg(feature = "unstable-msc3955")]
    #[serde(
        default,
        skip_serializing_if = "ruma_common::serde::is_default",
        rename = "org.matrix.msc1767.automated"
    )]
    pub automated: bool,

    /// Information about the poll start event this responds to.
    #[serde(rename = "m.relates_to")]
    pub relates_to: Reference,
}

impl PollResponseEventContent {
    /// Creates a new `PollResponseEventContent` that responds to the given poll start event ID,
    /// with the given poll response content.
    pub fn new(selections: SelectionsContentBlock, poll_start_id: OwnedEventId) -> Self {
        Self {
            selections,
            #[cfg(feature = "unstable-msc3955")]
            automated: false,
            relates_to: Reference::new(poll_start_id),
        }
    }
}

impl OriginalSyncPollResponseEvent {
    /// Get the data from this response necessary to compile poll results.
    pub fn data(&self) -> PollResponseData<'_> {
        PollResponseData {
            sender: &self.sender,
            origin_server_ts: self.origin_server_ts,
            selections: &self.content.selections,
        }
    }
}

impl OriginalPollResponseEvent {
    /// Get the data from this response necessary to compile poll results.
    pub fn data(&self) -> PollResponseData<'_> {
        PollResponseData {
            sender: &self.sender,
            origin_server_ts: self.origin_server_ts,
            selections: &self.content.selections,
        }
    }
}

/// A block for selections content.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct SelectionsContentBlock(Vec<String>);

impl SelectionsContentBlock {
    /// Whether this `SelectionsContentBlock` is empty.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Validate these selections against the given `PollContentBlock`.
    ///
    /// Returns the list of valid selections in this `SelectionsContentBlock`, or `None` if there is
    /// no valid selection.
    pub fn validate<'a>(
        &'a self,
        poll: &PollContentBlock,
    ) -> Option<impl Iterator<Item = &'a str>> {
        let answer_ids = poll.answers.iter().map(|a| a.id.as_str()).collect();
        validate_selections(&answer_ids, poll.max_selections, &self.0)
    }
}

impl From<Vec<String>> for SelectionsContentBlock {
    fn from(value: Vec<String>) -> Self {
        Self(value)
    }
}

impl IntoIterator for SelectionsContentBlock {
    type Item = String;
    type IntoIter = vec::IntoIter<String>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

impl FromIterator<String> for SelectionsContentBlock {
    fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
        Self(Vec::from_iter(iter))
    }
}

impl Deref for SelectionsContentBlock {
    type Target = [String];

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
