pub mod keymap;
pub mod settings;
pub mod theme;

use anyhow::Result;
use serde::Deserialize; // derive Deserialize to parse TOML into structs
use serde::Serialize;
use std::fs;            // file I/O for reading config files
use std::path::PathBuf;
use std::collections::HashSet;
use std::io::Write;

#[allow(unused_imports)]
pub use keymap::KeymapConfig; // re-export the structs for use in other modules
pub use settings::Settings; // re-export the structs for use in other modules
pub use theme::{ThemeConfig, PartialThemeConfig}; // re-export the structs for use in other modules

#[derive(Debug, Deserialize, Serialize, Default)]
struct PartialConfig {
    repo_path: Option<String>,
    theme: Option<String>,
    keymap: Option<String>,
    #[serde(default)]
    colors: Option<PartialThemeConfig>,
    pull_timeout_secs: Option<u64>,
    rebase_timeout_secs: Option<u64>,
    font_family: Option<String>,
    font_size: Option<u16>,
    editor_command: Option<String>,
    #[serde(default)]
    commit: Option<CommitConfig>,
    #[serde(default)]
    custom_commands: Option<std::collections::HashMap<String, String>>,
}

#[derive(Debug, Deserialize, Serialize, Default)]
struct CommitConfig {
    template: Option<String>,
}

/// Get config directory path (~/.config/eazygit/).
pub fn config_dir() -> Option<std::path::PathBuf> {
    dirs::config_dir().map(|p| p.join("eazygit"))
}

/// Load configuration: check ~/.config/eazygit/config.toml, then ./eazygit.toml.
pub fn load() -> Result<Settings> {
    let mut cfg = Settings::default();
    let mut loaded = false;

    // Candidate config paths, prefer config_dir() first (where persist_theme saves),
    // then XDG (~/.config), then local file.
    let mut candidates: Vec<std::path::PathBuf> = Vec::new();
    // First check config_dir() - this is where persist_theme() saves
    if let Some(config_path) = config_dir() {
        candidates.push(config_path.join("config.toml"));
    }
    // Then check XDG path
    if let Some(home) = dirs::home_dir() {
        candidates.push(home.join(".config").join("eazygit").join("config.toml"));
    }

    tracing::debug!("Config candidates: {:?}", candidates);

    for file_path in candidates {
        tracing::debug!("Checking config at: {:?}", file_path);
        if let Ok(raw) = fs::read_to_string(&file_path) {
            tracing::debug!("Read config file, length: {}", raw.len());
            match toml::from_str::<PartialConfig>(&raw) {
                Ok(partial) => {
                    tracing::debug!(
                        "Parsed config: theme={:?}, has_colors={}",
                        partial.theme,
                        partial.colors.is_some()
                    );
                    apply_partial(&mut cfg, partial);
                    loaded = true;
                    break;
                }
                Err(e) => {
                    tracing::warn!("Failed to parse config TOML: {}", e);
                    tracing::debug!("Config content: {}", raw);
                }
            }
        }
    }

    // Fall back to ./eazygit.toml in current directory
    if !loaded {
    if let Ok(raw) = fs::read_to_string("eazygit.toml") {
            if let Ok(partial) = toml::from_str::<PartialConfig>(&raw) {
                apply_partial(&mut cfg, partial);
            }
        }
    }

    Ok(cfg)
}

/// Persist the selected theme to config without clobbering other keys.
pub fn persist_theme(theme: &str) -> Result<()> {
    let Some(config_path) = config_dir() else {
        return Ok(()); // fail-closed; no directory
    };
    fs::create_dir_all(&config_path)?;
    let file_path = config_path.join("config.toml");
    let mut value = if let Ok(raw) = fs::read_to_string(&file_path) {
        toml::from_str::<toml::Value>(&raw).unwrap_or_else(|_| toml::Value::Table(Default::default()))
    } else {
        toml::Value::Table(Default::default())
    };
    if let Some(table) = value.as_table_mut() {
        table.insert("theme".to_string(), toml::Value::String(theme.to_string()));
    }
    let buf = toml::to_string(&value)?;
    let mut f = fs::File::create(&file_path)?;
    f.write_all(buf.as_bytes())?;
    Ok(())
}

fn apply_partial(cfg: &mut Settings, partial: PartialConfig) {
            if let Some(repo_path) = partial.repo_path {
                cfg.repo_path = repo_path;
            }
    if let Some(theme_name) = partial.theme.clone() {
        cfg.theme = theme_name.clone();
        cfg.theme_config = load_theme_by_name(&theme_name);
            }
            if let Some(keymap) = partial.keymap {
                cfg.keymap = keymap;
            }
    if let Some(colors) = partial.colors {
        // Merge custom colors into existing theme (don't replace entirely)
        cfg.theme_config.merge_partial(colors);
        // If user only set [colors] without theme, mark theme as custom.
        if partial.theme.is_none() {
            cfg.theme = "custom".to_string();
        }
    }
    if let Some(pull) = partial.pull_timeout_secs {
        cfg.pull_timeout_secs = pull;
    }
    if let Some(rebase) = partial.rebase_timeout_secs {
        cfg.rebase_timeout_secs = rebase;
    }
    if let Some(font_family) = partial.font_family {
        cfg.font_family = Some(font_family);
    }
    if let Some(font_size) = partial.font_size {
        cfg.font_size = Some(font_size);
    }
    if let Some(editor_command) = partial.editor_command {
        cfg.editor_command = Some(editor_command);
    }
    if let Some(commit) = partial.commit {
        if let Some(template) = commit.template {
            cfg.commit_template = Some(template);
        }
    }
    if let Some(custom_commands) = partial.custom_commands {
        cfg.custom_commands = custom_commands;
    }
}

