use std::borrow::Cow;

use schemars::{json_schema, JsonSchema, Schema, SchemaGenerator};
use serde::de::{DeserializeOwned, Error};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;

#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum SingleOrMultiple<T> {
    Single(T),
    Multiple(Vec<T>),
}

impl<T> SingleOrMultiple<T> {
    pub fn values(&self) -> &[T] {
        match self {
            SingleOrMultiple::Single(value) => std::slice::from_ref(value),
            SingleOrMultiple::Multiple(values) => values,
        }
    }
}

impl<'de, T: DeserializeOwned> Deserialize<'de> for SingleOrMultiple<T> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value = Value::deserialize(deserializer)?;

        if let Value::Array(_) = &value {
            match serde_json::from_value::<Vec<T>>(value) {
                Ok(multiple) => return Ok(SingleOrMultiple::Multiple(multiple)),
                Err(multiple_err) => {
                    return Err(D::Error::custom(format!(
                        "expected array of items: {}",
                        multiple_err
                    )));
                }
            }
        }

        match serde_json::from_value::<T>(value) {
            Ok(single) => Ok(SingleOrMultiple::Single(single)),
            Err(single_err) => Err(D::Error::custom(format!(
                "expected single value or array, but parsing both failed with error: {}",
                single_err
            ))),
        }
    }
}

impl<T> From<SingleOrMultiple<T>> for Vec<T> {
    fn from(val: SingleOrMultiple<T>) -> Self {
        match val {
            SingleOrMultiple::Single(item) => vec![item],
            SingleOrMultiple::Multiple(items) => items,
        }
    }
}

impl<T: JsonSchema> JsonSchema for SingleOrMultiple<T> {
    fn schema_name() -> Cow<'static, str> {
        format!("SingleOrMultiple<{}>", T::schema_name()).into()
    }

    fn json_schema(gen: &mut SchemaGenerator) -> Schema {
        let schema = json_schema!({
            "anyOf": [
                gen.subschema_for::<T>(),
                gen.subschema_for::<Vec<T>>()
            ]
        });
        schema
    }
}
