use crossterm::event::{KeyCode, KeyEvent};
use std::sync::atomic::Ordering;

use super::{InputContext, InputResult};
use crate::commands::AppCommand;
use crate::state::{ConfirmAction, Modal, OptionsFocus};

pub(crate) fn cycle_option_value(ctx: &mut InputContext, right: bool) {
    match ctx.app.options.focus {
        OptionsFocus::ColorScheme => {
            let new_scheme = if right {
                ctx.app.ui.color_scheme.next()
            } else {
                ctx.app.ui.color_scheme.prev()
            };
            ctx.dispatch(AppCommand::SetColorScheme(new_scheme));
        }
        OptionsFocus::HueRotation => {
            let delta = if right { 5.0 } else { -5.0 };
            let new_rotation = (ctx.app.ui.hue_rotation + delta).rem_euclid(360.0);
            ctx.dispatch(AppCommand::SetHueRotation(new_rotation));
        }
        OptionsFocus::RefreshRate => ctx.dispatch(AppCommand::ToggleRefreshRate),
        OptionsFocus::RuntimeHighlight => ctx.dispatch(AppCommand::ToggleRuntimeHighlight),
        OptionsFocus::ShowScope => ctx.dispatch(AppCommand::ToggleScope),
        OptionsFocus::ShowSpectrum => ctx.dispatch(AppCommand::ToggleSpectrum),
        OptionsFocus::ShowLissajous => ctx.dispatch(AppCommand::ToggleLissajous),
        OptionsFocus::GainBoost => {
            const GAINS: &[f32] = &[1.0, 2.0, 4.0, 8.0, 16.0];
            let pos = GAINS.iter().position(|g| (*g - ctx.app.audio.config.gain_boost).abs() < 0.01).unwrap_or(0);
            let new_pos = if right {
                (pos + 1) % GAINS.len()
            } else {
                (pos + GAINS.len() - 1) % GAINS.len()
            };
            ctx.dispatch(AppCommand::SetGainBoost(GAINS[new_pos]));
        }
        OptionsFocus::NormalizeViz => ctx.dispatch(AppCommand::ToggleNormalizeViz),
        OptionsFocus::ShowCompletion => ctx.dispatch(AppCommand::ToggleCompletion),
        OptionsFocus::ShowPreview => ctx.dispatch(AppCommand::TogglePreview),
        OptionsFocus::PerformanceMode => ctx.dispatch(AppCommand::TogglePerformanceMode),
        OptionsFocus::Font => {
            const FONTS: &[&str] = &["6x13", "7x13", "8x13", "9x15", "9x18", "10x20"];
            let pos = FONTS.iter().position(|f| *f == ctx.app.ui.font).unwrap_or(2);
            let new_pos = if right {
                (pos + 1) % FONTS.len()
            } else {
                (pos + FONTS.len() - 1) % FONTS.len()
            };
            ctx.dispatch(AppCommand::SetFont(FONTS[new_pos].to_string()));
        }
        OptionsFocus::ZoomFactor => {
            const ZOOMS: &[f32] = &[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0];
            let pos = ZOOMS
                .iter()
                .position(|z| (z - ctx.app.ui.zoom_factor).abs() < 0.01)
                .unwrap_or(4);
            let new_pos = if right {
                (pos + 1) % ZOOMS.len()
            } else {
                (pos + ZOOMS.len() - 1) % ZOOMS.len()
            };
            ctx.dispatch(AppCommand::SetZoomFactor(ZOOMS[new_pos]));
        }
        OptionsFocus::WindowSize => {
            const WINDOW_SIZES: &[(u32, u32)] = &[
                (900, 600), (1050, 700), (1200, 800), (1350, 900), (1500, 1000),
            ];
            let pos = WINDOW_SIZES
                .iter()
                .position(|&(w, h)| w == ctx.app.ui.window_width && h == ctx.app.ui.window_height)
                .unwrap_or_else(|| {
                    WINDOW_SIZES
                        .iter()
                        .enumerate()
                        .min_by_key(|&(_, &(w, h))| {
                            let dw = w as i64 - ctx.app.ui.window_width as i64;
                            let dh = h as i64 - ctx.app.ui.window_height as i64;
                            dw * dw + dh * dh
                        })
                        .map(|(i, _)| i)
                        .unwrap_or(2)
                });
            let new_pos = if right {
                (pos + 1) % WINDOW_SIZES.len()
            } else {
                (pos + WINDOW_SIZES.len() - 1) % WINDOW_SIZES.len()
            };
            let (w, h) = WINDOW_SIZES[new_pos];
            ctx.dispatch(AppCommand::SetWindowSize(w, h));
        }
        OptionsFocus::LinkEnabled => ctx.link.set_enabled(!ctx.link.is_enabled()),
        OptionsFocus::StartStopSync => ctx
            .link
            .set_start_stop_sync_enabled(!ctx.link.is_start_stop_sync_enabled()),
        OptionsFocus::Quantum => {
            let delta = if right { 1.0 } else { -1.0 };
            ctx.link.set_quantum(ctx.link.quantum() + delta);
        }
        OptionsFocus::MidiOutput0
        | OptionsFocus::MidiOutput1
        | OptionsFocus::MidiOutput2
        | OptionsFocus::MidiOutput3 => {
            let slot = match ctx.app.options.focus {
                OptionsFocus::MidiOutput0 => 0,
                OptionsFocus::MidiOutput1 => 1,
                OptionsFocus::MidiOutput2 => 2,
                OptionsFocus::MidiOutput3 => 3,
                _ => 0,
            };
            let all_devices = crate::midi::list_midi_outputs();
            let available: Vec<(usize, &crate::midi::MidiDeviceInfo)> = all_devices
                .iter()
                .enumerate()
                .filter(|(idx, _)| {
                    ctx.app.midi.selected_outputs[slot] == Some(*idx)
                        || !ctx
                            .app
                            .midi
                            .selected_outputs
                            .iter()
                            .enumerate()
                            .any(|(s, sel)| s != slot && *sel == Some(*idx))
                })
                .collect();
            let total_options = available.len() + 1;
            let current_pos = ctx.app.midi.selected_outputs[slot]
                .and_then(|idx| available.iter().position(|(i, _)| *i == idx))
                .map(|p| p + 1)
                .unwrap_or(0);
            let new_pos = if right {
                (current_pos + 1) % total_options
            } else if current_pos == 0 {
                total_options - 1
            } else {
                current_pos - 1
            };
            if new_pos == 0 {
                ctx.app.midi.disconnect_output(slot);
                ctx.dispatch(AppCommand::SetStatus(format!(
                    "MIDI output {slot}: disconnected"
                )));
            } else {
                let (device_idx, device) = available[new_pos - 1];
                if ctx.app.midi.connect_output(slot, device_idx).is_ok() {
                    ctx.dispatch(AppCommand::SetStatus(format!(
                        "MIDI output {}: {}",
                        slot, device.name
                    )));
                }
            }
        }
        OptionsFocus::ResetOnboarding => {
            ctx.dispatch(AppCommand::ResetOnboarding);
        }
        OptionsFocus::LoadDemoOnStartup => {
            ctx.app.ui.load_demo_on_startup = !ctx.app.ui.load_demo_on_startup;
        }
        OptionsFocus::MidiInput0
        | OptionsFocus::MidiInput1
        | OptionsFocus::MidiInput2
        | OptionsFocus::MidiInput3 => {
            let slot = match ctx.app.options.focus {
                OptionsFocus::MidiInput0 => 0,
                OptionsFocus::MidiInput1 => 1,
                OptionsFocus::MidiInput2 => 2,
                OptionsFocus::MidiInput3 => 3,
                _ => 0,
            };
            let all_devices = crate::midi::list_midi_inputs();
            let available: Vec<(usize, &crate::midi::MidiDeviceInfo)> = all_devices
                .iter()
                .enumerate()
                .filter(|(idx, _)| {
                    ctx.app.midi.selected_inputs[slot] == Some(*idx)
                        || !ctx
                            .app
                            .midi
                            .selected_inputs
                            .iter()
                            .enumerate()
                            .any(|(s, sel)| s != slot && *sel == Some(*idx))
                })
                .collect();
            let total_options = available.len() + 1;
            let current_pos = ctx.app.midi.selected_inputs[slot]
                .and_then(|idx| available.iter().position(|(i, _)| *i == idx))
                .map(|p| p + 1)
                .unwrap_or(0);
            let new_pos = if right {
                (current_pos + 1) % total_options
            } else if current_pos == 0 {
                total_options - 1
            } else {
                current_pos - 1
            };
            if new_pos == 0 {
                ctx.app.midi.disconnect_input(slot);
                ctx.dispatch(AppCommand::SetStatus(format!(
                    "MIDI input {slot}: disconnected"
                )));
            } else {
                let (device_idx, device) = available[new_pos - 1];
                if ctx.app.midi.connect_input(slot, device_idx).is_ok() {
                    ctx.dispatch(AppCommand::SetStatus(format!(
                        "MIDI input {}: {}",
                        slot, device.name
                    )));
                }
            }
        }
    }
    ctx.app.save_settings(ctx.link);
}

pub(super) fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
    match key.code {
        KeyCode::Char('q') if !ctx.app.plugin_mode => {
            ctx.dispatch(AppCommand::OpenModal(Modal::Confirm {
                action: ConfirmAction::Quit,
                selected: false,
            }));
        }
        KeyCode::Down | KeyCode::Tab => ctx.dispatch(AppCommand::OptionsNextFocus),
        KeyCode::Up | KeyCode::BackTab => ctx.dispatch(AppCommand::OptionsPrevFocus),
        KeyCode::Left | KeyCode::Right => {
            cycle_option_value(ctx, key.code == KeyCode::Right);
        }
        KeyCode::Char(' ') if !ctx.app.plugin_mode => {
            ctx.dispatch(AppCommand::TogglePlaying);
            ctx.playing
                .store(ctx.app.playback.playing, Ordering::Relaxed);
        }
        KeyCode::Char('s') => super::open_save(ctx),
        KeyCode::Char('l') => super::open_load(ctx),
        KeyCode::Char('?') => {
            ctx.dispatch(AppCommand::OpenModal(Modal::KeybindingsHelp { scroll: 0 }));
        }
        _ => {}
    }
    InputResult::Continue
}
