use nu_plugin::{EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Example, LabeledError, Signature, Spanned, SyntaxShape, Type, Value};

use crate::{SemverPlugin, custom_value::SemverCustomValue, version::Level};

use super::custom_type;

pub struct SemverBump;

impl SimplePluginCommand for SemverBump {
    type Plugin = SemverPlugin;

    fn name(&self) -> &str {
        "semver bump"
    }

    fn signature(&self) -> Signature {
        Signature::build(self.name())
                .switch(
                    "ignore-errors",
                    "If the input is not a valid SemVer version, return the original string unchanged without raising an error",
                    Some('i')
                )
                .named(
                    "build-metadata",
                    SyntaxShape::String,
                    "Additionally set the build metadata",
                    Some('b')
                )
                .required(
                    "level",
                    SyntaxShape::String,
                    "The version level to bump. Valid values are: major, minor, patch, alpha, beta, rc, or release."
                )
                .input_output_types(vec![
                    (Type::String, custom_type()),
                    (custom_type(), custom_type()),
                ])
    }

    fn description(&self) -> &str {
        "Bump the version to the next level"
    }

    fn examples(&'_ self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: r#""1.2.3-alpha.1+build" | semver bump major"#,
                description: "Bump major version",
                result: Some(SemverCustomValue::test_value("2.0.0")),
            },
            Example {
                example: r#""1.2.3-alpha.1+build" | semver bump minor"#,
                description: "Bump minor version",
                result: Some(SemverCustomValue::test_value("1.3.0")),
            },
            Example {
                example: r#""1.2.3+build" | semver bump patch"#,
                description: "Bump patch version with build metadata",
                result: Some(SemverCustomValue::test_value("1.2.4")),
            },
            Example {
                example: r#""1.2.3-alpha.1+build" | semver bump patch"#,
                description: "Bump patch version from pre-release",
                result: Some(SemverCustomValue::test_value("1.2.3+build")),
            },
            Example {
                example: r#""1.2.3-alpha.1+build" | semver bump alpha"#,
                description: "Bump current alpha pre-release to next alpha pre-release",
                result: Some(SemverCustomValue::test_value("1.2.3-alpha.2+build")),
            },
            Example {
                example: r#""1.2.3" | semver bump alpha"#,
                description: "Bump version to next alpha pre-release",
                result: Some(SemverCustomValue::test_value("1.2.4-alpha.1")),
            },
            Example {
                example: r#""1.2.3-rc.1" | semver bump release"#,
                description: "Release the current pre-release version",
                result: Some(SemverCustomValue::test_value("1.2.3")),
            },
        ]
    }

    fn run(
        &self,
        _plugin: &Self::Plugin,
        _engine: &nu_plugin::EngineInterface,
        call: &EvaluatedCall,
        input: &Value,
    ) -> Result<Value, LabeledError> {
        let ignore_errors = call.has_flag("ignore-errors")?;
        let span = call.head;

        let res = {
            let mut version: SemverCustomValue = input.try_into()?;
            let level: Spanned<String> = call.req(0)?;
            let level = level.item.parse::<Level>().map_err(|e| {
                LabeledError::new("Valid levels are: major, minor, patch, alpha, beta, rc")
                    .with_label(e.to_string(), level.span)
            })?;
            let meta: Option<String> = call.get_flag("build-metadata")?;

            version
                .bump(level, meta)
                .map_err(|e| e.into_labeled_error(span))?;

            Ok(version)
        };

        match res {
            Ok(v) => Ok(v.into_value(span)),
            Err(e) => {
                if ignore_errors {
                    Ok(input.clone())
                } else {
                    Err(e)
                }
            }
        }
    }
}