pub fn load_theme_by_name(name: &str) -> ThemeConfig {
    let mut candidates: Vec<PathBuf> = Vec::new();
    
    // 1. Check user config directory first (~/.config/eazygit/themes/)
    if let Some(config_path) = config_dir() {
        candidates.push(config_path.join("themes").join(format!("{}.toml", name)));
    }
    
    // 2. Check relative to binary location (for installed packages)
    if let Ok(exe_path) = std::env::current_exe() {
        if let Some(exe_dir) = exe_path.parent() {
            // Check in same directory as binary
            candidates.push(exe_dir.join("themes").join(format!("{}.toml", name)));
            // Check in parent directory (for development builds)
            if let Some(parent) = exe_dir.parent() {
                candidates.push(parent.join("themes").join(format!("{}.toml", name)));
            }
        }
    }
    
    // 3. Check current working directory (for development)
    candidates.push(PathBuf::from("themes").join(format!("{}.toml", name)));

    for path in candidates {
        if path.exists() {
            if let Some(theme) = ThemeConfig::load_external(&path, name) {
                return theme;
            }
        }
    }
    ThemeConfig::load_named(name)
}

/// List available themes: built-ins plus external TOML files.
pub fn list_themes() -> Vec<String> {
    let mut set: HashSet<String> = ThemeConfig::builtins().iter().map(|s| s.to_string()).collect();

    let mut add_from_dir = |dir: &PathBuf| {
        if dir.exists() {
            if let Ok(read_dir) = std::fs::read_dir(dir) {
                for entry in read_dir.flatten() {
                    if let Some(ext) = entry.path().extension() {
                        if ext == "toml" {
                            if let Some(stem) = entry.path().file_stem().and_then(|s| s.to_str()) {
                                set.insert(stem.to_string());
                            }
                        }
                    }
                }
            }
        }
    };

    // 1. Check user config directory (~/.config/eazygit/themes/)
    if let Some(config_path) = config_dir() {
        add_from_dir(&config_path.join("themes"));
    }
    
    // 2. Check relative to binary location (for installed packages)
    if let Ok(exe_path) = std::env::current_exe() {
        if let Some(exe_dir) = exe_path.parent() {
            // Check in same directory as binary
            add_from_dir(&exe_dir.join("themes"));
            // Check in parent directory (for development builds)
            if let Some(parent) = exe_dir.parent() {
                add_from_dir(&parent.join("themes"));
            }
        }
    }
    
    // 3. Check current working directory (for development)
    add_from_dir(&PathBuf::from("themes"));

    let mut list: Vec<String> = set.into_iter().collect();
    list.sort();
    list
}

/// Create default config file if it doesn't exist.
#[allow(dead_code)]
pub fn create_default_config() -> Result<()> {
    if let Some(config_path) = config_dir() {
        fs::create_dir_all(&config_path)?;
        let file_path = config_path.join("config.toml");
        if !file_path.exists() {
            let default_config = r##"# eazygit configuration
# repo_path = "."
# theme = "nord"  # Options: dark, light, nord, dracula, gruvbox, onedark, everforest
# font_family = "JetBrainsMono Nerd Font"
# font_size = 13

# Custom colors (optional, overrides theme). Only set keys you want to change.
# [colors]
# border = "#4C566A"
# border_focused = "#88C0D0"
# selection_bg = "#3B4252"
# selection_fg = "#ECEFF4"
# diff_add = "#A3BE8C"
# diff_remove = "#BF616A"
# diff_context = "#4C566A"
# diff_hunk = "#81A1C1"
# staged = "#A3BE8C"
# unstaged = "#EBCB8B"
# untracked = "#4C566A"
# header = "#5E81AC"
# footer = "#4C566A"
# error = "#BF616A"
# warning = "#EBCB8B"
# success = "#A3BE8C"
"##;
            fs::write(file_path, default_config)?;
        }
    }
    Ok(())
}

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

    #[test]
    fn apply_partial_sets_font_fields() {
        let mut cfg = Settings::default();
        let mut partial = PartialConfig::default();
        partial.font_family = Some("Mono".into());
        partial.font_size = Some(14);
        apply_partial(&mut cfg, partial);
        assert_eq!(cfg.font_family.as_deref(), Some("Mono"));
        assert_eq!(cfg.font_size, Some(14));
    }

    #[test]
    fn default_font_fields_are_none() {
        let cfg = Settings::default();
        assert!(cfg.font_family.is_none());
        assert!(cfg.font_size.is_none());
    }
}

/*
use anyhow::{bail, Result};

// Example: returning an error
pub fn load() -> Result<Settings> {
    let path = "/bad/path/config.toml";
    bail!("failed to read config at {path}");
}

// Caller: match the Result
fn main() -> Result<()> {
    match config::load() {
        Ok(settings) => println!("settings: {:?}", settings),
        Err(e) => eprintln!("config load error: {e}"),
    }
    Ok(())
}

// Or propagate with ? (like in main)
fn main() -> Result<()> {
    let settings = config::load()?; // if Err, returns early; otherwise continues
    // … more work, also using ? as needed …
    Ok(()) // success
}

// “error” is Err(anyhow::Error); match it or let ? propagate.
*/