use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::{de, ser, Deserialize, Serialize};
use std::borrow::Cow;

#[derive(Clone, PartialEq, Eq, Debug, Copy, Default)]
pub enum Maybe<T> {
  #[default]
  Absent,
  Present(T),
}

impl<T> Maybe<T> {
  pub fn is_present(&self) -> bool {
    matches!(self, Maybe::Present(_))
  }
  pub fn is_absent(&self) -> bool {
    matches!(self, Maybe::Absent)
  }
  pub fn unwrap(self) -> T {
    match self {
      Maybe::Absent => panic!("called `Maybe::unwrap()` on an `Absent` value"),
      Maybe::Present(t) => t,
    }
  }
}

impl<T> From<Maybe<T>> for Option<T> {
  fn from(maybe: Maybe<T>) -> Self {
    match maybe {
      Maybe::Present(v) => Some(v),
      Maybe::Absent => None,
    }
  }
}

impl<T> From<Option<T>> for Maybe<T> {
  fn from(opt: Option<T>) -> Maybe<T> {
    match opt {
      Some(v) => Maybe::Present(v),
      None => Maybe::Absent,
    }
  }
}

const ERROR_STR: &str = r#"Maybe fields need to be annotated with:
  #[serde(default, skip_serializing_if = "Maybe::is_absent")]"#;

impl<T: Serialize> Serialize for Maybe<T> {
  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  where
    S: serde::Serializer,
  {
    match self {
      Maybe::Absent => Err(ser::Error::custom(ERROR_STR)),
      Maybe::Present(t) => T::serialize(t, serializer),
    }
  }
}

impl<'de, T: Deserialize<'de>> Deserialize<'de> for Maybe<T> {
  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  where
    D: serde::Deserializer<'de>,
  {
    match Option::deserialize(deserializer)? {
      Some(t) => Ok(Maybe::Present(t)),
      None => Err(de::Error::custom("Maybe field cannot be null.")),
    }
  }
}

impl<T: JsonSchema> JsonSchema for Maybe<T> {
  fn schema_name() -> String {
    format!("Maybe_{}", T::schema_name())
  }
  fn schema_id() -> Cow<'static, str> {
    Cow::Owned(format!("Maybe<{}>", T::schema_id()))
  }
  fn json_schema(gen: &mut SchemaGenerator) -> Schema {
    gen.subschema_for::<T>()
  }

  fn _schemars_private_non_optional_json_schema(gen: &mut SchemaGenerator) -> Schema {
    T::_schemars_private_non_optional_json_schema(gen)
  }

  fn _schemars_private_is_option() -> bool {
    true
  }
}

#[cfg(test)]
mod test {
  use super::*;
  use crate::from_str;

  #[derive(Serialize, Deserialize, Debug)]
  struct Correct {
    #[serde(default, skip_serializing_if = "Maybe::is_absent")]
    a: Maybe<i32>,
  }
  #[derive(Serialize, Deserialize, Debug)]
  struct Wrong {
    #[serde(skip_serializing_if = "Maybe::is_absent")]
    a: Maybe<i32>,
  }

  #[test]
  fn test_de_correct_ok() {
    let correct: Correct = from_str("a: 123").expect("should ok");
    assert!(matches!(correct.a, Maybe::Present(123)));
    let correct: Correct = from_str("").expect("should ok");
    assert!(matches!(correct.a, Maybe::Absent));
  }
  #[test]
  fn test_de_correct_err() {
    let ret: Result<Correct, _> = from_str("a:");
    assert!(ret.is_err());
    let err = ret.unwrap_err().to_string();
    assert!(err.contains("cannot be null"));
  }
  #[test]
  fn test_de_wrong_err() {
    let wrong: Wrong = from_str("a: 123").expect("should ok");
    assert!(matches!(wrong.a, Maybe::Present(123)));
    let wrong: Result<Wrong, _> = from_str("a:");
    assert!(wrong.is_err());
    let wrong: Result<Wrong, _> = from_str("");
    assert!(wrong.is_err());
  }

  #[test]
  #[should_panic]
  fn test_unwrap_absent() {
    let nothing: Maybe<()> = Maybe::Absent;
    nothing.unwrap();
  }

  #[test]
  fn test_from_optio() {
    let mut maybe = Maybe::from(None);
    assert!(maybe.is_absent());
    maybe = Maybe::from(Some(123));
    assert!(maybe.is_present());
  }
}
