#[cfg(feature = "chrono")]
use chrono::{serde::ts_seconds, DateTime, Utc};
use serde::{Deserialize, Deserializer};

use crate::types::{ChapterId, GroupId, Language, MangaId, UserId};

/// Partial information about a chapter.
///
/// This struct should not be used directly.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ChapterPartial {
    id: ChapterId,
    hash: String,
    manga_id: MangaId,
    #[serde(deserialize_with = "parse_manga_title")]
    manga_title: String,
    volume: String,
    /// Chapter number.
    ///
    /// This can be an "integer" or "float" with the following example values respectively
    /// "3" and "3.5".
    chapter: String,
    /// Chapter title.
    title: String,
    language: Language,
    /// Numerical user ID of the user that uploaded the chapter.
    uploader: UserId,
    #[cfg(feature = "chrono")]
    #[serde(with = "ts_seconds")]
    timestamp: DateTime<Utc>,
    #[cfg(not(feature = "chrono"))]
    timestamp: u64,
    /// The number of comments posted to this chapter.
    comments: u64,
    /// The number of views this chapter has received.
    views: u64,
}

impl ChapterPartial {
    pub fn id(&self) -> &ChapterId {
        &self.id
    }

    pub fn hash(&self) -> &String {
        &self.hash
    }

    pub fn manga_id(&self) -> &MangaId {
        &self.manga_id
    }

    pub fn manga_title(&self) -> &String {
        &self.manga_title
    }

    pub fn volume(&self) -> &String {
        &self.volume
    }

    pub fn chapter(&self) -> &String {
        &self.chapter
    }

    pub fn title(&self) -> &String {
        &self.title
    }

    pub fn language(&self) -> &Language {
        &self.language
    }

    pub fn uploader(&self) -> &UserId {
        &self.uploader
    }

    #[cfg(feature = "chrono")]
    pub fn timestamp(&self) -> &DateTime<Utc> {
        &self.timestamp
    }

    #[cfg(not(feature = "chrono"))]
    pub fn timestamp(&self) -> &u64 {
        &self.timestamp
    }

    pub fn comments(&self) -> &u64 {
        &self.comments
    }

    pub fn views(&self) -> &u64 {
        &self.views
    }
}

#[derive(Debug, Clone, Deserialize, PartialOrd, PartialEq)]
pub struct PartialChapterData {
    #[serde(flatten)]
    chapter: ChapterPartial,
    groups: Vec<GroupId>,
}

impl PartialChapterData {
    pub fn groups(&self) -> &Vec<GroupId> {
        &self.groups
    }
}

/// Custom deserializer for the `manga_title` field.
///
/// This returns the string if it exists, otherwise initializes an empty string.
///
/// There is a chance for the manga title to be `None`, so a custom deserializer is needed.
fn parse_manga_title<'de, D>(d: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    Deserialize::deserialize(d)
        .map(|manga_title: Option<_>| manga_title.unwrap_or_else(|| String::from("")))
}
