//! Redirect support

use serde::de::Error;
use serde::{Deserialize, Serialize};
use trillium::Status;

/// Type of redirect.
#[derive(Debug, Clone, Copy)]
pub enum RedirType {
    Permanent,
    Temporary,
    SeeOther,
    Generic(Status),
}

#[derive(Deserialize, Debug, Serialize)]
#[serde(untagged)]
enum RedirSpec {
    Numeric(u16),
    String(RedirName),
}

#[derive(Deserialize, Debug, Serialize)]
#[serde(rename_all = "kebab-case")]
enum RedirName {
    Permanent,
    Temporary,
    SeeOther,
}

impl Default for RedirType {
    fn default() -> Self {
        RedirType::Temporary
    }
}

impl RedirType {
    /// Get the HTTP status for this redirect type.
    pub fn status(&self) -> Status {
        match self {
            Self::Permanent => Status::MovedPermanently,
            Self::Temporary => Status::Found,
            Self::SeeOther => Status::SeeOther,
            Self::Generic(s) => *s,
        }
    }
}

impl<'de> Deserialize<'de> for RedirType {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let spec = RedirSpec::deserialize(deserializer)?;
        match spec {
            RedirSpec::Numeric(301) | RedirSpec::String(RedirName::Permanent) => {
                Ok(Self::Permanent)
            }
            RedirSpec::Numeric(302) | RedirSpec::String(RedirName::Temporary) => {
                Ok(Self::Temporary)
            }
            RedirSpec::Numeric(303) | RedirSpec::String(RedirName::SeeOther) => Ok(Self::SeeOther),
            RedirSpec::Numeric(n) => {
                Ok(Self::Generic(n.try_into().map_err(|_e| {
                    Error::custom(format!("invalid HTTP code {}", n))
                })?))
            }
        }
    }
}

impl Serialize for RedirType {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        match self {
            RedirType::Permanent => serializer.serialize_str("permanent"),
            RedirType::Temporary => serializer.serialize_str("temporary"),
            RedirType::SeeOther => serializer.serialize_str("see-other"),
            RedirType::Generic(n) => serializer.serialize_u16(*n as u16),
        }
    }
}
