use std::{num::ParseIntError, ops::Deref};

use nu_protocol::{FromValue, LabeledError, ShellError, Span, Value};

pub const ALPHA: &str = "alpha";
pub const BETA: &str = "beta";
pub const RC: &str = "rc";

#[derive(Debug, thiserror::Error)]
pub enum VersionError {
    #[error("Invalid level {0} for pre-release {1}")]
    InvalidLevelForPrerelease(Level, String),
    #[error("Only numerical suffixes are supported in pre-release field: {0}")]
    InvalidNumericSuffixInPrerelease(#[from] ParseIntError),
    #[error(transparent)]
    Semver(#[from] semver::Error),
}

impl VersionError {
    pub fn into_labeled_error(self, span: Span) -> LabeledError {
        LabeledError::new("Semver error").with_label(self.to_string(), span)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, strum::EnumString)]
#[strum(serialize_all = "lowercase")]
pub enum Level {
    Major,
    Minor,
    Patch,
    Alpha,
    Beta,
    Rc,
    Release,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VersionValue {
    version: semver::Version,
    span: Span,
}

impl VersionValue {
    pub fn into_value(self) -> Value {
        Value::string(self.version.to_string(), self.span)
    }
}

impl<'a> TryFrom<&'a Value> for VersionValue {
    type Error = ShellError;

    fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
        let span = value.span();
        let item = value.as_str()?;

        let version = semver::Version::parse(item).map_err(|e| ShellError::IncorrectValue {
            msg: format!("Value is not a valid semver version: {e}"),
            val_span: span,
            call_span: span,
        })?;
        Ok(Self { version, span })
    }
}

impl FromValue for VersionValue {
    fn from_value(v: Value) -> Result<Self, ShellError> {
        (&v).try_into()
    }
}

impl Deref for VersionValue {
    type Target = semver::Version;

    fn deref(&self) -> &Self::Target {
        &self.version
    }
}

pub struct VersionReqValue {
    req: semver::VersionReq,
    #[allow(dead_code)]
    span: Span,
}

impl VersionReqValue {
    #[allow(dead_code)]
    pub fn into_inner(self) -> semver::VersionReq {
        self.req
    }

    #[allow(dead_code)]
    pub fn into_value(self) -> Value {
        Value::string(self.req.to_string(), self.span)
    }
}

impl FromValue for VersionReqValue {
    fn from_value(v: Value) -> Result<Self, ShellError> {
        let span = v.span();
        let item = v.as_str()?;

        let req = semver::VersionReq::parse(item).map_err(|e| ShellError::IncorrectValue {
            msg: format!("Value is not a valid semver requirement: {e}"),
            val_span: span,
            call_span: span,
        })?;
        Ok(Self { req, span })
    }
}

impl Deref for VersionReqValue {
    type Target = semver::VersionReq;

    fn deref(&self) -> &Self::Target {
        &self.req
    }
}
