use crate::engine::PatternChange;
use crate::model::{FollowUp, LaunchQuantization, PatternSpeed, SyncMode};
use std::collections::{HashMap, HashSet};

#[derive(Clone)]
pub struct StagedChange {
    pub change: PatternChange,
    pub quantization: LaunchQuantization,
    pub sync_mode: SyncMode,
}

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum StagedMuteChange {
    ToggleMute { bank: usize, pattern: usize },
    ToggleSolo { bank: usize, pattern: usize },
}

pub struct StagedPropChange {
    pub name: Option<String>,
    pub description: Option<String>,
    pub length: Option<usize>,
    pub speed: PatternSpeed,
    pub quantization: LaunchQuantization,
    pub sync_mode: SyncMode,
    pub follow_up: FollowUp,
}

pub struct PlaybackState {
    pub playing: bool,
    pub staged_changes: Vec<StagedChange>,
    pub queued_changes: Vec<StagedChange>,
    pub staged_mute_changes: HashSet<StagedMuteChange>,
    pub staged_prop_changes: HashMap<(usize, usize), StagedPropChange>,
    pub muted: HashSet<(usize, usize)>,
    pub soloed: HashSet<(usize, usize)>,
}

impl Default for PlaybackState {
    fn default() -> Self {
        Self {
            playing: true,
            staged_changes: Vec::new(),
            queued_changes: Vec::new(),
            staged_mute_changes: HashSet::new(),
            staged_prop_changes: HashMap::new(),
            muted: HashSet::new(),
            soloed: HashSet::new(),
        }
    }
}

impl PlaybackState {
    pub fn toggle(&mut self) {
        self.playing = !self.playing;
    }

    pub fn clear_queues(&mut self) {
        self.staged_changes.clear();
        self.queued_changes.clear();
        self.staged_prop_changes.clear();
    }

    pub fn has_staged_props(&self, bank: usize, pattern: usize) -> bool {
        self.staged_prop_changes.contains_key(&(bank, pattern))
    }

    pub fn stage_mute(&mut self, bank: usize, pattern: usize) {
        let change = StagedMuteChange::ToggleMute { bank, pattern };
        if self.staged_mute_changes.contains(&change) {
            self.staged_mute_changes.remove(&change);
        } else {
            self.staged_mute_changes.insert(change);
        }
    }

    pub fn stage_solo(&mut self, bank: usize, pattern: usize) {
        let change = StagedMuteChange::ToggleSolo { bank, pattern };
        if self.staged_mute_changes.contains(&change) {
            self.staged_mute_changes.remove(&change);
        } else {
            self.staged_mute_changes.insert(change);
        }
    }

    pub fn clear_staged_mutes(&mut self) {
        self.staged_mute_changes.retain(|c| !matches!(c, StagedMuteChange::ToggleMute { .. }));
    }

    pub fn clear_staged_solos(&mut self) {
        self.staged_mute_changes.retain(|c| !matches!(c, StagedMuteChange::ToggleSolo { .. }));
    }

    pub fn clear_mutes(&mut self) {
        self.clear_staged_mutes();
        self.muted.clear();
    }

    pub fn clear_solos(&mut self) {
        self.clear_staged_solos();
        self.soloed.clear();
    }

    pub fn has_staged_mute(&self, bank: usize, pattern: usize) -> bool {
        self.staged_mute_changes.contains(&StagedMuteChange::ToggleMute { bank, pattern })
    }

    pub fn has_staged_solo(&self, bank: usize, pattern: usize) -> bool {
        self.staged_mute_changes.contains(&StagedMuteChange::ToggleSolo { bank, pattern })
    }

    pub fn toggle_mute(&mut self, bank: usize, pattern: usize) {
        let key = (bank, pattern);
        if !self.muted.remove(&key) {
            self.muted.insert(key);
        }
    }

    pub fn toggle_solo(&mut self, bank: usize, pattern: usize) {
        let key = (bank, pattern);
        if !self.soloed.remove(&key) {
            self.soloed.insert(key);
        }
    }

    pub fn is_muted(&self, bank: usize, pattern: usize) -> bool {
        self.muted.contains(&(bank, pattern))
    }

    pub fn is_soloed(&self, bank: usize, pattern: usize) -> bool {
        self.soloed.contains(&(bank, pattern))
    }

    pub fn has_armed(&self) -> bool {
        !self.staged_changes.is_empty()
            || !self.staged_mute_changes.is_empty()
            || !self.staged_prop_changes.is_empty()
    }

    pub fn armed_summary(&self) -> Option<String> {
        let play = self.staged_changes.iter().filter(|c| matches!(c.change, PatternChange::Start { .. })).count();
        let stop = self.staged_changes.iter().filter(|c| matches!(c.change, PatternChange::Stop { .. })).count();
        let mute = self.staged_mute_changes.iter().filter(|c| matches!(c, StagedMuteChange::ToggleMute { .. })).count();
        let solo = self.staged_mute_changes.iter().filter(|c| matches!(c, StagedMuteChange::ToggleSolo { .. })).count();
        let props = self.staged_prop_changes.len();

        let parts: Vec<String> = [
            (play, "play"),
            (stop, "stop"),
            (mute, "mute"),
            (solo, "solo"),
            (props, "props"),
        ]
        .into_iter()
        .filter(|(n, _)| *n > 0)
        .map(|(n, label)| format!("{n} {label}"))
        .collect();

        if parts.is_empty() { None } else { Some(parts.join(", ")) }
    }

    pub fn is_effectively_muted(&self, bank: usize, pattern: usize) -> bool {
        if self.muted.contains(&(bank, pattern)) {
            return true;
        }
        !self.soloed.is_empty() && !self.soloed.contains(&(bank, pattern))
    }
}
