//! CI/CD variable-related data models.

use serde::{Deserialize, Serialize};

/// A CI/CD variable in a GitLab project.
///
/// Variables are used to store configuration and secrets
/// that can be used in CI/CD pipelines.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Variable {
    /// Variable name/key (alphanumeric and underscore only)
    pub key: String,

    /// Variable value
    pub value: String,

    /// Type of variable (env_var or file)
    pub variable_type: VariableType,

    /// Whether the variable is protected (only available in protected branches/tags)
    pub protected: bool,

    /// Whether the variable value should be masked in job logs
    pub masked: bool,

    /// Whether the variable value should be hidden in the UI
    #[serde(default)]
    pub hidden: bool,

    /// Whether to disable variable expansion
    #[serde(default = "default_raw")]
    pub raw: bool,

    /// Environment scope for the variable (e.g., "production", "*" for all)
    #[serde(default = "default_environment_scope")]
    pub environment_scope: String,

    /// Optional description of the variable
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
}

fn default_raw() -> bool {
    true
}

fn default_environment_scope() -> String {
    "*".to_string()
}

/// Type of CI/CD variable.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum VariableType {
    /// Environment variable (default)
    #[default]
    EnvVar,
    /// File variable (value written to temp file, path in env var)
    File,
}

/// Options for creating or updating a CI/CD variable.
#[derive(Debug, Clone, Default)]
pub struct VariableOptions {
    /// Whether the variable is protected
    pub protected: bool,

    /// Whether to mask the value in logs
    pub masked: bool,

    /// Whether to disable variable expansion
    pub raw: bool,

    /// Type of variable
    pub variable_type: VariableType,

    /// Environment scope
    pub environment_scope: Option<String>,

    /// Variable description
    pub description: Option<String>,
}

impl VariableOptions {
    /// Create a new VariableOptions with default values.
    pub fn new() -> Self {
        Self {
            protected: false,
            masked: false,
            raw: true,
            variable_type: VariableType::EnvVar,
            environment_scope: None,
            description: None,
        }
    }

    /// Set the variable as protected.
    pub fn protected(mut self, protected: bool) -> Self {
        self.protected = protected;
        self
    }

    /// Set whether to mask the value.
    pub fn masked(mut self, masked: bool) -> Self {
        self.masked = masked;
        self
    }

    /// Set whether to disable variable expansion.
    pub fn raw(mut self, raw: bool) -> Self {
        self.raw = raw;
        self
    }

    /// Set the variable type.
    pub fn variable_type(mut self, variable_type: VariableType) -> Self {
        self.variable_type = variable_type;
        self
    }

    /// Set the environment scope.
    pub fn environment_scope(mut self, scope: impl Into<String>) -> Self {
        self.environment_scope = Some(scope.into());
        self
    }

    /// Set the description.
    pub fn description(mut self, description: impl Into<String>) -> Self {
        self.description = Some(description.into());
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_variable_type_default() {
        assert_eq!(VariableType::default(), VariableType::EnvVar);
    }

    #[test]
    fn test_variable_options_builder() {
        let opts = VariableOptions::new()
            .protected(true)
            .masked(true)
            .variable_type(VariableType::File)
            .description("Test variable");

        assert!(opts.protected);
        assert!(opts.masked);
        assert_eq!(opts.variable_type, VariableType::File);
        assert_eq!(opts.description, Some("Test variable".to_string()));
    }
}
