use std::fmt;
use std::path::PathBuf;

/// Type of audio plugin
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PluginType {
    Vst2,
    Vst3,
    Clap,
}

impl fmt::Display for PluginType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            PluginType::Vst2 => write!(f, "VST2"),
            PluginType::Vst3 => write!(f, "VST3"),
            PluginType::Clap => write!(f, "CLAP"),
        }
    }
}

/// Architecture of the plugin binary
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Architecture {
    Win64,
    Win32,
    Unknown,
}

impl fmt::Display for Architecture {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Architecture::Win64 => write!(f, "64-bit"),
            Architecture::Win32 => write!(f, "32-bit"),
            Architecture::Unknown => write!(f, "???"),
        }
    }
}

/// Sync status of a plugin bridge
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SyncStatus {
    Synced,
    NotSynced,
    Symlink,
    Invalid,
    Unknown,
}

impl fmt::Display for SyncStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            SyncStatus::Synced => write!(f, "synced"),
            SyncStatus::NotSynced => write!(f, "not synced"),
            SyncStatus::Symlink => write!(f, "symlink"),
            SyncStatus::Invalid => write!(f, "invalid"),
            SyncStatus::Unknown => write!(f, "unknown"),
        }
    }
}

impl SyncStatus {
    pub fn symbol(&self) -> &str {
        match self {
            SyncStatus::Synced => "✓",
            SyncStatus::NotSynced => "✗",
            SyncStatus::Symlink => "~",
            SyncStatus::Invalid => "!",
            SyncStatus::Unknown => "?",
        }
    }
}

/// A single discovered plugin
#[derive(Debug, Clone)]
pub struct Plugin {
    /// Display name of the plugin
    pub name: String,
    /// Full path to the Windows plugin file
    pub path: PathBuf,
    /// Plugin format
    pub plugin_type: PluginType,
    /// Architecture
    pub architecture: Architecture,
    /// Current bridge sync status
    pub sync_status: SyncStatus,
    /// Which managed directory this plugin belongs to
    pub parent_dir: PathBuf,
}

/// VST2 location preference
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Vst2Location {
    Centralized,
    Inline,
}

impl fmt::Display for Vst2Location {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Vst2Location::Centralized => write!(f, "centralized"),
            Vst2Location::Inline => write!(f, "inline"),
        }
    }
}

/// Parsed yabridgectl configuration
#[derive(Debug, Clone)]
pub struct YabridgeConfig {
    /// Path to yabridge libraries (None = auto-detect)
    pub yabridge_home: Option<PathBuf>,
    /// Registered plugin directories
    pub plugin_dirs: Vec<PathBuf>,
    /// VST2 setup mode
    pub vst2_location: Vst2Location,
    /// Skip verification after sync
    pub no_verify: bool,
    /// Blacklisted paths
    pub blacklist: Vec<PathBuf>,
}

impl Default for YabridgeConfig {
    fn default() -> Self {
        Self {
            yabridge_home: None,
            plugin_dirs: Vec::new(),
            vst2_location: Vst2Location::Centralized,
            no_verify: false,
            blacklist: Vec::new(),
        }
    }
}

/// Known option keys that we parse and manage
pub const KNOWN_KEYS: &[&str] = &[
    "group",
    "disable_pipes",
    "editor_coordinate_hack",
    "editor_disable_host_scaling",
    "editor_force_dnd",
    "editor_xembed",
    "frame_rate",
    "hide_daw",
    "vst3_prefer_32bit",
];

/// Options for a plugin group in yabridge.toml
#[derive(Debug, Clone, Default)]
pub struct PluginOptions {
    /// Glob pattern this applies to
    pub pattern: String,
    /// Plugin group name
    pub group: Option<String>,
    /// Disable pipes (redirect Wine output)
    pub disable_pipes: Option<bool>,
    /// Fix absolute screen coordinate issues
    pub editor_coordinate_hack: Option<bool>,
    /// Disable host-driven HiDPI scaling
    pub editor_disable_host_scaling: Option<bool>,
    /// Force drag and drop (REAPER workaround)
    pub editor_force_dnd: Option<bool>,
    /// Use Wine's XEmbed instead of yabridge embedding
    pub editor_xembed: Option<bool>,
    /// GUI refresh rate / Win32 event handling rate
    pub frame_rate: Option<u32>,
    /// Don't report DAW name to plugin
    pub hide_daw: Option<bool>,
    /// Prefer 32-bit VST3 version when both exist
    pub vst3_prefer_32bit: Option<bool>,
    /// Unknown keys preserved for round-trip fidelity
    pub extra_keys: Vec<(String, toml::Value)>,
}

