use std::path::PathBuf;

use serde::{Deserialize, Serialize};

use crate::state::{ColorScheme, MainLayout};

#[cfg(feature = "cli")]
const APP_NAME: &str = "cagire";

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct MidiSettings {
    #[serde(default)]
    pub output_devices: Vec<String>,
    #[serde(default)]
    pub input_devices: Vec<String>,
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Settings {
    pub audio: AudioSettings,
    pub display: DisplaySettings,
    pub link: LinkSettings,
    #[serde(default)]
    pub midi: MidiSettings,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AudioSettings {
    pub output_device: Option<String>,
    pub input_device: Option<String>,
    pub channels: u16,
    pub buffer_size: u32,
    #[serde(default = "default_max_voices")]
    pub max_voices: usize,
    #[serde(default)]
    pub sample_paths: Vec<PathBuf>,
}

fn default_max_voices() -> usize { 32 }

#[derive(Debug, Serialize, Deserialize)]
pub struct DisplaySettings {
    pub fps: u32,
    pub runtime_highlight: bool,
    pub show_scope: bool,
    pub show_spectrum: bool,
    #[serde(default = "default_true")]
    pub show_lissajous: bool,
    #[serde(default = "default_true")]
    pub show_preview: bool,
    #[serde(default = "default_true")]
    pub show_completion: bool,
    #[serde(default = "default_font")]
    pub font: String,
    #[serde(default = "default_zoom")]
    pub zoom_factor: f32,
    #[serde(default)]
    pub color_scheme: ColorScheme,
    #[serde(default)]
    pub layout: MainLayout,
    #[serde(default)]
    pub performance_mode: bool,
    #[serde(default)]
    pub hue_rotation: f32,
    #[serde(default)]
    pub onboarding_dismissed: Vec<String>,
    #[serde(default = "default_true")]
    pub load_demo_on_startup: bool,
    #[serde(default)]
    pub demo_index: usize,
    #[serde(default = "default_gain_boost")]
    pub gain_boost: f32,
    #[serde(default)]
    pub normalize_viz: bool,
}

fn default_font() -> String {
    "8x13".to_string()
}

fn default_zoom() -> f32 {
    1.5
}

fn default_gain_boost() -> f32 {
    1.0
}

#[derive(Debug, Serialize, Deserialize)]
pub struct LinkSettings {
    pub enabled: bool,
    pub tempo: f64,
    pub quantum: f64,
}

impl Default for AudioSettings {
    fn default() -> Self {
        Self {
            output_device: None,
            input_device: None,
            channels: 2,
            buffer_size: 512,
            max_voices: 32,
            sample_paths: Vec::new(),
        }
    }
}

fn default_true() -> bool { true }

impl Default for DisplaySettings {
    fn default() -> Self {
        Self {
            fps: 60,
            runtime_highlight: false,
            show_scope: true,
            show_spectrum: true,
            show_lissajous: true,
            show_preview: true,
            show_completion: true,
            font: default_font(),
            zoom_factor: default_zoom(),
            performance_mode: false,
            color_scheme: ColorScheme::default(),
            layout: MainLayout::default(),
            hue_rotation: 0.0,
            onboarding_dismissed: Vec::new(),
            load_demo_on_startup: true,
            demo_index: 0,
            gain_boost: 1.0,
            normalize_viz: false,
        }
    }
}

impl Default for LinkSettings {
    fn default() -> Self {
        Self {
            enabled: true,
            tempo: 120.0,
            quantum: 4.0,
        }
    }
}

impl Settings {
    #[cfg(feature = "cli")]
    pub fn load() -> Self {
        let mut settings: Self = confy::load(APP_NAME, None).unwrap_or_default();
        if settings.audio.channels == 0 {
            settings.audio.channels = AudioSettings::default().channels;
        }
        if settings.audio.buffer_size == 0 {
            settings.audio.buffer_size = AudioSettings::default().buffer_size;
        }
        settings
    }

    #[cfg(not(feature = "cli"))]
    pub fn load() -> Self {
        Self::default()
    }

    #[cfg(feature = "cli")]
    pub fn save(&self) {
        if let Err(e) = confy::store(APP_NAME, None, self) {
            eprintln!("Failed to save settings: {e}");
        }
    }

    #[cfg(not(feature = "cli"))]
    pub fn save(&self) {}
}

