use super::*;
use crate::list_view::*;

impl App {
    pub fn toggle_external_editor_mode(&mut self) {
        self.editor.external_editor_enabled = !self.editor.external_editor_enabled;
        let msg = if self.editor.external_editor_enabled {
            "External editor mode enabled"
        } else {
            "External editor mode disabled"
        };
        self.set_temporary_status(msg);
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.editor.external_enabled = self.editor.external_editor_enabled;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_pin(&mut self) {
        if let Some(VisualItem::Note { summary_idx, .. }) =
            self.list.visual_list.get(self.list.visual_index)
        {
            let id = self.notes[*summary_idx].id.clone();
            match self.storage.toggle_pin(&id) {
                Ok(pinned) => {
                    if let Err(e) = self.refresh_notes() {
                        self.set_temporary_status(&format!("Refresh failed: {e}"));
                    }
                    if pinned {
                        self.set_temporary_status_static("Note pinned");
                    } else {
                        self.set_temporary_status_static("Note unpinned");
                    }
                }
                Err(e) => {
                    self.set_temporary_status(&format!("Failed to toggle pin: {e}"));
                }
            }
        } else {
            self.set_temporary_status_static("Select a note to pin/unpin");
        }
    }

    pub fn toggle_preview(&mut self) {
        self.list.preview_enabled = !self.list.preview_enabled;
        if self.list.preview_enabled {
            self.update_preview();
            self.set_temporary_status_static("Preview enabled");
        } else {
            self.list.preview_content = None;
            self.set_temporary_status_static("Preview disabled");
        }
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.list.preview_enabled = self.list.preview_enabled;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_calendar(&mut self) {
        self.list.calendar_enabled = !self.list.calendar_enabled;
        if self.list.calendar_enabled {
            self.set_temporary_status_static("Calendar enabled");
        } else {
            self.set_temporary_status_static("Calendar disabled");
        }
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.list.calendar_enabled = self.list.calendar_enabled;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_layout_edit(&mut self) {
        self.layout_edit = !self.layout_edit;
        self.layout_drag = None;
        self.set_temporary_status_static(if self.layout_edit {
            "Layout edit mode: drag borders / hjkl←→↑↓ / s swap / c cal / Esc"
        } else {
            "Layout edit mode off"
        });
        if !self.layout_edit {
            self.persist_list_layout();
        }
    }

    pub fn adjust_preview_width(&mut self, delta: f32) {
        self.adjust_preview_width_to(self.list.preview_width_ratio + delta);
        self.persist_list_layout();
    }

    pub fn adjust_preview_width_to(&mut self, ratio: f32) {
        self.list.preview_width_ratio = ratio.clamp(0.2, 0.8);
    }

    pub fn adjust_calendar_height(&mut self, delta: i16) {
        self.adjust_calendar_height_to(self.list.calendar_height.saturating_add_signed(delta));
        self.persist_list_layout();
    }

    pub fn adjust_calendar_height_to(&mut self, height: u16) {
        self.list.calendar_height = height.clamp(9, 20);
    }

    pub fn swap_preview_position(&mut self) {
        self.preview_position = match self.preview_position {
            crate::config::PreviewPosition::Left => crate::config::PreviewPosition::Right,
            crate::config::PreviewPosition::Right => crate::config::PreviewPosition::Left,
        };
        self.set_temporary_status_static(
            if matches!(self.preview_position, crate::config::PreviewPosition::Left) {
                "Preview moved to left"
            } else {
                "Preview moved to right"
            },
        );
        self.persist_list_layout();
    }

    pub fn swap_calendar_position(&mut self) {
        self.calendar_position = match self.calendar_position {
            crate::config::CalendarPosition::Top => crate::config::CalendarPosition::Bottom,
            crate::config::CalendarPosition::Bottom => crate::config::CalendarPosition::Top,
        };
        self.set_temporary_status_static(
            if matches!(self.calendar_position, crate::config::CalendarPosition::Top) {
                "Calendar moved to top"
            } else {
                "Calendar moved to bottom"
            },
        );
        self.persist_list_layout();
    }

    pub fn active_strip_sections(&self) -> Vec<crate::config::NotesSection> {
        let mut v: Vec<_> = self
            .list
            .sections
            .iter()
            .copied()
            .filter(|s| {
                !matches!(s, crate::config::NotesSection::Goals) || self.config.goals.enabled
            })
            .collect();
        if v.is_empty() {
            v.push(crate::config::NotesSection::Calendar);
        }
        v
    }

    pub fn active_strip_sections_for(&self, width: u16) -> Vec<crate::config::NotesSection> {
        let mut v = self.active_strip_sections();
        if width < 42 {
            v.retain(|s| !matches!(s, crate::config::NotesSection::Goals));
        }
        if v.is_empty() {
            v.push(crate::config::NotesSection::Calendar);
        }
        v
    }

    pub fn swap_section_order(&mut self) {
        if self.list.sections.len() >= 2 {
            self.list.sections.reverse();
            self.set_temporary_status_static("Sections swapped");
            self.persist_list_layout();
        }
    }

    pub fn cycle_section(&mut self, slot: usize) {
        if slot >= self.list.sections.len() {
            return;
        }
        self.list.sections[slot] = match self.list.sections[slot] {
            crate::config::NotesSection::Calendar => crate::config::NotesSection::Goals,
            crate::config::NotesSection::Goals => crate::config::NotesSection::Draw,
            crate::config::NotesSection::Draw => crate::config::NotesSection::Graf,
            crate::config::NotesSection::Graf => crate::config::NotesSection::Calendar,
        };
        self.set_temporary_status_static("Section changed");
        self.persist_list_layout();
    }

    pub fn toggle_section(&mut self) {
        if self.list.sections.len() < 2 {
            self.list.sections.push(crate::config::NotesSection::Goals);
            self.set_temporary_status_static("Section added");
        } else {
            self.list.sections.truncate(1);
            self.set_temporary_status_static("Section removed");
        }
        self.persist_list_layout();
    }

    pub(crate) fn persist_list_layout(&mut self) {
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.list.preview_width_ratio = self.list.preview_width_ratio;
            config.list.calendar_height = self.list.calendar_height;
            config.list.preview_position = self.preview_position;
            config.list.calendar_position = self.calendar_position;
            config.list.sections = self.list.sections.clone();
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save layout: {e}"));
            }
        }
    }

    pub fn toggle_markdown_preview(&mut self) {
        self.editor.editor_preview_enabled = !self.editor.editor_preview_enabled;
        if self.editor.editor_preview_enabled {
            self.update_editor_markdown_preview();
            self.set_temporary_status_static("Markdown preview enabled");
        } else {
            self.editor.md_preview_renderer = None;
            self.set_temporary_status_static("Markdown preview disabled");
        }
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.editor.preview_enabled = self.editor.editor_preview_enabled;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }
    pub fn toggle_preview_fullscreen(&mut self) {
        if matches!(
            self.config.core.preview_expand_mode,
            crate::config::PreviewExpandMode::External
        ) {
            self.open_external_preview();
            return;
        }
        self.preview_fullscreen = !self.preview_fullscreen;
        match self.mode {
            ViewMode::Edit => self.update_editor_markdown_preview(),
            _ => self.update_preview(),
        }
        if self.preview_fullscreen {
            self.set_temporary_status_static("Preview expanded");
        } else {
            self.set_temporary_status_static("Preview restored");
        }
    }

    pub fn toggle_preview_wrap(&mut self) {
        self.preview_wrap = !self.preview_wrap;
        self.config.core.preview_wrap = self.preview_wrap;
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.core.preview_wrap = self.preview_wrap;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
        match self.mode {
            ViewMode::Edit => self.update_editor_markdown_preview(),
            _ => self.update_preview(),
        }
        self.set_temporary_status_static(if self.preview_wrap {
            "Wrap on"
        } else {
            "Wrap off"
        });
    }

    pub fn toggle_show_line_numbers(&mut self) {
        self.editor.show_line_numbers = !self.editor.show_line_numbers;
        let msg: &'static str = if self.editor.show_line_numbers {
            "Line numbers enabled"
        } else {
            "Line numbers disabled"
        };
        self.set_temporary_status_static(msg);
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.editor.show_line_numbers = self.editor.show_line_numbers;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_confirm_on_delete(&mut self) {
        self.confirm_on_delete = !self.confirm_on_delete;
        let msg: &'static str = if self.confirm_on_delete {
            "Delete confirmation enabled"
        } else {
            "Delete confirmation disabled"
        };
        self.set_temporary_status_static(msg);
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.core.confirm_on_delete = self.confirm_on_delete;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_confirm_on_quit(&mut self) {
        self.confirm_on_quit = !self.confirm_on_quit;
        let msg: &'static str = if self.confirm_on_quit {
            "Quit confirmation enabled"
        } else {
            "Quit confirmation disabled"
        };
        self.set_temporary_status_static(msg);
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.core.confirm_on_quit = self.confirm_on_quit;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_preview_encryption(&mut self) {
        self.preview_encryption = !self.preview_encryption;
        let msg: &'static str = if self.preview_encryption {
            "Encrypted note previews enabled"
        } else {
            "Encrypted note previews hidden"
        };
        self.set_temporary_status_static(msg);
        if self.list.preview_enabled {
            self.update_preview();
        }
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.list.preview_encryption = self.preview_encryption;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_pinned_on_top(&mut self) {
        self.pinned_on_top = !self.pinned_on_top;
        if let Err(e) = self.refresh_notes() {
            self.set_temporary_status(&format!("Refresh failed: {e}"));
        }
        let msg: &'static str = if self.pinned_on_top {
            "Pinned notes shown on top"
        } else {
            "Pinned notes in natural order"
        };
        self.set_temporary_status_static(msg);
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.list.pinned_on_top = self.pinned_on_top;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_show_hidden_files(&mut self) {
        self.list.show_hidden_files = !self.list.show_hidden_files;
        self.list.folder_cache = None; // invalidate cached folder list
        if let Err(e) = self.refresh_notes() {
            self.set_temporary_status(&format!("Refresh failed: {e}"));
        }
        let msg: &'static str = if self.list.show_hidden_files {
            "Hidden files shown"
        } else {
            "Hidden files hidden"
        };
        self.set_temporary_status_static(msg);
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.list.show_hidden_files = self.list.show_hidden_files;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_show_all_files(&mut self) {
        self.list.show_all_files = !self.list.show_all_files;
        self.list.folder_cache = None; // invalidate cached folder list
        if let Err(e) = self.refresh_notes() {
            self.set_temporary_status(&format!("Refresh failed: {e}"));
        }
        let msg: &'static str = if self.list.show_all_files {
            "Showing all files"
        } else {
            "Showing notes only"
        };
        self.set_temporary_status_static(msg);
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.list.show_all_files = self.list.show_all_files;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_folders_first(&mut self) {
        self.list.folders_first = !self.list.folders_first;
        self.refresh_visual_list();
        let msg: &'static str = if self.list.folders_first {
            "Folders first in list"
        } else {
            "Files first in list"
        };
        self.set_temporary_status_static(msg);
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.list.folders_first = self.list.folders_first;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_tab_icons_only(&mut self) {
        self.config.ui.tab_icons_only = !self.config.ui.tab_icons_only;
        let msg: &'static str = if self.config.ui.tab_icons_only {
            "Tab icons only"
        } else {
            "Tab icons + labels"
        };
        self.set_temporary_status_static(msg);
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.ui.tab_icons_only = self.config.ui.tab_icons_only;
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }

