//! Wire text values

use std::fmt;
use std::str::FromStr;

use efdn_types::TypeError;
use serde::{Deserialize, Serialize};

#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    Hash,
    PartialOrd,
    Ord,
    Serialize,
    Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct ErrorPath(String);

#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    Hash,
    PartialOrd,
    Ord,
    Serialize,
    Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct ErrorMessage(String);

#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    Hash,
    PartialOrd,
    Ord,
    Serialize,
    Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct ErrorTag(String);

#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    Hash,
    PartialOrd,
    Ord,
    Serialize,
    Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct ErrorHelp(String);

#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    Hash,
    PartialOrd,
    Ord,
    Serialize,
    Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct MetadataKey(String);

#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    Hash,
    PartialOrd,
    Ord,
    Serialize,
    Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct MetadataValue(String);

impl ErrorPath {
    pub fn new(value: impl Into<String>) -> Result<Self, TypeError> {
        checked_text("ErrorPath", value, "provide a dotted field path").map(Self)
    }

    pub fn from_static(value: &'static str) -> Self {
        static_text(Self::new(value))
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    pub fn into_string(self) -> String {
        self.0
    }
}

impl ErrorMessage {
    pub fn new(value: impl Into<String>) -> Result<Self, TypeError> {
        checked_text("ErrorMessage", value, "provide a human-readable message").map(Self)
    }

    pub fn from_static(value: &'static str) -> Self {
        static_text(Self::new(value))
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    pub fn into_string(self) -> String {
        self.0
    }
}

impl ErrorTag {
    pub fn new(value: impl Into<String>) -> Result<Self, TypeError> {
        checked_text("ErrorTag", value, "provide a stable namespaced error tag").map(Self)
    }

    pub fn from_static(value: &'static str) -> Self {
        static_text(Self::new(value))
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    pub fn into_string(self) -> String {
        self.0
    }
}

impl ErrorHelp {
    pub fn new(value: impl Into<String>) -> Result<Self, TypeError> {
        checked_text("ErrorHelp", value, "provide caller-safe recovery help").map(Self)
    }

    pub fn from_static(value: &'static str) -> Self {
        static_text(Self::new(value))
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    pub fn into_string(self) -> String {
        self.0
    }
}

impl MetadataKey {
    pub fn new(value: impl Into<String>) -> Result<Self, TypeError> {
        checked_text("MetadataKey", value, "provide a metadata key").map(Self)
    }

    pub fn from_static(value: &'static str) -> Self {
        static_text(Self::new(value))
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    pub fn into_string(self) -> String {
        self.0
    }
}

impl MetadataValue {
    pub fn new(value: impl Into<String>) -> Result<Self, TypeError> {
        checked_text("MetadataValue", value, "provide a metadata value").map(Self)
    }

    pub fn from_static(value: &'static str) -> Self {
        static_text(Self::new(value))
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    pub fn into_string(self) -> String {
        self.0
    }
}

impl fmt::Display for ErrorPath {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl fmt::Display for ErrorMessage {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl fmt::Display for ErrorTag {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl fmt::Display for ErrorHelp {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl fmt::Display for MetadataKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl fmt::Display for MetadataValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl FromStr for ErrorPath {
    type Err = TypeError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Self::new(value)
    }
}

impl FromStr for ErrorMessage {
    type Err = TypeError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Self::new(value)
    }
}

impl FromStr for ErrorTag {
    type Err = TypeError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Self::new(value)
    }
}

impl FromStr for ErrorHelp {
    type Err = TypeError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Self::new(value)
    }
}

impl FromStr for MetadataKey {
    type Err = TypeError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Self::new(value)
    }
}

impl FromStr for MetadataValue {
    type Err = TypeError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Self::new(value)
    }
}

fn checked_text(
    type_name: &'static str,
    value: impl Into<String>,
    suggestion: &'static str,
) -> Result<String, TypeError> {
    let value = value.into();
    if value.is_empty() {
        return Err(TypeError::empty(type_name));
    }
    if value.chars().any(char::is_control) {
        return Err(TypeError::new(
            type_name,
            "value contains a control character",
            suggestion,
        ));
    }
    Ok(value)
}

fn static_text<T>(value: Result<T, TypeError>) -> T {
    match value {
        Ok(value) => value,
        Err(error) => panic!("{error}"),
    }
}
