//! Routes `AppCommand` variants to the appropriate `App` methods.

use crate::commands::AppCommand;
use crate::engine::{LinkState, SequencerSnapshot};
use crate::model::bp_label;
use crate::page::Page;
use crate::services::{dict_nav, euclidean, help_nav, pattern_editor};
use crate::state::{undo::UndoEntry, FlashKind, Modal, StagedPropChange};

use super::App;

impl App {
    pub fn dispatch(&mut self, cmd: AppCommand, link: &LinkState, snapshot: &SequencerSnapshot) {
        match cmd {
            AppCommand::Undo => {
                if let Some(entry) = self.undo.pop_undo() {
                    let reverse = self.apply_undo_entry(entry);
                    self.undo.push_redo(reverse);
                    self.ui.flash("Undo", 100, FlashKind::Info);
                }
                return;
            }
            AppCommand::Redo => {
                if let Some(entry) = self.undo.pop_redo() {
                    let reverse = self.apply_undo_entry(entry);
                    self.undo.undo_stack.push(reverse);
                    self.ui.flash("Redo", 100, FlashKind::Info);
                }
                return;
            }
            _ => {}
        }

        if let Some(scope) = self.undoable_scope(&cmd) {
            let cursor = (self.editor_ctx.bank, self.editor_ctx.pattern, self.editor_ctx.step);
            self.undo.push(UndoEntry { scope, cursor });
        }

        match cmd {
            AppCommand::Undo | AppCommand::Redo => unreachable!(),

            // Playback
            AppCommand::TogglePlaying => self.toggle_playing(link),
            AppCommand::TempoUp => self.tempo_up(link),
            AppCommand::TempoDown => self.tempo_down(link),

            // Navigation
            AppCommand::NextStep => self.next_step(),
            AppCommand::PrevStep => self.prev_step(),
            AppCommand::StepUp => self.step_up(),
            AppCommand::StepDown => self.step_down(),
            AppCommand::NextPattern => self.navigate_pattern(1),
            AppCommand::PrevPattern => self.navigate_pattern(-1),
            AppCommand::NextBank => self.navigate_bank(1),
            AppCommand::PrevBank => self.navigate_bank(-1),

            // Pattern editing
            AppCommand::ToggleSteps => self.toggle_steps(),
            AppCommand::LengthIncrease => self.length_increase(),
            AppCommand::LengthDecrease => self.length_decrease(),
            AppCommand::SpeedIncrease => self.speed_increase(),
            AppCommand::SpeedDecrease => self.speed_decrease(),
            AppCommand::SetLength {
                bank,
                pattern,
                length,
            } => {
                let (change, new_len) = pattern_editor::set_length(
                    &mut self.project_state.project,
                    bank,
                    pattern,
                    length,
                );
                if self.editor_ctx.bank == bank
                    && self.editor_ctx.pattern == pattern
                    && self.editor_ctx.step >= new_len
                {
                    self.editor_ctx.step = new_len - 1;
                }
                self.project_state.mark_dirty(change.bank, change.pattern);
            }
            AppCommand::SetSpeed {
                bank,
                pattern,
                speed,
            } => {
                let change = pattern_editor::set_speed(
                    &mut self.project_state.project,
                    bank,
                    pattern,
                    speed,
                );
                self.project_state.mark_dirty(change.bank, change.pattern);
            }

            // Script editing
            AppCommand::SaveEditorToStep => self.save_editor_to_step(),
            AppCommand::CompileCurrentStep => self.compile_current_step(link),
            AppCommand::DeleteStep {
                bank,
                pattern,
                step,
            } => self.delete_step(bank, pattern, step),
            AppCommand::DeleteSteps {
                bank,
                pattern,
                steps,
            } => self.delete_steps(bank, pattern, &steps),
            AppCommand::ResetPattern { bank, pattern } => self.reset_pattern(bank, pattern),
            AppCommand::ResetBank { bank } => self.reset_bank(bank),
            AppCommand::CopyPattern { bank, pattern } => self.copy_pattern(bank, pattern),
            AppCommand::PastePattern { bank, pattern } => self.paste_pattern(bank, pattern),
            AppCommand::SharePattern { bank, pattern } => self.share_pattern(bank, pattern),
            AppCommand::ImportPattern { bank, pattern } => self.import_pattern(bank, pattern),
            AppCommand::ShareBank { bank } => self.share_bank(bank),
            AppCommand::ImportBank { bank } => self.import_bank(bank),
            AppCommand::CopyPatterns { bank, patterns } => self.copy_patterns(bank, &patterns),
            AppCommand::PastePatterns { bank, start } => self.paste_patterns(bank, start),
            AppCommand::CopyBank { bank } => self.copy_bank(bank),
            AppCommand::PasteBank { bank } => self.paste_bank(bank),
            AppCommand::CopyBanks { banks } => self.copy_banks(&banks),
            AppCommand::PasteBanks { start } => self.paste_banks(start),
            AppCommand::ResetPatterns { bank, patterns } => self.reset_patterns(bank, &patterns),
            AppCommand::ResetBanks { banks } => self.reset_banks(&banks),

            // Reorder
            AppCommand::ShiftPatternsUp => self.shift_patterns_up(),
            AppCommand::ShiftPatternsDown => self.shift_patterns_down(),

            // Clipboard
            AppCommand::HardenSteps => self.harden_steps(),
            AppCommand::CopySteps => self.copy_steps(),
            AppCommand::PasteSteps => self.paste_steps(link),
            AppCommand::LinkPasteSteps => self.link_paste_steps(),
            AppCommand::DuplicateSteps => self.duplicate_steps(link),

            // Pattern playback (staging)
            AppCommand::ClearStagedChanges => self.clear_staged_changes(),

            // Project
            AppCommand::RenameBank { bank, name } => {
                self.project_state.project.banks[bank].name = name;
            }
            AppCommand::RenamePattern {
                bank,
                pattern,
                name,
            } => {
                self.project_state.project.banks[bank].patterns[pattern].name = name;
            }
            AppCommand::DescribePattern {
                bank,
                pattern,
                description,
            } => {
                self.project_state.project.banks[bank].patterns[pattern].description = description;
            }
            AppCommand::RenameStep {
                bank,
                pattern,
                step,
                name,
            } => {
                if let Some(s) =
                    self.project_state.project.banks[bank].patterns[pattern].step_mut(step)
                {
                    s.name = name;
                }
                self.project_state.mark_dirty(bank, pattern);
            }
            AppCommand::Save(path) => self.save(path, link, snapshot),
            AppCommand::Load(path) => self.load(path, link),

            // UI
            AppCommand::SetStatus(msg) => self.ui.set_status(msg),
            AppCommand::ClearStatus => self.ui.clear_status(),
            AppCommand::OpenModal(modal) => {
                if matches!(modal, Modal::Editor) {
                    let pattern = &self.project_state.project.banks[self.editor_ctx.bank].patterns
                        [self.editor_ctx.pattern];
                    if let Some(source) = pattern.step(self.editor_ctx.step).and_then(|s| s.source)
                    {
                        self.editor_ctx.step = source as usize;
                    }
                    self.load_step_to_editor();
                }
                self.ui.modal = modal;
            }
            AppCommand::CloseModal => self.ui.modal = Modal::None,
            AppCommand::OpenPatternModal(field) => self.open_pattern_modal(field),
            AppCommand::OpenPatternPropsModal { bank, pattern } => {
                self.open_pattern_props_modal(bank, pattern);
            }
            AppCommand::StagePatternProps {
                bank,
                pattern,
                name,
                description,
                length,
                speed,
                quantization,
                sync_mode,
                follow_up,
            } => {
                self.playback.staged_prop_changes.insert(
                    (bank, pattern),
                    StagedPropChange {
                        name,
                        description,
                        length,
                        speed,
                        quantization,
                        sync_mode,
                        follow_up,
                    },
                );
                self.ui
                    .set_status(format!("{} props staged", bp_label(bank, pattern)));
            }

            // Page navigation
            AppCommand::PageLeft => {
                self.auto_save_script_on_leave();
                self.page.left();
                self.auto_load_script_on_arrive();
                self.maybe_show_onboarding();
            }
            AppCommand::PageRight => {
                self.auto_save_script_on_leave();
                self.page.right();
                self.auto_load_script_on_arrive();
                self.maybe_show_onboarding();
            }
            AppCommand::PageUp => {
                self.auto_save_script_on_leave();
                self.page.up();
                self.auto_load_script_on_arrive();
                self.maybe_show_onboarding();
            }
            AppCommand::PageDown => {
                self.auto_save_script_on_leave();
                self.page.down();
                self.auto_load_script_on_arrive();
                self.maybe_show_onboarding();
            }
            AppCommand::GoToPage(page) => {
                self.auto_save_script_on_leave();
                self.page = page;
                self.auto_load_script_on_arrive();
                self.maybe_show_onboarding();
            }

            // Help navigation
            AppCommand::HelpToggleFocus => help_nav::toggle_focus(&mut self.ui),
            AppCommand::HelpNextTopic(n) => help_nav::next_topic(&mut self.ui, n),
            AppCommand::HelpPrevTopic(n) => help_nav::prev_topic(&mut self.ui, n),
            AppCommand::HelpScrollDown(n) => help_nav::scroll_down(&mut self.ui, n),
            AppCommand::HelpScrollUp(n) => help_nav::scroll_up(&mut self.ui, n),
            AppCommand::HelpActivateSearch => help_nav::activate_search(&mut self.ui),
            AppCommand::HelpClearSearch => help_nav::clear_search(&mut self.ui),
            AppCommand::HelpSearchInput(c) => help_nav::search_input(&mut self.ui, c),
            AppCommand::HelpSearchBackspace => help_nav::search_backspace(&mut self.ui),
            AppCommand::HelpSearchConfirm => help_nav::search_confirm(&mut self.ui),

            // Dictionary navigation
            AppCommand::DictToggleFocus => dict_nav::toggle_focus(&mut self.ui),
            AppCommand::DictNextCategory => dict_nav::next_category(&mut self.ui),
            AppCommand::DictPrevCategory => dict_nav::prev_category(&mut self.ui),
            AppCommand::DictScrollDown(n) => dict_nav::scroll_down(&mut self.ui, n),
            AppCommand::DictScrollUp(n) => dict_nav::scroll_up(&mut self.ui, n),
            AppCommand::DictActivateSearch => dict_nav::activate_search(&mut self.ui),
            AppCommand::DictClearSearch => dict_nav::clear_search(&mut self.ui),
            AppCommand::DictSearchInput(c) => dict_nav::search_input(&mut self.ui, c),
            AppCommand::DictSearchBackspace => dict_nav::search_backspace(&mut self.ui),
            AppCommand::DictSearchConfirm => dict_nav::search_confirm(&mut self.ui),

            // Patterns view
            AppCommand::PatternsCursorLeft => self.patterns_nav.move_left(),
            AppCommand::PatternsCursorRight => self.patterns_nav.move_right(),
            AppCommand::PatternsCursorUp => self.patterns_nav.move_up(),
            AppCommand::PatternsCursorDown => self.patterns_nav.move_down(),
            AppCommand::PatternsEnter => {
                let bank = self.patterns_nav.selected_bank();
                let pattern = self.patterns_nav.selected_pattern();
                self.select_edit_bank(bank);
                self.select_edit_pattern(pattern);
                self.page.down();
                self.maybe_show_onboarding();
            }
            AppCommand::PatternsBack => {
                self.page.down();
                self.maybe_show_onboarding();
            }

            // Mute/Solo (staged)
            AppCommand::StageMute { bank, pattern } => self.playback.stage_mute(bank, pattern),
            AppCommand::StageSolo { bank, pattern } => self.playback.stage_solo(bank, pattern),
            AppCommand::ClearMutes => {
                self.playback.clear_staged_mutes();
                self.mute.clear_mute();
            }
            AppCommand::ClearSolos => {
                self.playback.clear_staged_solos();
                self.mute.clear_solo();
            }

            // UI state
            AppCommand::ClearMinimap => self.ui.dismiss_minimap(),
            AppCommand::HideTitle => {
                self.ui.show_title = false;
                self.maybe_show_onboarding();
            }
            AppCommand::ToggleEditorStack => {
                self.editor_ctx.show_stack = !self.editor_ctx.show_stack;
                if self.editor_ctx.show_stack {
                    crate::services::stack_preview::update_cache(&self.editor_ctx);
                }
            }
            AppCommand::SetColorScheme(scheme) => {
                self.ui.color_scheme = scheme;
                let palette = scheme.to_palette();
                let rotated = cagire_ratatui::theme::transform::rotate_palette(&palette, self.ui.hue_rotation);
                crate::theme::set(rotated);
                self.ui.invalidate_help_cache();
            }
            AppCommand::SetHueRotation(degrees) => {
                self.ui.hue_rotation = degrees;
                let palette = self.ui.color_scheme.to_palette();
                let rotated = cagire_ratatui::theme::transform::rotate_palette(&palette, degrees);
                crate::theme::set(rotated);
                self.ui.invalidate_help_cache();
            }
            AppCommand::ToggleRuntimeHighlight => {
                self.ui.runtime_highlight = !self.ui.runtime_highlight;
            }
            AppCommand::ToggleCompletion => {
                self.ui.show_completion = !self.ui.show_completion;
                self.editor_ctx
                    .editor
                    .set_completion_enabled(self.ui.show_completion);
            }
            AppCommand::SetFont(f) => self.ui.font = f,
            AppCommand::SetZoomFactor(z) => self.ui.zoom_factor = z,
            AppCommand::SetWindowSize(w, h) => {
                self.ui.window_width = w;
                self.ui.window_height = h;
            }
            AppCommand::ToggleLiveKeysFill => self.live_keys.flip_fill(),

            // Panel
            AppCommand::ClosePanel => {
                self.panel.visible = false;
                self.panel.focus = crate::state::PanelFocus::Main;
            }

            // Direct navigation (mouse)
            AppCommand::GoToStep(step) => {
                let len = self.current_edit_pattern().length;
                if step < len {
                    self.editor_ctx.step = step;
                    self.editor_ctx.clear_selection();
                    self.load_step_to_editor();
                }
            }
            AppCommand::PatternsSelectBank(bank) => {
                self.patterns_nav.bank_cursor = bank;
                self.patterns_nav.clear_selection();
            }
            AppCommand::PatternsSelectPattern(pattern) => {
                self.patterns_nav.pattern_cursor = pattern;
                self.patterns_nav.clear_selection();
            }
            AppCommand::HelpSelectTopic(i) => help_nav::select_topic(&mut self.ui, i),
            AppCommand::DictSelectCategory(i) => dict_nav::select_category(&mut self.ui, i),

            // Selection
            AppCommand::SetSelectionAnchor(step) => {
                self.editor_ctx.selection_anchor = Some(step);
            }

            // Audio settings (engine page)
            AppCommand::AudioSetSection(section) => self.audio.section = section,
            AppCommand::AudioNextSection => self.audio.next_section(self.plugin_mode),
            AppCommand::AudioPrevSection => self.audio.prev_section(self.plugin_mode),
            AppCommand::AudioOutputListUp => self.audio.output_list.move_up(),
            AppCommand::AudioOutputListDown(count) => self.audio.output_list.move_down(count),
            AppCommand::AudioOutputPageUp => self.audio.output_list.page_up(),
            AppCommand::AudioOutputPageDown(count) => self.audio.output_list.page_down(count),
            AppCommand::AudioInputListUp => self.audio.input_list.move_up(),
            AppCommand::AudioInputListDown(count) => self.audio.input_list.move_down(count),
            AppCommand::AudioInputPageDown(count) => self.audio.input_list.page_down(count),
            AppCommand::AudioSettingNext => self.audio.next_setting(self.plugin_mode),
            AppCommand::AudioSettingPrev => self.audio.prev_setting(self.plugin_mode),
            AppCommand::SetOutputDevice(name) => self.audio.config.output_device = Some(name),
            AppCommand::SetInputDevice(name) => self.audio.config.input_device = Some(name),
            AppCommand::SetDeviceKind(kind) => self.audio.device_kind = kind,
            AppCommand::AdjustAudioSetting { setting, delta } => {
                use crate::state::SettingKind;
                match setting {
                    SettingKind::Channels => self.audio.adjust_channels(delta as i16),
                    SettingKind::BufferSize => self.audio.adjust_buffer_size(delta),
                    SettingKind::Polyphony => self.audio.adjust_max_voices(delta),
                    SettingKind::Nudge => {
                        self.metrics.nudge_ms =
                            (self.metrics.nudge_ms + delta as f64).clamp(-50.0, 50.0);
                    }
                }
            }
            AppCommand::AudioTriggerRestart => self.audio.trigger_restart(),
            AppCommand::RemoveSamplePath(index) => self.audio.remove_sample_path(index),
            AppCommand::AudioRefreshDevices => self.audio.refresh_devices(),

            // Options page
            AppCommand::OptionsNextFocus => self.options.next_focus(self.plugin_mode),
            AppCommand::OptionsPrevFocus => self.options.prev_focus(self.plugin_mode),
            AppCommand::OptionsSetFocus(focus) => self.options.focus = focus,
            AppCommand::ToggleRefreshRate => self.audio.toggle_refresh_rate(),
            AppCommand::ToggleScope => self.audio.config.show_scope = !self.audio.config.show_scope,
            AppCommand::ToggleSpectrum => self.audio.config.show_spectrum = !self.audio.config.show_spectrum,
            AppCommand::ToggleLissajous => self.audio.config.show_lissajous = !self.audio.config.show_lissajous,
            AppCommand::CycleScopeMode => self.audio.config.scope_mode = self.audio.config.scope_mode.toggle(),
            AppCommand::FlipScopeOrientation => self.audio.config.scope_vertical = !self.audio.config.scope_vertical,
            AppCommand::ToggleLissajousTrails => self.audio.config.lissajous_trails = !self.audio.config.lissajous_trails,
            AppCommand::CycleSpectrumMode => self.audio.config.spectrum_mode = self.audio.config.spectrum_mode.cycle(),
            AppCommand::ToggleSpectrumPeaks => self.audio.config.spectrum_peaks = !self.audio.config.spectrum_peaks,
            AppCommand::TogglePreview => self.audio.config.show_preview = !self.audio.config.show_preview,
            AppCommand::SetGainBoost(g) => self.audio.config.gain_boost = g,
            AppCommand::ToggleNormalizeViz => self.audio.config.normalize_viz = !self.audio.config.normalize_viz,
            AppCommand::TogglePerformanceMode => self.ui.performance_mode = !self.ui.performance_mode,

            // Metrics
            AppCommand::ResetPeakVoices => self.metrics.peak_voices = 0,

            // Euclidean distribution
            AppCommand::ApplyEuclideanDistribution {
                bank,
                pattern,
                source_step,
                pulses,
                steps,
                rotation,
            } => {
                let targets = euclidean::apply_distribution(
                    &mut self.project_state.project,
                    bank,
                    pattern,
                    source_step,
                    pulses,
                    steps,
                    rotation,
                );
                let created_count = targets.len();
                self.project_state.mark_dirty(bank, pattern);
                for &target in &targets {
                    let saved = self.editor_ctx.step;
                    self.editor_ctx.step = target;
                    self.compile_current_step(link);
                    self.editor_ctx.step = saved;
                }
                self.load_step_to_editor();
                self.ui.flash(
                    &format!("Created {created_count} linked steps (E({pulses},{steps}))"),
                    200,
                    FlashKind::Success,
                );
            }

            // Onboarding
            AppCommand::DismissOnboarding => {
                let name = self.page.name().to_string();
                if !self.ui.onboarding_dismissed.contains(&name) {
                    self.ui.onboarding_dismissed.push(name);
                }
            }
            AppCommand::ResetOnboarding => self.ui.onboarding_dismissed.clear(),
            AppCommand::GoToHelpTopic(topic) => {
                self.ui.modal = Modal::None;
                self.page = crate::page::Page::Help;
                self.ui.help_topic = topic;
            }

            // Prelude
            AppCommand::OpenPreludeEditor => self.open_prelude_editor(),
            AppCommand::SavePrelude => self.save_prelude(),
            AppCommand::EvaluatePrelude => self.evaluate_prelude(link),
            AppCommand::ClosePreludeEditor => self.close_prelude_editor(),

            // Periodic script
            AppCommand::OpenScriptModal(field) => self.open_script_modal(field),
            AppCommand::SetScriptSpeed(speed) => {
                self.project_state.project.script_speed = speed;
                self.script_editor.dirty = true;
            }
            AppCommand::SetScriptLength(len) => {
                self.project_state.project.script_length = len.clamp(1, 256);
                self.script_editor.dirty = true;
            }
            AppCommand::ScriptSave => self.save_script_from_editor(),
            AppCommand::ScriptEvaluate => self.evaluate_script_page(link),
            AppCommand::ToggleScriptStack => {
                self.script_editor.show_stack = !self.script_editor.show_stack;
            }
        }
    }

    fn auto_save_script_on_leave(&mut self) {
        if self.page == Page::Script {
            self.save_script_from_editor();
            self.script_editor.focused = false;
        }
    }

    fn auto_load_script_on_arrive(&mut self) {
        if self.page == Page::Script {
            self.load_script_to_editor();
        }
    }
}