    pub fn toggle_notes_layout(&mut self) {
        self.list.notes_layout = match self.list.notes_layout {
            crate::config::NotesLayout::Tree => crate::config::NotesLayout::Grid,
            crate::config::NotesLayout::Grid => crate::config::NotesLayout::Tree,
        };
        self.list.visual_index = 0;
        // #1: entering grid always opens the Vault tab (grid_folder == "")
        self.list.grid_folder = String::new();
        self.refresh_visual_list();
        // #2: persist
        if let Ok(mut config) = crate::config::ClinConfig::load() {
            config.list.default_view = self.list.notes_layout.clone();
            if let Err(e) = config.save() {
                self.set_temporary_status(&format!("Failed to save config: {e}"));
            }
        }
    }
    pub fn load_goals_progress(&self) -> crate::goals::DailyProgress {
        let path = self.storage.config_dir.join("goals_progress.json");
        let today = chrono::Local::now().date_naive().to_string();
        if path.exists()
            && let Ok(content) = std::fs::read_to_string(&path)
            && let Ok(progress) = serde_json::from_str::<crate::goals::DailyProgress>(&content)
            && progress.date == today
        {
            return progress;
        }
        crate::goals::DailyProgress {
            date: today,
            words_written: 0,
            notes_modified: std::collections::HashSet::new(),
        }
    }

