//! Toggle actions and configuration operations for the Editor.
//!
//! This module contains toggle methods and configuration operations:
//! - Toggle line numbers, debug highlights, menu bar
//! - Toggle mouse capture, mouse hover, inlay hints
//! - Reset buffer settings
//! - Config dump, save, and reload

use crate::types::LspServerConfig;
use rust_i18n::t;

use crate::config::Config;
use crate::config_io::{ConfigLayer, ConfigResolver};
use crate::input::keybindings::KeybindingResolver;

use super::Editor;

impl Editor {
    /// Toggle line numbers in the gutter for the active split.
    ///
    /// Line number visibility is stored per-split in `BufferViewState` so that
    /// different splits of the same buffer can independently show/hide line numbers
    /// (e.g., source mode shows them, compose mode hides them).
    pub fn toggle_line_numbers(&mut self) {
        let active_split = self
            .windows
            .get(&self.active_window)
            .and_then(|w| w.buffers.splits())
            .map(|(mgr, _)| mgr)
            .expect("active window must have a populated split layout")
            .active_split();
        if let Some(vs) = self
            .windows
            .get_mut(&self.active_window)
            .and_then(|w| w.split_view_states_mut())
            .expect("active window must have a populated split layout")
            .get_mut(&active_split)
        {
            let currently_shown = vs.show_line_numbers;
            vs.show_line_numbers = !currently_shown;
            if currently_shown {
                self.set_status_message(t!("toggle.line_numbers_hidden").to_string());
            } else {
                self.set_status_message(t!("toggle.line_numbers_shown").to_string());
            }
        }
    }

    /// Toggle menu bar visibility.
    ///
    /// `editor.show_menu_bar` is a global preference, so the toggle updates the
    /// runtime config and persists the change to the user config layer (same
    /// pattern as the file-explorer toggles). See issue #1156.
    pub fn toggle_menu_bar(&mut self) {
        let new_value = !self.active_window_mut().menu_bar_visible;
        self.config_mut().editor.show_menu_bar = new_value;
        self.active_window_mut().menu_bar_visible = new_value;
        // When explicitly toggling, clear auto-show state
        self.active_window_mut().menu_bar_auto_shown = false;
        // Close any open menu when hiding the menu bar
        if !self.active_window_mut().menu_bar_visible {
            self.menu_state.close_menu();
        }
        self.persist_config_change("/editor/show_menu_bar", serde_json::Value::Bool(new_value));
        let status = if self.active_window_mut().menu_bar_visible {
            t!("toggle.menu_bar_shown")
        } else {
            t!("toggle.menu_bar_hidden")
        };
        self.set_status_message(status.to_string());
    }

    // `toggle_tab_bar` / `toggle_status_bar` / `toggle_prompt_line` and
    // their `*_visible` getters live on `impl Window` — call them via
    // `self.active_window_mut().toggle_tab_bar()` etc. (or read
    // `active_window().tab_bar_visible` for the flag directly).

    /// Toggle vertical scrollbar visibility
    pub fn toggle_vertical_scrollbar(&mut self) {
        let new_value = !self.config.editor.show_vertical_scrollbar;
        self.config_mut().editor.show_vertical_scrollbar = new_value;
        let status = if self.config.editor.show_vertical_scrollbar {
            t!("toggle.vertical_scrollbar_shown")
        } else {
            t!("toggle.vertical_scrollbar_hidden")
        };
        self.set_status_message(status.to_string());
    }

    /// Toggle horizontal scrollbar visibility
    pub fn toggle_horizontal_scrollbar(&mut self) {
        let new_value = !self.config.editor.show_horizontal_scrollbar;
        self.config_mut().editor.show_horizontal_scrollbar = new_value;
        let status = if self.config.editor.show_horizontal_scrollbar {
            t!("toggle.horizontal_scrollbar_shown")
        } else {
            t!("toggle.horizontal_scrollbar_hidden")
        };
        self.set_status_message(status.to_string());
    }

