use crate::env::SlateEnv;
use crate::error::Result;
use crate::theme::ThemeVariant;
use std::path::PathBuf;

pub mod alacritty;
pub mod bat;
pub mod delta;
pub mod eza;
pub mod fastfetch;
pub mod font;
pub mod ghostty;
pub mod kitty;
pub mod lazygit;
pub mod ls_colors;
pub mod marker_block;
pub mod nvim;
pub mod opencode;
pub mod palette_renderer;
pub mod registry;
pub mod starship;
pub mod tmux;
pub mod zsh_highlight;

// Re-export adapter structs for use in commands and tests
pub use alacritty::AlacrittyAdapter;
pub use bat::BatAdapter;
pub use delta::DeltaAdapter;
pub use eza::EzaAdapter;
pub use fastfetch::FastfetchAdapter;
pub use font::FontAdapter;
pub use ghostty::GhosttyAdapter;
pub use kitty::KittyAdapter;
pub use lazygit::LazygitAdapter;
pub use ls_colors::LsColorsAdapter;
pub use nvim::NvimAdapter;
pub use opencode::OpencodeAdapter;
pub use registry::{ToolApplyResult, ToolApplyStatus, ToolRegistry};
pub use starship::StarshipAdapter;
pub use tmux::TmuxAdapter;
pub use zsh_highlight::ZshHighlightAdapter;

/// How a tool includes external configuration files.
/// Variants match real-world tool behavior.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApplyStrategy {
    /// Write managed file + add include directive to integration file
    /// Tools: Alacritty (TOML include path), delta (git config [include]), tmux (source-file), lazygit (LG_CONFIG_FILE)
    WriteAndInclude,

    /// Modify specific fields in tool's integration config, preserving format
    /// Tools: Ghostty (edit color blocks directly), Starship (edit palette field)
    EditInPlace,

    /// Set environment variables emitted by `slate init`
    /// Tools: bat (BAT_THEME env var), eza (EZA_CONFIG_DIR)
    EnvironmentVariable,

    /// Source a generated script emitted by `slate init`
    /// Tools: zsh-syntax-highlighting (source ~/.config/slate/managed/zsh/highlight-styles.sh)
    SourceScript,

    /// Detect and install tool (no config file modification)
    /// Tools: Nerd Font (detection + brew install mapping only)
    DetectAndInstall,
}

/// Structured outcome for a single adapter theme application attempt.
/// The `Applied` variant carries `requires_new_shell: bool` so every adapter can
/// truthfully declare whether its change becomes visible inside the current shell
/// session (e.g. Ghostty hot-reload) or only after the user spawns a new shell
/// (e.g. `BAT_THEME` env var, `EZA_CONFIG_DIR`, starship palette select).
/// This drives the UX-01 "new-terminal" reminder.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApplyOutcome {
    Applied { requires_new_shell: bool },
    Skipped(SkipReason),
}

impl ApplyOutcome {
    /// Convenience constructor for adapters whose change is visible in the
    /// current shell session (no new-terminal reminder needed).
    pub const fn applied_no_shell() -> Self {
        Self::Applied {
            requires_new_shell: false,
        }
    }

    /// Convenience constructor for adapters whose change only takes effect
    /// in a fresh shell session (triggers the new-terminal reminder).
    pub const fn applied_needs_new_shell() -> Self {
        Self::Applied {
            requires_new_shell: true,
        }
    }
}

/// Reason an adapter intentionally skipped theme application.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SkipReason {
    MissingIntegrationConfig,
    NotInstalled,
}

impl std::fmt::Display for SkipReason {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SkipReason::MissingIntegrationConfig => {
                write!(f, "missing integration config")
            }
            SkipReason::NotInstalled => write!(f, "tool not installed"),
        }
    }
}

/// Trait all tool adapters must implement.
/// Adapters are responsible for:
/// - Detecting if tool is installed
/// - Knowing how to apply themes (include mechanism)
/// - Respecting three-tier config: managed/ (slate owns) + integration (entry point) + user/ (never touch)
pub trait ToolAdapter: Send + Sync {
    /// Tool identifier (e.g., "ghostty", "starship", "bat")
    fn tool_name(&self) -> &'static str;