    pub fn save_goals_progress(&self, progress: &crate::goals::DailyProgress) {
        let path = self.storage.config_dir.join("goals_progress.json");
        if let Some(parent) = path.parent() {
            let _ = std::fs::create_dir_all(parent);
        }
        if let Ok(content) = serde_json::to_string(progress) {
            let _ = crate::fsutil::atomic_write_str(&path, &content);
        }
    }

    pub fn get_current_goals_progress(&mut self) -> &mut crate::goals::DailyProgress {
        self.check_and_reload_config();
        let today = chrono::Local::now().date_naive().to_string();
        if self.goals_progress.date != today {
            self.goals_progress = self.load_goals_progress();
        }
        &mut self.goals_progress
    }

    pub fn ensure_draw_preview(&mut self) {
        let target = self
            .get_selected_note_id()
            .filter(|id| id.ends_with(".draw"))
            .or_else(|| {
                self.notes
                    .iter()
                    .filter(|n| n.id.ends_with(".draw"))
                    .max_by_key(|n| n.updated_at)
                    .map(|n| n.id.clone())
            });
        let target = match target {
            Some(id) => id,
            None => {
                if !matches!(
                    self.draw_preview.as_ref().map(|(id, _)| id.as_str()),
                    Some("")
                ) {
                    self.draw_preview =
                        Some((String::new(), crate::draw::state::DrawData::default()));
                }
                return;
            }
        };
        if self
            .draw_preview
            .as_ref()
            .map(|(id, _)| id == &target)
            .unwrap_or(false)
        {
            return;
        }
        let data = std::fs::read_to_string(self.storage.note_path(&target))
            .ok()
            .and_then(|s| serde_json::from_str::<crate::draw::state::DrawData>(&s).ok())
            .unwrap_or_default();
        self.draw_preview = Some((target, data));
    }

