use std::path::PathBuf;

use bone_render::PickAperture;
use serde::{Deserialize, Serialize};

use crate::hotkeys::HotkeyOverrides;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Settings {
    pub pick_aperture: PickAperture,
    pub hotkey_overrides: HotkeyOverrides,
}

impl Default for Settings {
    fn default() -> Self {
        Self {
            pick_aperture: PickAperture::DEFAULT,
            hotkey_overrides: HotkeyOverrides::default(),
        }
    }
}

#[derive(Serialize, Deserialize, Default)]
struct SettingsOnDisk {
    aperture_px: u32,
    hotkey_overrides: HotkeyOverrides,
}

impl From<&Settings> for SettingsOnDisk {
    fn from(value: &Settings) -> Self {
        Self {
            aperture_px: value.pick_aperture.radius_px(),
            hotkey_overrides: value.hotkey_overrides.clone(),
        }
    }
}

impl SettingsOnDisk {
    fn into_settings(self) -> Settings {
        Settings {
            pick_aperture: PickAperture::new(self.aperture_px),
            hotkey_overrides: self.hotkey_overrides,
        }
    }
}

#[must_use]
pub fn settings_path() -> Option<PathBuf> {
    let base = config_base_dir()?;
    Some(base.join("bone").join("settings.ron"))
}

#[cfg(target_os = "windows")]
fn config_base_dir() -> Option<PathBuf> {
    std::env::var_os("APPDATA").map(PathBuf::from).or_else(|| {
        std::env::var_os("USERPROFILE").map(|p| PathBuf::from(p).join("AppData").join("Roaming"))
    })
}

#[cfg(not(target_os = "windows"))]
fn config_base_dir() -> Option<PathBuf> {
    std::env::var_os("XDG_CONFIG_HOME")
        .map(PathBuf::from)
        .or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".config")))
}

#[must_use]
pub fn load() -> Settings {
    let Some(path) = settings_path() else {
        return Settings::default();
    };
    let Ok(text) = std::fs::read_to_string(&path) else {
        return Settings::default();
    };
    match ron::de::from_str::<SettingsOnDisk>(&text) {
        Ok(on_disk) => on_disk.into_settings(),
        Err(e) => {
            tracing::warn!(error = %e, path = %path.display(), "settings load");
            Settings::default()
        }
    }
}

pub fn save(settings: &Settings) {
    let Some(path) = settings_path() else { return };
    if let Some(parent) = path.parent()
        && let Err(e) = std::fs::create_dir_all(parent)
    {
        tracing::warn!(error = %e, path = %parent.display(), "settings dir create");
        return;
    }
    let on_disk = SettingsOnDisk::from(settings);
    let pretty = ron::ser::PrettyConfig::default();
    match ron::ser::to_string_pretty(&on_disk, pretty) {
        Ok(text) => {
            if let Err(e) = std::fs::write(&path, text) {
                tracing::warn!(error = %e, path = %path.display(), "settings save");
            }
        }
        Err(e) => tracing::warn!(error = %e, "settings serialize"),
    }
}
