//! Configuration loading utilities.

#![allow(clippy::missing_docs_in_private_items)]

use crate::error::{ConfigError, ConfigFields, Result};

/// Builder for loading configuration from multiple sources.
///
/// Provides a flexible way to load and merge configuration from
/// files and environment variables.
///
/// # Example
///
/// ```
/// use cfgmatic::LoadBuilder;
///
/// let builder = LoadBuilder::new("myapp")
///     .with_env_prefix("MYAPP")
///     .with_files(true);
/// ```
#[derive(Debug, Clone)]
pub struct LoadBuilder {
    app_name: String,
    env_prefix: Option<String>,
    use_files: bool,
    use_env: bool,
}

impl LoadBuilder {
    /// Create a new load builder.
    ///
    /// # Example
    ///
    /// ```
    /// use cfgmatic::LoadBuilder;
    ///
    /// let builder = LoadBuilder::new("myapp");
    /// ```
    pub fn new(app_name: impl Into<String>) -> Self {
        Self {
            app_name: app_name.into(),
            env_prefix: None,
            use_files: true,
            use_env: true,
        }
    }

    /// Set the environment variable prefix.
    ///
    /// # Example
    ///
    /// ```
    /// use cfgmatic::LoadBuilder;
    ///
    /// let builder = LoadBuilder::new("myapp")
    ///     .with_env_prefix("MYAPP");
    /// ```
    #[must_use]
    pub fn with_env_prefix(mut self, prefix: impl Into<String>) -> Self {
        self.env_prefix = Some(prefix.into());
        self
    }

    /// Enable or disable file-based configuration.
    ///
    /// Default is `true`.
    ///
    /// # Example
    ///
    /// ```
    /// use cfgmatic::LoadBuilder;
    ///
    /// let builder = LoadBuilder::new("myapp")
    ///     .with_files(false);
    /// ```
    #[must_use]
    pub const fn with_files(mut self, use_files: bool) -> Self {
        self.use_files = use_files;
        self
    }

    /// Enable or disable environment variable configuration.
    ///
    /// Default is `true`.
    ///
    /// # Example
    ///
    /// ```
    /// use cfgmatic::LoadBuilder;
    ///
    /// let builder = LoadBuilder::new("myapp")
    ///     .with_env(false);
    /// ```
    #[must_use]
    pub const fn with_env(mut self, use_env: bool) -> Self {
        self.use_env = use_env;
        self
    }

    /// Load configuration.
    ///
    /// First loads from files (if enabled), then applies environment
    /// variable overrides (if enabled).
    ///
    /// # Errors
    ///
    /// Returns an error if no configuration source is available
    /// or if parsing fails.
    #[cfg(feature = "files")]
    pub fn load<T>(&self) -> Result<T>
    where
        T: serde::de::DeserializeOwned + ConfigFields + Default,
    {
        let mut config: T = if self.use_files {
            self.load_from_files().unwrap_or_default()
        } else {
            T::default()
        };

        if self.use_env {
            config.load_from_env(self.env_prefix.as_deref())?;
        }

        Ok(config)
    }

    /// Load configuration from files only.
    ///
    /// # Errors
    ///
    /// Returns an error if no configuration file is found.
    #[cfg(feature = "files")]
    fn load_from_files<T>(&self) -> Result<T>
    where
        T: serde::de::DeserializeOwned,
    {
        use cfgmatic_files::load_first;

        load_first::<T>(&self.app_name)
            .map_err(ConfigError::File)?
            .ok_or_else(|| ConfigError::not_found(format!("config files for {}", self.app_name)))
    }

    /// Load configuration from environment only.
    ///
    /// # Errors
    ///
    /// Returns an error if environment variables cannot be parsed.
    pub fn load_from_env<T>(&self) -> Result<T>
    where
        T: ConfigFields + Default,
    {
        let mut config = T::default();
        config.load_from_env(self.env_prefix.as_deref())?;
        Ok(config)
    }
}

/// Load configuration using the builder pattern.
///
/// This is a convenience function that creates a `LoadBuilder`.
///
/// # Example
///
/// ```
/// use cfgmatic::load_with;
///
/// // let config = load_with("myapp")
/// //     .with_env_prefix("MYAPP")
/// //     .load::<MyConfig>();
/// ```
pub fn load_with(app_name: impl Into<String>) -> LoadBuilder {
    LoadBuilder::new(app_name)
}
