// SPDX-FileCopyrightText: 2023 - 2024 Markus Haug (Korrat)
//
// SPDX-License-Identifier: EUPL-1.2

use core::borrow::Borrow;
use core::cmp::Ordering;
use core::fmt;
use core::fmt::Display;
use core::fmt::Formatter;
use core::ops::Deref;
use core::str::FromStr;

use arrayvec::ArrayString;
use miette::Diagnostic;
use serde::Deserialize;
use snafu::ensure;
use snafu::Backtrace;
use snafu::Snafu;

#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(try_from = "String")]
pub struct Commodity {
    pub(crate) name: ArrayString<24>,
}

impl AsRef<str> for Commodity {
    fn as_ref(&self) -> &str {
        self
    }
}

impl Borrow<str> for Commodity {
    fn borrow(&self) -> &str {
        self
    }
}

impl Deref for Commodity {
    type Target = str;

    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.name
    }
}

impl Display for Commodity {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.pad(&self.name)
    }
}

impl From<&Self> for Commodity {
    fn from(value: &Self) -> Self {
        *value
    }
}

impl FromStr for Commodity {
    type Err = <Self as TryFrom<&'static str>>::Error;

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

impl PartialEq<Commodity> for str {
    fn eq(&self, other: &Commodity) -> bool {
        other.eq(self)
    }
}

impl PartialEq<str> for Commodity {
    fn eq(&self, other: &str) -> bool {
        self.partial_cmp(other).is_some_and(Ordering::is_eq)
    }
}

impl PartialOrd<Commodity> for str {
    fn partial_cmp(&self, other: &Commodity) -> Option<Ordering> {
        other.partial_cmp(self)
    }
}

impl PartialOrd<str> for Commodity {
    fn partial_cmp(&self, other: &str) -> Option<Ordering> {
        self.name.partial_cmp(other)
    }
}

impl TryFrom<&str> for Commodity {
    type Error = CommodityError;

    fn try_from(name: &str) -> Result<Self, Self::Error> {
        ensure!(
            lazy_regex::regex_is_match!("^[A-Z](?:[-A-Z0-9._]{0,22}[A-Z0-9])?$", name),
            CommoditySnafu { name }
        );

        let name = name.try_into().expect("length should fit");
        Ok(Self { name })
    }
}

impl TryFrom<String> for Commodity {
    type Error = CommodityError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        Self::try_from(&*value)
    }
}

#[derive(Debug, Diagnostic, Snafu)]
#[snafu(display("invalid commodity: {name:?}"))]
pub struct CommodityError {
    pub(crate) name: String,

    pub(crate) backtrace: Backtrace,
}