use doux::audio::AudioDeviceInfo;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

use super::CyclicEnum;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum MainLayout {
    #[default]
    Top,
    Bottom,
    Left,
    Right,
}

impl CyclicEnum for MainLayout {
    const VARIANTS: &'static [Self] = &[Self::Top, Self::Bottom, Self::Left, Self::Right];
}

#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum ScopeMode {
    #[default]
    Line,
    Filled,
}

impl ScopeMode {
    pub fn toggle(self) -> Self {
        match self {
            Self::Line => Self::Filled,
            Self::Filled => Self::Line,
        }
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum SpectrumMode {
    #[default]
    Bars,
    Line,
    Filled,
}

impl SpectrumMode {
    pub fn cycle(self) -> Self {
        match self {
            Self::Bars => Self::Line,
            Self::Line => Self::Filled,
            Self::Filled => Self::Bars,
        }
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum RefreshRate {
    #[default]
    Fps60,
    Fps30,
    Fps15,
}

impl RefreshRate {
    pub fn from_fps(fps: u32) -> Self {
        if fps >= 60 {
            RefreshRate::Fps60
        } else if fps >= 30 {
            RefreshRate::Fps30
        } else {
            RefreshRate::Fps15
        }
    }

    pub fn toggle(self) -> Self {
        match self {
            RefreshRate::Fps60 => RefreshRate::Fps30,
            RefreshRate::Fps30 => RefreshRate::Fps15,
            RefreshRate::Fps15 => RefreshRate::Fps60,
        }
    }

    pub fn millis(self) -> u64 {
        match self {
            RefreshRate::Fps60 => 16,
            RefreshRate::Fps30 => 33,
            RefreshRate::Fps15 => 66,
        }
    }

    pub fn label(self) -> &'static str {
        match self {
            RefreshRate::Fps60 => "60",
            RefreshRate::Fps30 => "30",
            RefreshRate::Fps15 => "15",
        }
    }

    pub fn to_fps(self) -> u32 {
        match self {
            RefreshRate::Fps60 => 60,
            RefreshRate::Fps30 => 30,
            RefreshRate::Fps15 => 15,
        }
    }
}

#[derive(Clone)]
pub struct AudioConfig {
    pub output_device: Option<String>,
    pub input_device: Option<String>,
    pub channels: u16,
    pub buffer_size: u32,
    pub max_voices: usize,
    pub sample_rate: f32,
    pub host_name: String,
    pub sample_paths: Vec<PathBuf>,
    pub sample_counts: Vec<usize>,
    pub refresh_rate: RefreshRate,
    pub show_scope: bool,
    pub show_spectrum: bool,
    pub show_lissajous: bool,
    pub show_preview: bool,
    pub gain_boost: f32,
    pub normalize_viz: bool,
    pub layout: MainLayout,
    pub scope_mode: ScopeMode,
    pub scope_vertical: bool,
    pub lissajous_trails: bool,
    pub spectrum_mode: SpectrumMode,
    pub spectrum_peaks: bool,
}

impl Default for AudioConfig {
    fn default() -> Self {
        Self {
            output_device: None,
            input_device: None,
            channels: 2,
            buffer_size: 512,
            max_voices: 32,
            sample_rate: 44100.0,
            host_name: String::new(),
            sample_paths: Vec::new(),
            sample_counts: Vec::new(),
            refresh_rate: RefreshRate::default(),
            show_scope: true,
            show_spectrum: true,
            show_lissajous: true,
            show_preview: true,
            gain_boost: 1.0,
            normalize_viz: false,
            layout: MainLayout::default(),
            scope_mode: ScopeMode::default(),
            scope_vertical: false,
            lissajous_trails: false,
            spectrum_mode: SpectrumMode::default(),
            spectrum_peaks: false,
        }
    }
}

pub struct ListSelectState {
    pub cursor: usize,
    pub scroll_offset: usize,
}

impl ListSelectState {
    pub fn move_up(&mut self) {
        if self.cursor > 0 {
            self.cursor -= 1;
            if self.cursor < self.scroll_offset {
                self.scroll_offset = self.cursor;
            }
        }
    }

    pub fn move_down(&mut self, item_count: usize) {
        if self.cursor + 1 < item_count {
            self.cursor += 1;
            if self.cursor >= self.scroll_offset + 5 {
                self.scroll_offset = self.cursor - 4;
            }
        }
    }

    pub fn page_up(&mut self) {
        self.cursor = self.cursor.saturating_sub(5);
        if self.cursor < self.scroll_offset {
            self.scroll_offset = self.cursor;
        }
    }

    pub fn page_down(&mut self, item_count: usize) {
        self.cursor = (self.cursor + 5).min(item_count.saturating_sub(1));
        if self.cursor >= self.scroll_offset + 5 {
            self.scroll_offset = self.cursor.saturating_sub(4);
        }
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum EngineSection {
    #[default]
    Devices,
    Settings,
    Samples,
}

impl CyclicEnum for EngineSection {
    const VARIANTS: &'static [Self] = &[Self::Devices, Self::Settings, Self::Samples];
}

#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum DeviceKind {
    #[default]
    Output,
    Input,
}

#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum SettingKind {
    #[default]
    Channels,
    BufferSize,
    Polyphony,
    Nudge,
}

impl CyclicEnum for SettingKind {
    const VARIANTS: &'static [Self] = &[
        Self::Channels,
        Self::BufferSize,
        Self::Polyphony,
        Self::Nudge,
    ];
}

pub struct Metrics {
    pub event_count: usize,
    pub active_voices: usize,
    pub peak_voices: usize,
    pub cpu_load: f32,
    pub schedule_depth: usize,
    pub scope: [f32; 256],
    pub scope_right: [f32; 256],
    pub peak_left: f32,
    pub peak_right: f32,
    pub spectrum: [f32; 32],
    pub nudge_ms: f64,
}

impl Default for Metrics {
    fn default() -> Self {
        Self {
            event_count: 0,
            active_voices: 0,
            peak_voices: 0,
            cpu_load: 0.0,
            schedule_depth: 0,
            scope: [0.0; 256],
            scope_right: [0.0; 256],
            peak_left: 0.0,
            peak_right: 0.0,
            spectrum: [0.0; 32],
            nudge_ms: 0.0,
        }
    }
}

pub struct AudioSettings {
    pub config: AudioConfig,
    pub section: EngineSection,
    pub device_kind: DeviceKind,
    pub setting_kind: SettingKind,
    pub output_devices: Vec<AudioDeviceInfo>,
    pub input_devices: Vec<AudioDeviceInfo>,
    pub output_list: ListSelectState,
    pub input_list: ListSelectState,
    pub sample_list: ListSelectState,
    pub restart_pending: bool,
    pub error: Option<String>,
    pub sample_registry: Option<std::sync::Arc<doux::SampleRegistry>>,
}

impl Default for AudioSettings {
    fn default() -> Self {
        Self {
            config: AudioConfig::default(),
            section: EngineSection::default(),
            device_kind: DeviceKind::default(),
            setting_kind: SettingKind::default(),
            output_devices: doux::audio::list_output_devices(),
            input_devices: doux::audio::list_input_devices(),
            output_list: ListSelectState {
                cursor: 0,
                scroll_offset: 0,
            },
            input_list: ListSelectState {
                cursor: 0,
                scroll_offset: 0,
            },
            sample_list: ListSelectState {
                cursor: 0,
                scroll_offset: 0,
            },
            restart_pending: false,
            error: None,
            sample_registry: None,
        }
    }
}

impl AudioSettings {
    pub fn new_plugin() -> Self {
        Self {
            config: AudioConfig::default(),
            section: EngineSection::default(),
            device_kind: DeviceKind::default(),
            setting_kind: SettingKind::default(),
            output_devices: Vec::new(),
            input_devices: Vec::new(),
            output_list: ListSelectState {
                cursor: 0,
                scroll_offset: 0,
            },
            input_list: ListSelectState {
                cursor: 0,
                scroll_offset: 0,
            },
            sample_list: ListSelectState {
                cursor: 0,
                scroll_offset: 0,
            },
            restart_pending: false,
            error: None,
            sample_registry: None,
        }
    }

    pub fn refresh_devices(&mut self) {
        self.output_devices = doux::audio::list_output_devices();
        self.input_devices = doux::audio::list_input_devices();
    }

    pub fn next_section(&mut self, plugin_mode: bool) {
        self.section = if plugin_mode {
            match self.section {
                EngineSection::Settings => EngineSection::Samples,
                EngineSection::Samples => EngineSection::Settings,
                EngineSection::Devices => EngineSection::Settings,
            }
        } else {
            self.section.next()
        };
    }

    pub fn prev_section(&mut self, plugin_mode: bool) {
        self.section = if plugin_mode {
            match self.section {
                EngineSection::Settings => EngineSection::Samples,
                EngineSection::Samples => EngineSection::Settings,
                EngineSection::Devices => EngineSection::Settings,
            }
        } else {
            self.section.prev()
        };
    }

    pub fn next_setting(&mut self, plugin_mode: bool) {
        self.setting_kind = if plugin_mode {
            match self.setting_kind {
                SettingKind::Polyphony => SettingKind::Nudge,
                SettingKind::Nudge => SettingKind::Polyphony,
                _ => SettingKind::Polyphony,
            }
        } else {
            self.setting_kind.next()
        };
    }

    pub fn prev_setting(&mut self, plugin_mode: bool) {
        self.setting_kind = if plugin_mode {
            match self.setting_kind {
                SettingKind::Polyphony => SettingKind::Nudge,
                SettingKind::Nudge => SettingKind::Polyphony,
                _ => SettingKind::Polyphony,
            }
        } else {
            self.setting_kind.prev()
        };
    }

    pub fn current_output_device_index(&self) -> usize {
        match &self.config.output_device {
            Some(name) => self
                .output_devices
                .iter()
                .position(|d| &d.name == name)
                .unwrap_or(0),
            None => self
                .output_devices
                .iter()
                .position(|d| d.is_default)
                .unwrap_or(0),
        }
    }

    pub fn current_input_device_index(&self) -> usize {
        match &self.config.input_device {
            Some(name) => self
                .input_devices
                .iter()
                .position(|d| &d.name == name)
                .unwrap_or(0),
            None => self
                .input_devices
                .iter()
                .position(|d| d.is_default)
                .unwrap_or(0),
        }
    }

    pub fn adjust_channels(&mut self, delta: i16) {
        let new_val = (self.config.channels as i16 + delta).clamp(1, 64) as u16;
        self.config.channels = new_val;
    }

    pub fn adjust_buffer_size(&mut self, delta: i32) {
        let new_val = (self.config.buffer_size as i32 + delta).clamp(64, 4096) as u32;
        self.config.buffer_size = new_val;
    }

    pub fn adjust_max_voices(&mut self, delta: i32) {
        let new_val = (self.config.max_voices as i32 + delta).clamp(1, 128) as usize;
        self.config.max_voices = new_val;
    }

    pub fn toggle_refresh_rate(&mut self) {
        self.config.refresh_rate = self.config.refresh_rate.toggle();
    }

    pub fn total_sample_count(&self) -> usize {
        self.config.sample_counts.iter().sum()
    }

    pub fn add_sample_path(&mut self, path: PathBuf, count: usize) {
        if !self.config.sample_paths.contains(&path) {
            self.config.sample_paths.push(path);
            self.config.sample_counts.push(count);
        }
    }

    pub fn remove_sample_path(&mut self, index: usize) {
        if index < self.config.sample_paths.len() {
            self.config.sample_paths.remove(index);
            self.config.sample_counts.remove(index);
            let len = self.config.sample_paths.len();
            if len == 0 {
                self.sample_list.cursor = 0;
                self.sample_list.scroll_offset = 0;
            } else if self.sample_list.cursor >= len {
                self.sample_list.cursor = len - 1;
            }
        }
    }

    pub fn trigger_restart(&mut self) {
        self.restart_pending = true;
    }
}