    /// Reset buffer settings (tab_size, use_tabs, auto_close, whitespace visibility) to config defaults
    pub fn reset_buffer_settings(&mut self) {
        use crate::config::WhitespaceVisibility;
        let buffer_id = self.active_buffer();

        // Determine settings from config using buffer's stored language
        let mut whitespace = WhitespaceVisibility::from_editor_config(&self.config.editor);
        let mut auto_close = self.config.editor.auto_close;
        let mut word_characters = String::new();
        let (tab_size, use_tabs) = if let Some(state) = self
            .windows
            .get(&self.active_window)
            .map(|w| &w.buffers)
            .expect("active window present")
            .get(&buffer_id)
        {
            let language = &state.language;
            if let Some(lang_config) = self.config.languages.get(language) {
                whitespace =
                    whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
                // Auto close: language override (only if globally enabled)
                if auto_close {
                    if let Some(lang_auto_close) = lang_config.auto_close {
                        auto_close = lang_auto_close;
                    }
                }
                if let Some(ref wc) = lang_config.word_characters {
                    word_characters = wc.clone();
                }
                (
                    lang_config.tab_size.unwrap_or(self.config.editor.tab_size),
                    lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs),
                )
            } else {
                (self.config.editor.tab_size, self.config.editor.use_tabs)
            }
        } else {
            (self.config.editor.tab_size, self.config.editor.use_tabs)
        };

        // Apply settings to buffer
        if let Some(state) = self
            .windows
            .get_mut(&self.active_window)
            .map(|w| &mut w.buffers)
            .expect("active window present")
            .get_mut(&buffer_id)
        {
            state.buffer_settings.tab_size = tab_size;
            state.buffer_settings.use_tabs = use_tabs;
            state.buffer_settings.auto_close = auto_close;
            state.buffer_settings.whitespace = whitespace;
            state.buffer_settings.word_characters = word_characters;
        }

