pub mod drive;
pub mod emoji;
pub mod instance;
pub mod note;
pub mod notification;
pub mod streaming;
pub mod timeline;
pub mod user;

use crate::types::notification::NotificationType;
use crate::util_types::U64Range;
use chrono::{DateTime, Utc};
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};
use ts_rs::TS;

pub(crate) mod packed_time {
    use chrono::{DateTime, NaiveDateTime, Utc};
    use serde::de::Error;
    use serde::{Deserialize, Deserializer, Serializer};

    pub(crate) fn serialize<S>(value: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&value.timestamp_millis().to_string())
    }

    pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        Ok(DateTime::<Utc>::from_naive_utc_and_offset(
            NaiveDateTime::from_timestamp_millis(
                String::deserialize(deserializer)?
                    .parse::<i64>()
                    .map_err(Error::custom)?,
            )
            .ok_or_else(|| Error::custom("millisecond value out of range"))?,
            Utc,
        ))
    }
}

#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct RangeFilter {
    #[serde(with = "packed_time")]
    pub time_start: DateTime<Utc>,
    #[serde(with = "packed_time")]
    pub time_end: DateTime<Utc>,
    pub id_start: String,
    pub id_end: String,
}

#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct StartFilter {
    #[serde(with = "packed_time")]
    pub time_start: DateTime<Utc>,
    pub id_start: String,
}

#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct EndFilter {
    #[serde(with = "packed_time")]
    pub time_end: DateTime<Utc>,
    pub id_end: String,
}

#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NoFilter {}

#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(untagged)]
pub enum SpanFilter {
    Range(RangeFilter),
    Start(StartFilter),
    End(EndFilter),
    None(NoFilter),
}

impl SpanFilter {
    pub fn is_desc(&self) -> bool {
        !matches!(self, Self::Start(_))
    }

    pub fn start(&self) -> Option<(DateTime<Utc>, String)> {
        match self {
            Self::Start(StartFilter {
                time_start,
                id_start,
            })
            | Self::Range(RangeFilter {
                time_start,
                id_start,
                ..
            }) => Some((*time_start, id_start.clone())),
            _ => None,
        }
    }

    pub fn end(&self) -> Option<(DateTime<Utc>, String)> {
        match self {
            Self::End(EndFilter { time_end, id_end })
            | Self::Range(RangeFilter {
                time_end, id_end, ..
            }) => Some((*time_end, id_end.clone())),
            _ => None,
        }
    }

    pub fn prev(&self, first_date: DateTime<Utc>, first_id: &str) -> Option<SpanFilter> {
        match self {
            Self::Start(_) => Some(SpanFilter::End(EndFilter {
                time_end: first_date,
                id_end: first_id.to_string(),
            })),
            Self::End(_) => Some(SpanFilter::Start(StartFilter {
                time_start: first_date,
                id_start: first_id.to_string(),
            })),
            // TODO: This is jank
            // It forgets the bounds of the cursor
            Self::Range(_) => Some(SpanFilter::Start(StartFilter {
                time_start: first_date,
                id_start: first_id.to_string(),
            })),
            Self::None(_) => None,
        }
    }

    pub fn next(&self, last_date: DateTime<Utc>, last_id: &str) -> Option<SpanFilter> {
        match self {
            Self::Start(_) => Some(SpanFilter::Start(StartFilter {
                time_start: last_date,
                id_start: last_id.to_string(),
            })),
            Self::End(_) | Self::None(_) => Some(SpanFilter::End(EndFilter {
                time_end: last_date,
                id_end: last_id.to_string(),
            })),
            Self::Range(RangeFilter { time_start, .. }) if *time_start > last_date => None,
            Self::Range(RangeFilter {
                time_start,
                id_start,
                ..
            }) => Some(SpanFilter::Range(RangeFilter {
                time_start: *time_start,
                id_start: id_start.clone(),
                time_end: last_date,
                id_end: last_id.to_string(),
            })),
        }
    }
}

#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct PaginationShape {
    #[serde(default = "pagination_default")]
    #[serde(deserialize_with = "deserialize_pagination")]
    pub pagination: SpanFilter,
    #[serde(default = "limit_defalt")]
    #[serde(deserialize_with = "deserialize_limit")]
    pub limit: U64Range<10, 100>,
}

fn pagination_default() -> SpanFilter {
    SpanFilter::None(NoFilter {})
}

fn limit_defalt() -> U64Range<10, 100> {
    U64Range::try_from(20).unwrap()
}

fn deserialize_limit<'de, D>(deserializer: D) -> Result<U64Range<10, 100>, D::Error>
where
    D: Deserializer<'de>,
{
    let str_val = String::deserialize(deserializer)?;

    U64Range::try_from(str_val.parse::<u64>().map_err(Error::custom)?)
        .map_err(|_| Error::custom("number out of range"))
}

fn deserialize_pagination<'de, D>(deserializer: D) -> Result<SpanFilter, D::Error>
where
    D: Deserializer<'de>,
{
    let str_val = String::deserialize(deserializer)?;

    serde_urlencoded::from_str::<SpanFilter>(&str_val).map_err(Error::custom)
}

#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[repr(transparent)]
pub struct Id {
    pub id: String,
}

impl<T: AsRef<str>> From<T> for Id {
    fn from(id: T) -> Self {
        Self {
            id: id.as_ref().to_string(),
        }
    }
}

#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[repr(transparent)]
pub struct MmXml(pub String);

#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NotificationSettings {
    pub enabled: Vec<NotificationType>,
}

#[derive(Copy, Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub enum FollowVisibility {
    Public,
    Followers,
    Private,
}