    pub fn ensure_graph_preview(&mut self) {
        let sig = self.notes.len();
        if self.graph_preview.is_some() && self.graph_preview_sig == sig {
            return;
        }
        match crate::graf::graph::GraphState::new(&self.storage, &self.config) {
            Ok(mut gs) => {
                gs.viewport = gs
                    .viewport
                    .auto_fit_from_graph(gs.simulation.get_graph(), 1.4);
                gs.graph_bounds =
                    crate::graf::render::compute_graph_bounds(gs.simulation.get_graph());
                self.graph_preview = Some(gs);
                self.graph_preview_sig = sig;
                self.graph_preview_steps = 0;
            }
            Err(_) => {
                self.graph_preview = None;
                self.graph_preview_sig = sig;
                self.graph_preview_steps = 0;
            }
        }
    }
}

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

    fn make_app() -> App {
        let temp_dir = tempfile::tempdir().unwrap();
        let data_dir = temp_dir.path().join("data");
        let config_dir = temp_dir.path().join("config");
        let notes_dir = temp_dir.path().join("notes");
        let templates_dir = temp_dir.path().join("templates");
        std::fs::create_dir_all(&data_dir).unwrap();
        std::fs::create_dir_all(&config_dir).unwrap();
        std::fs::create_dir_all(&notes_dir).unwrap();
        std::fs::create_dir_all(&templates_dir).unwrap();

        let storage = crate::storage::Storage {
            data_dir,
            config_dir,
            notes_dir,
            templates_dir,
            key: [0u8; 32],
        };
        App::new(storage).unwrap()
    }

    #[test]
    fn test_swap_section_order_reverses() {
        let mut app = make_app();
        app.list.sections = vec![
            crate::config::NotesSection::Calendar,
            crate::config::NotesSection::Draw,
        ];
        app.swap_section_order();
        assert_eq!(app.list.sections[0], crate::config::NotesSection::Draw);
        assert_eq!(app.list.sections[1], crate::config::NotesSection::Calendar);
    }

    #[test]
    fn test_swap_section_order_noop_on_single() {
        let mut app = make_app();
        app.list.sections = vec![crate::config::NotesSection::Calendar];
        app.swap_section_order();
        assert_eq!(app.list.sections.len(), 1);
        assert_eq!(app.list.sections[0], crate::config::NotesSection::Calendar);
    }

    #[test]
    fn test_cycle_section_calendar_to_goals() {
        let mut app = make_app();
        app.list.sections = vec![crate::config::NotesSection::Calendar];
        app.cycle_section(0);
        assert_eq!(app.list.sections[0], crate::config::NotesSection::Goals);
    }

    #[test]
    fn test_cycle_section_graf_wraps_to_calendar() {
        let mut app = make_app();
        app.list.sections = vec![crate::config::NotesSection::Graf];
        app.cycle_section(0);
        assert_eq!(app.list.sections[0], crate::config::NotesSection::Calendar);
    }

    #[test]
    fn test_cycle_section_out_of_range_noop() {
        let mut app = make_app();
        app.list.sections = vec![crate::config::NotesSection::Calendar];
        app.cycle_section(5); // out of range
        assert_eq!(app.list.sections.len(), 1);
        assert_eq!(app.list.sections[0], crate::config::NotesSection::Calendar);
    }

    #[test]
    fn apply_setup_live_writes_5_fields() {
        let _lock = crate::config::CONFIG_TEST_MUTEX.lock();
        let mut app = make_app();

        app.setup_state = Some(crate::setup::SetupState {
            theme: 4, // gruvbox
            background_solid: true,
            hint_bar_style: 2, // PowerlineSharp
            icon_mode: 1,      // Unicode
            keybind_preset: 2, // Vim
            selected: 0,
            confirm_exit: false,
        });

        app.finish_setup();

        assert_eq!(app.config.ui.theme, "gruvbox");
        assert_eq!(app.config.ui.background, crate::config::Background::Solid);
        assert_eq!(
            app.config.ui.hint_bar_style,
            crate::config::HintBarStyle::PowerlineSharp
        );
        assert_eq!(app.config.ui.icon_mode, crate::config::IconMode::Unicode);
        assert_eq!(
            app.config.core.keybind_preset,
            crate::config::KeybindPreset::Vim
        );
        // finish_setup tears down the view.
        assert!(app.setup_state.is_none());
        assert_eq!(app.mode, crate::app::ViewMode::List);
    }

    #[test]
    fn setup_cycle_live_applies_theme() {
        let _lock = crate::config::CONFIG_TEST_MUTEX.lock();
        let mut app = make_app();

        app.setup_state = Some(crate::setup::SetupState {
            theme: 0,
            background_solid: false,
            hint_bar_style: 0,
            icon_mode: 0,
            keybind_preset: 0,
            selected: 0, // Theme row
            confirm_exit: false,
        });

        // Cycle theme forward → apply_setup_live writes it to config.
        app.setup_state.as_mut().unwrap().cycle(true);
        app.apply_setup_live();
        assert_eq!(app.config.ui.theme, "tokyo_night");

        // Flip background via row 1 → config mirrors it.
        let state = app.setup_state.as_mut().unwrap();
        state.selected = 1;
        state.cycle(true);
        app.apply_setup_live();
        assert_eq!(app.config.ui.background, crate::config::Background::Solid);
    }

    #[test]
    fn setup_esc_opens_confirm_then_y_finishes() {
        use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
        let _lock = crate::config::CONFIG_TEST_MUTEX.lock();
        let mut app = make_app();

        app.setup_state = Some(crate::setup::SetupState {
            theme: 4, // gruvbox — non-default so we can confirm it propagates
            background_solid: true,
            hint_bar_style: 0,
            icon_mode: 0,
            keybind_preset: 0,
            selected: 0,
            confirm_exit: false,
        });

        let esc = KeyEvent {
            code: KeyCode::Esc,
            modifiers: KeyModifiers::NONE,
            kind: KeyEventKind::Press,
            state: KeyEventState::empty(),
        };
        crate::events::handle_setup_keys(&mut app, esc);
        assert!(app.setup_state.as_ref().unwrap().confirm_exit);

        // 'n' cancels the confirm overlay.
        let n = KeyEvent {
            code: KeyCode::Char('n'),
            modifiers: KeyModifiers::NONE,
            kind: KeyEventKind::Press,
            state: KeyEventState::empty(),
        };
        crate::events::handle_setup_keys(&mut app, n);
        assert!(!app.setup_state.as_ref().unwrap().confirm_exit);

        // Re-open and confirm with 'y' → finish_setup saves + closes.
        app.setup_state.as_mut().unwrap().confirm_exit = true;
        let y = KeyEvent {
            code: KeyCode::Char('y'),
            modifiers: KeyModifiers::NONE,
            kind: KeyEventKind::Press,
            state: KeyEventState::empty(),
        };
        crate::events::handle_setup_keys(&mut app, y);
        assert!(app.setup_state.is_none());
        assert_eq!(app.mode, crate::app::ViewMode::List);
        assert_eq!(app.config.ui.theme, "gruvbox");
    }
}