        self.set_status_message(t!("toggle.buffer_settings_reset").to_string());
    }

    /// Toggle mouse capture on/off
    pub fn toggle_mouse_capture(&mut self) {
        use std::io::stdout;

        self.active_window_mut().mouse_enabled = !self.active_window_mut().mouse_enabled;

        if self.active_window_mut().mouse_enabled {
            // Best-effort terminal mouse capture toggle.
            #[allow(clippy::let_underscore_must_use)]
            let _ = crossterm::execute!(stdout(), crossterm::event::EnableMouseCapture);
            self.set_status_message(t!("toggle.mouse_capture_enabled").to_string());
        } else {
            // Best-effort terminal mouse capture toggle.
            #[allow(clippy::let_underscore_must_use)]
            let _ = crossterm::execute!(stdout(), crossterm::event::DisableMouseCapture);
            self.set_status_message(t!("toggle.mouse_capture_disabled").to_string());
        }
    }

    /// Check if mouse capture is enabled
    pub fn is_mouse_enabled(&self) -> bool {
        self.active_window().mouse_enabled
    }

    /// Toggle mouse hover for LSP on/off
    ///
    /// On Windows, this also switches the mouse tracking mode: mode 1003
    /// (all motion) when enabled, mode 1002 (cell motion) when disabled.
    pub fn toggle_mouse_hover(&mut self) {
        let new_value = !self.config.editor.mouse_hover_enabled;
        self.config_mut().editor.mouse_hover_enabled = new_value;

        if self.config.editor.mouse_hover_enabled {
            self.set_status_message(t!("toggle.mouse_hover_enabled").to_string());
        } else {
            // Clear any pending hover state
            self.active_window_mut().mouse_state.lsp_hover_state = None;
            self.active_window_mut().mouse_state.lsp_hover_request_sent = false;
            self.set_status_message(t!("toggle.mouse_hover_disabled").to_string());
        }

        // On Windows, switch mouse tracking mode to match
        #[cfg(windows)]
        {
            let mode = if self.config.editor.mouse_hover_enabled {
                fresh_winterm::MouseMode::AllMotion
            } else {
                fresh_winterm::MouseMode::CellMotion
            };
            if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
                tracing::error!("Failed to switch mouse mode: {}", e);
            }
        }
    }

    /// Check if mouse hover is enabled
    pub fn is_mouse_hover_enabled(&self) -> bool {
        self.config.editor.mouse_hover_enabled
    }

    /// Set GPM active flag (enables software mouse cursor rendering)
    ///
    /// When GPM is used for mouse input on Linux consoles, we need to draw
    /// our own mouse cursor because GPM can't draw on the alternate screen
    /// buffer used by TUI applications.
    pub fn set_gpm_active(&mut self, active: bool) {
        self.active_window_mut().gpm_active = active;
    }

    /// Toggle inlay hints visibility
    pub fn toggle_inlay_hints(&mut self) {
        let new_value = !self.config.editor.enable_inlay_hints;
        self.config_mut().editor.enable_inlay_hints = new_value;
        // `Window::send_lsp_changes_for_buffer` reads
        // `resources.config.editor.enable_inlay_hints`; sync so the per-edit
        // LSP refresh sees the new value without waiting for a reload.
        self.sync_windows_config();

        if self.config.editor.enable_inlay_hints {
            // Re-request inlay hints for the active buffer
            self.request_inlay_hints_for_active_buffer();
            self.set_status_message(t!("toggle.inlay_hints_enabled").to_string());
        } else {
            // Clear inlay hints from all buffers
            for (_, state) in self
                .windows
                .get_mut(&self.active_window)
                .map(|w| &mut w.buffers)
                .expect("active window present")
            {
                state.virtual_texts.clear(&mut state.marker_list);
            }
            self.set_status_message(t!("toggle.inlay_hints_disabled").to_string());
        }
    }

    /// Dump the current configuration to the user's config file
    pub fn dump_config(&mut self) {
        // Create the config directory if it doesn't exist
        if let Err(e) = self
            .authority
            .filesystem
            .create_dir_all(&self.dir_context.config_dir)
        {
            self.set_status_message(
                t!("error.config_dir_failed", error = e.to_string()).to_string(),
            );
            return;
        }

        let config_path = self.dir_context.config_path();
        let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());

        // Save the config to user layer
        match resolver.save_to_layer(&self.config, ConfigLayer::User) {
            Ok(()) => {
                // Open the saved config file in a new buffer
                match self.open_file(&config_path) {
                    Ok(_buffer_id) => {
                        self.set_status_message(
                            t!("config.saved", path = config_path.display().to_string())
                                .to_string(),
                        );
                    }
                    Err(e) => {
                        // Check if this is a large file encoding confirmation error
                        if let Some(confirmation) =
                            e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
                        {
                            self.start_large_file_encoding_confirmation(confirmation);
                        } else {
                            self.set_status_message(
                                t!("config.saved_failed_open", error = e.to_string()).to_string(),
                            );
                        }
                    }
                }
            }
            Err(e) => {
                self.set_status_message(
                    t!("error.config_save_failed", error = e.to_string()).to_string(),
                );
            }
        }
    }

    /// Save the current configuration to file (without opening it)
    ///
    /// Returns Ok(()) on success, or an error message on failure
    pub fn save_config(&self) -> Result<(), String> {
        // Create the config directory if it doesn't exist
        self.authority
            .filesystem
            .create_dir_all(&self.dir_context.config_dir)
            .map_err(|e| format!("Failed to create config directory: {}", e))?;

        let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
        resolver
            .save_to_layer(&self.config, ConfigLayer::User)
            .map_err(|e| format!("Failed to save config: {}", e))
    }

    /// Reload configuration from the config file
    ///
    /// This reloads the config from disk, applies runtime changes (theme, keybindings),
    /// and emits a config_changed event so plugins can update their state accordingly.
    /// Uses the layered config system to properly merge with defaults.
    pub fn reload_config(&mut self) {
        let old_theme = self.config.theme.clone();
        self.set_config(Config::load_with_layers(
            &self.dir_context,
            &self.working_dir,
        ));

        // Refresh cached raw user config for plugins
        self.set_user_config_raw(Config::read_user_config_raw(&self.working_dir));

        // Apply theme change if needed
        if old_theme != self.config.theme {
            if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
                *self.theme.write().unwrap() = theme;
                tracing::info!("Theme changed to '{}'", self.config.theme.0);
            } else {
                tracing::error!("Theme '{}' not found", self.config.theme.0);
            }
        }

        // Always reload keybindings (complex types don't implement PartialEq)
        *self.keybindings.write().unwrap() = KeybindingResolver::new(&self.config);

        // Update clipboard configuration
        self.clipboard.apply_config(&self.config.clipboard);

        // Apply bar visibility changes immediately
        self.active_window_mut().menu_bar_visible = self.config.editor.show_menu_bar;
        self.active_window_mut().tab_bar_visible = self.config.editor.show_tab_bar;
        self.active_window_mut().status_bar_visible = self.config.editor.show_status_bar;
        self.active_window_mut().prompt_line_visible = self.config.editor.show_prompt_line;

        // Update LSP configs
        let __active_id = self.active_window;
        if let Some(lsp) = self
            .windows
            .get_mut(&__active_id)
            .and_then(|w| w.lsp.as_mut())
        {
            for (language, lsp_configs) in &self.config.lsp {
                lsp.set_language_configs(language.clone(), lsp_configs.as_slice().to_vec());
            }
            // Configure universal (global) LSP servers
            let universal_servers: Vec<LspServerConfig> = self
                .config
                .universal_lsp
                .values()
                .flat_map(|lc| lc.as_slice().to_vec())
                .filter(|c| c.enabled)
                .collect();
            lsp.set_universal_configs(universal_servers);
        }

        // Emit event so plugins know config changed
        let config_path = Config::find_config_path(&self.working_dir);
        self.emit_event(
            "config_changed",
            serde_json::json!({
                "path": config_path.map(|p| p.to_string_lossy().into_owned()),
            }),
        );
    }

    /// Reload the theme registry from disk.
    ///
    /// Call this after installing new theme packages or saving new themes.
    /// This rescans all theme directories and updates the available themes list.
    pub fn reload_themes(&mut self) {
        use crate::view::theme::ThemeLoader;

        let theme_loader = ThemeLoader::new(self.dir_context.themes_dir());
        self.theme_registry = std::sync::Arc::new(theme_loader.load_all(&[]));
        self.expanded_menus_cache.invalidate();

        // Propagate the new registry to every window's resources so
        // window-side reads see the updated catalogue. (Theme registry
        // is `Arc<ThemeRegistry>` not `Arc<RwLock<>>`, so swapping the
        // Editor's pointer leaves Window clones stale unless we sync.)
        for w in self.windows.values_mut() {
            w.resources.theme_registry = self.theme_registry.clone();
        }

        // Update shared theme cache for plugin access
        *self.theme_cache.write().unwrap() = self.theme_registry.to_json_map();

        // Re-apply current theme if it still exists, otherwise it might have been updated
        if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
            *self.theme.write().unwrap() = theme;
        }

        tracing::info!(
            "Theme registry reloaded ({} themes)",
            self.theme_registry.len()
        );

        // Emit event so plugins know themes changed
        self.emit_event("themes_changed", serde_json::json!({}));
    }

    /// Persist a single config change to the user config file.
    ///
    /// Used when toggling settings via menu/command palette so that
    /// the change is saved immediately (matching the settings UI behavior).
    pub(super) fn persist_config_change(&self, json_pointer: &str, value: serde_json::Value) {
        let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
        let changes = std::collections::HashMap::from([(json_pointer.to_string(), value)]);
        let deletions = std::collections::HashSet::new();
        if let Err(e) = resolver.save_changes_to_layer(&changes, &deletions, ConfigLayer::User) {
            tracing::error!("Failed to persist config change {}: {}", json_pointer, e);
        }
    }
}
