//! UI configuration for the CLI
//!
//! Reads and manages UI settings from TOML configuration files,
//! including logo display preferences and application naming.

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;

/// UI configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UiConfig {
    /// Application display name (default: "syntaxis")
    #[serde(default = "default_run_name")]
    pub run_name: String,

    /// Show logo when CLI is called without arguments (default: true)
    #[serde(default = "default_show_logo_on_empty")]
    pub show_logo_on_empty: bool,

    /// Show additional commands/help text (default: true)
    /// When false, only displays the main usage without extra command suggestions
    #[serde(default = "default_show_more")]
    pub show_more: bool,
}

/// Default run name
fn default_run_name() -> String {
    "syntaxis".to_string()
}

/// Default show_logo_on_empty value
fn default_show_logo_on_empty() -> bool {
    true
}

/// Default show_more value
fn default_show_more() -> bool {
    true
}

impl Default for UiConfig {
    fn default() -> Self {
        Self {
            run_name: default_run_name(),
            show_logo_on_empty: default_show_logo_on_empty(),
            show_more: default_show_more(),
        }
    }
}

impl UiConfig {
    /// Load UI configuration from a TOML file
    ///
    /// # Arguments
    /// * `path` - Path to the TOML configuration file
    ///
    /// # Returns
    /// * `UiConfig` with loaded values or defaults if not present
    pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path = path.as_ref();
        let content = std::fs::read_to_string(path)
            .with_context(|| format!("Failed to read configuration file: {}", path.display()))?;

        // Parse the entire TOML file as a generic table
        let table: toml::Table = toml::from_str(&content)
            .with_context(|| format!("Failed to parse TOML file: {}", path.display()))?;

        // Extract the [ui] section if it exists
        if let Some(ui_section) = table.get("ui").and_then(|v| v.as_table()) {
            let ui_toml =
                toml::to_string(ui_section).context("Failed to serialize [ui] section")?;
            toml::from_str(&ui_toml).context("Failed to parse [ui] section as UiConfig")
        } else {
            // No [ui] section found, use defaults
            Ok(Self::default())
        }
    }

    /// Get the application display name
    ///
    /// Used by handlers that need to display the configured application name
    /// in messages and output. Defaults to "syntaxis".
    #[allow(dead_code)]
    pub fn run_name(&self) -> &str {
        &self.run_name
    }

    /// Check if logo should be shown on empty arguments
    ///
    /// The NuShell wrapper script respects this setting to control
    /// whether the ASCII logo is displayed when the CLI is called without arguments.
    #[allow(dead_code)]
    pub fn should_show_logo_on_empty(&self) -> bool {
        self.show_logo_on_empty
    }

    /// Check if additional commands/help should be shown
    ///
    /// When false, handlers show minimal output without extra command suggestions.
    /// When true (default), shows full help with all command variations.
    #[allow(dead_code)]
    pub fn should_show_more(&self) -> bool {
        self.show_more
    }
}

/// Get the application display name from config, or default to "syntaxis"
///
/// This is a convenience function that handlers can use to get the
/// configured application name for use in messages and output.
#[allow(dead_code)]
pub fn get_app_name() -> String {
    use tools_shared::find_config_path_warn_conflicts;

    if let Ok(Some(config_path)) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        std::env::var("SYNTAXIS_CONFIG_PATH").ok()
    })) {
        if let Ok(config) = UiConfig::load_from_file(&config_path) {
            return config.run_name;
        }
    }

    if let Some(config_path) = find_config_path_warn_conflicts("config.toml", None) {
        if let Ok(config) = UiConfig::load_from_file(config_path) {
            return config.run_name;
        }
    }

    "syntaxis".to_string()
}

/// Check if additional help/commands should be shown from config
///
/// Returns true if show_more is enabled (default), false otherwise.
/// Handlers can use this to decide whether to display extra command suggestions.
#[allow(dead_code)]
pub fn should_show_more() -> bool {
    use tools_shared::find_config_path_warn_conflicts;

    if let Ok(Some(config_path)) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        std::env::var("SYNTAXIS_CONFIG_PATH").ok()
    })) {
        if let Ok(config) = UiConfig::load_from_file(&config_path) {
            return config.show_more;
        }
    }

    if let Some(config_path) = find_config_path_warn_conflicts("config.toml", None) {
        if let Ok(config) = UiConfig::load_from_file(config_path) {
            return config.show_more;
        }
    }

    true // Default: show more
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::TempDir;

    #[test]
    fn test_default_ui_config() {
        let config = UiConfig::default();
        assert_eq!(config.run_name, "syntaxis");
        assert!(config.show_logo_on_empty);
    }

    #[test]
    fn test_load_ui_config_with_all_settings() -> Result<()> {
        let temp_dir = TempDir::new()?;
        let config_path = temp_dir.path().join("config.toml");
        fs::write(
            &config_path,
            r#"
[ui]
run_name = "vapora"
show_logo_on_empty = false
"#,
        )?;

        let config = UiConfig::load_from_file(&config_path)?;
        assert_eq!(config.run_name, "vapora");
        assert!(!config.show_logo_on_empty);

        Ok(())
    }

    #[test]
    fn test_load_ui_config_with_defaults() -> Result<()> {
        let temp_dir = TempDir::new()?;
        let config_path = temp_dir.path().join("config.toml");
        fs::write(
            &config_path,
            r#"
[ui]
run_name = "custom-app"
"#,
        )?;

        let config = UiConfig::load_from_file(&config_path)?;
        assert_eq!(config.run_name, "custom-app");
        assert!(config.show_logo_on_empty); // Should use default

        Ok(())
    }

    #[test]
    fn test_load_ui_config_missing_section() -> Result<()> {
        let temp_dir = TempDir::new()?;
        let config_path = temp_dir.path().join("config.toml");
        fs::write(
            &config_path,
            r#"
[server]
host = "localhost"
port = 3000
"#,
        )?;

        let config = UiConfig::load_from_file(&config_path)?;
        // Should use defaults when [ui] section is missing
        assert_eq!(config.run_name, "syntaxis");
        assert!(config.show_logo_on_empty);

        Ok(())
    }

    #[test]
    fn test_ui_config_getters() {
        let config = UiConfig {
            run_name: "test-app".to_string(),
            show_logo_on_empty: false,
            show_more: false,
        };

        assert_eq!(config.run_name(), "test-app");
        assert!(!config.should_show_logo_on_empty());
        assert!(!config.should_show_more());
    }

    #[test]
    fn test_get_app_name_default() {
        // When no config is found, should return default
        let app_name = get_app_name();
        assert_eq!(app_name, "syntaxis");
    }

    #[test]
    fn test_load_ui_config_with_show_more() -> Result<()> {
        let temp_dir = TempDir::new()?;
        let config_path = temp_dir.path().join("config.toml");
        fs::write(
            &config_path,
            r#"
[ui]
run_name = "myapp"
show_logo_on_empty = true
show_more = false
"#,
        )?;

        let config = UiConfig::load_from_file(&config_path)?;
        assert_eq!(config.run_name, "myapp");
        assert!(config.show_logo_on_empty);
        assert!(!config.show_more);

        Ok(())
    }

    #[test]
    fn test_show_more_default() {
        let config = UiConfig::default();
        assert!(config.show_more);
    }
}