impl PluginOptions {
    /// Human-readable name for each option field
    pub fn field_labels() -> &'static [(&'static str, &'static str)] {
        &[
            ("group", "Plugin Group"),
            ("disable_pipes", "Disable Wine Output"),
            ("editor_coordinate_hack", "Fix Screen Coordinates"),
            ("editor_disable_host_scaling", "Disable Host HiDPI Scaling"),
            ("editor_force_dnd", "Force Drag & Drop (REAPER)"),
            ("editor_xembed", "Use XEmbed (Wine embedding)"),
            ("frame_rate", "GUI Refresh Rate (fps)"),
            ("hide_daw", "Hide DAW Name"),
            ("vst3_prefer_32bit", "Prefer 32-bit VST3"),
        ]
    }

    /// Get a field value as a displayable string by index (0-8)
    pub fn get_field_display(&self, idx: usize) -> String {
        match idx {
            0 => self.group.as_deref().unwrap_or("").to_string(),
            1 => bool_display(self.disable_pipes),
            2 => bool_display(self.editor_coordinate_hack),
            3 => bool_display(self.editor_disable_host_scaling),
            4 => bool_display(self.editor_force_dnd),
            5 => bool_display(self.editor_xembed),
            6 => self
                .frame_rate
                .map(|v| v.to_string())
                .unwrap_or_else(|| "-".to_string()),
            7 => bool_display(self.hide_daw),
            8 => bool_display(self.vst3_prefer_32bit),
            _ => String::new(),
        }
    }

    /// Check if a field at given index is a boolean toggle
    pub fn is_bool_field(idx: usize) -> bool {
        matches!(idx, 1 | 2 | 3 | 4 | 5 | 7 | 8)
    }

    /// Returns the yabridge default value hint for a field, shown when the field is unset.
    /// Returns None for fields where showing a default doesn't make sense (e.g. group).
    pub fn field_default_hint(idx: usize) -> Option<&'static str> {
        match idx {
            0 => None,        // group: no meaningful default to show
            1 => Some("off"), // disable_pipes
            2 => Some("off"), // editor_coordinate_hack
            3 => Some("off"), // editor_disable_host_scaling
            4 => Some("off"), // editor_force_dnd
            5 => Some("off"), // editor_xembed
            6 => Some("60"),  // frame_rate
            7 => Some("off"), // hide_daw
            8 => Some("off"), // vst3_prefer_32bit
            _ => None,
        }
    }

    /// Check if a field at given index is a string field
    pub fn is_string_field(idx: usize) -> bool {
        idx == 0
    }

    /// Check if a field at given index is a numeric field
    pub fn is_numeric_field(idx: usize) -> bool {
        idx == 6
    }

    /// Cycle a boolean field through 3 states: None → Some(true) → Some(false) → None
    pub fn toggle_bool(&mut self, idx: usize) {
        let cycle = |val: &mut Option<bool>| {
            *val = match *val {
                None => Some(true),
                Some(true) => Some(false),
                Some(false) => None,
            };
        };
        match idx {
            1 => cycle(&mut self.disable_pipes),
            2 => cycle(&mut self.editor_coordinate_hack),
            3 => cycle(&mut self.editor_disable_host_scaling),
            4 => cycle(&mut self.editor_force_dnd),
            5 => cycle(&mut self.editor_xembed),
            7 => cycle(&mut self.hide_daw),
            8 => cycle(&mut self.vst3_prefer_32bit),
            _ => {}
        }
    }

    /// Set a string field by index
    pub fn set_string(&mut self, idx: usize, value: String) {
        if idx == 0 {
            self.group = if value.is_empty() { None } else { Some(value) };
        }
    }

    /// Set a numeric field by index
    pub fn set_numeric(&mut self, idx: usize, value: Option<u32>) {
        if idx == 6 {
            self.frame_rate = value;
        }
    }

    /// Count how many options are set
    pub fn option_count(&self) -> usize {
        let mut count = 0;
        if self.group.is_some() {
            count += 1;
        }
        if self.disable_pipes.is_some() {
            count += 1;
        }
        if self.editor_coordinate_hack.is_some() {
            count += 1;
        }
        if self.editor_disable_host_scaling.is_some() {
            count += 1;
        }
        if self.editor_force_dnd.is_some() {
            count += 1;
        }
        if self.editor_xembed.is_some() {
            count += 1;
        }
        if self.frame_rate.is_some() {
            count += 1;
        }
        if self.hide_daw.is_some() {
            count += 1;
        }
        if self.vst3_prefer_32bit.is_some() {
            count += 1;
        }
        count
    }
}

fn bool_display(val: Option<bool>) -> String {
    match val {
        Some(true) => "yes".to_string(),
        Some(false) => "no".to_string(),
        None => "-".to_string(),
    }
}

/// Sync operation options
#[derive(Debug, Clone, Default)]
pub struct SyncOptions {
    pub force: bool,
    pub no_verify: bool,
    pub prune: bool,
    pub verbose: bool,
}