    /// Check if tool is installed.
    /// Must verify: binary in PATH AND config directory exists
    /// adapter is responsible for robust detection
    fn is_installed(&self) -> Result<bool>;

    /// Env-aware variant of [`is_installed`] used by the preview path so a
    /// tempdir-backed `SlateEnv` is honored when probing for the tool.
    /// Default delegates to `is_installed()`. Adapters that detect via
    /// `~/Applications/*.app` or `~/.config/<tool>/...` should override this.
    fn is_installed_with_env(&self, _env: &SlateEnv) -> Result<bool> {
        self.is_installed()
    }

    /// Path to tool's primary/integration config file.
    /// This is the file user owns and modifies (entry point for includes).
    /// Example: ~/.config/ghostty/config.ghostty or ~/.config/starship.toml
    fn integration_config_path(&self) -> Result<PathBuf>;

    /// Path where slate writes managed configuration.
    /// This is read-only from user perspective; slate regenerates it.
    /// Example: ~/.config/slate/managed/ghostty/ or ~/.config/slate/managed/starship/
    /// hardcode ~/.config/slate/ not ProjectDirs
    fn managed_config_path(&self) -> PathBuf;

    /// Strategy for applying theme to this tool.
    /// Determines whether adapter writes separate file or edits integration file.
    /// one of four variants
    fn apply_strategy(&self) -> ApplyStrategy;

    /// Apply theme to tool's configuration.
    /// Must:
    /// 1. Write theme data to managed_config_path()
    /// 2. Ensure integration file includes/references managed path (per apply_strategy)
    /// 3. Never modify user/ directory
    /// 4. Be idempotent (running twice produces same result)
    fn apply_theme(&self, theme: &ThemeVariant) -> Result<ApplyOutcome>;

    /// Apply theme using an explicitly injected [`SlateEnv`].
    /// Preview-path adapters (Ghostty, Alacritty, Kitty, Starship) override
    /// this method so that `silent_preview_apply(&env, …)` can route a
    /// tempdir-backed env all the way through to per-adapter path resolution
    /// (`integration_config_path`, `managed_config_path`) without any call to
    /// `SlateEnv::from_process()`. That in turn lets integration tests assert
    /// real file-level rollback against the injected env with zero
    /// `std::env::set_var` — closing the "signature lie" that
    /// Gemini's review surfaced.
    /// Adapters that write files must override this method. The default is
    /// only appropriate for pure signal/no-op adapters that do not resolve
    /// user paths during apply.
    fn apply_theme_with_env(&self, theme: &ThemeVariant, _env: &SlateEnv) -> Result<ApplyOutcome> {
        self.apply_theme(theme)
    }

    /// Hot-reload mechanism for this tool.
    /// Allows theme changes to take effect without closing terminal.
    /// implementation varies by tool
    /// Returns Ok() if reload succeeded, Err if tool doesn't support or failed.
    fn reload(&self) -> Result<()> {
        // Default: no-op. Adapters override if they support reload.
        Ok(())
    }

    /// Get current theme applied to this tool (optional, for status).
    /// Adapters can leave unimplemented; returns None by default.
    fn get_current_theme(&self) -> Result<Option<String>> {
        Ok(None)
    }
}

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

    #[test]
    fn test_apply_strategy_variants() {
        // Verify all variants exist and are distinct
        let write_include = ApplyStrategy::WriteAndInclude;
        let edit_in_place = ApplyStrategy::EditInPlace;
        let env_var = ApplyStrategy::EnvironmentVariable;
        let source_script = ApplyStrategy::SourceScript;
        let detect_install = ApplyStrategy::DetectAndInstall;

        // All variants should be distinguishable
        assert_ne!(write_include, edit_in_place);
        assert_ne!(env_var, source_script);
        assert_ne!(write_include, env_var);
        assert_ne!(edit_in_place, source_script);
        assert_ne!(detect_install, write_include);
    }

    #[test]
    fn test_apply_strategy_debug() {
        let strategy = ApplyStrategy::WriteAndInclude;
        let debug_str = format!("{:?}", strategy);
        assert!(debug_str.contains("WriteAndInclude"));
    }

    #[test]
    fn test_apply_strategy_clone_copy() {
        let strategy = ApplyStrategy::EditInPlace;
        let cloned = strategy;
        assert_eq!(strategy, cloned);
    }
}
