use std::collections::{HashMap, HashSet};

use serde::{Deserialize, Serialize};

use crate::engine::key_stats::KeyStatsStore;
use crate::keyboard::display::{BACKSPACE, SPACE};
use crate::l10n::language_pack::{
    DEFAULT_LATIN_PRIMARY_SEQUENCE, normalized_primary_letter_sequence,
};

/// Events returned by `SkillTree::update` describing what changed.
pub struct SkillTreeUpdate {
    pub newly_unlocked: Vec<char>,
    pub newly_mastered: Vec<char>,
    pub branches_newly_available: Vec<BranchId>,
    pub branches_newly_completed: Vec<BranchId>,
    pub all_keys_unlocked: bool,
    pub all_keys_mastered: bool,
}

// --- Branch ID ---

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BranchId {
    Lowercase,
    Capitals,
    Numbers,
    ProsePunctuation,
    Whitespace,
    CodeSymbols,
}

impl BranchId {
    pub fn to_key(self) -> &'static str {
        match self {
            BranchId::Lowercase => "lowercase",
            BranchId::Capitals => "capitals",
            BranchId::Numbers => "numbers",
            BranchId::ProsePunctuation => "prose_punctuation",
            BranchId::Whitespace => "whitespace",
            BranchId::CodeSymbols => "code_symbols",
        }
    }

    #[allow(dead_code)]
    pub fn from_key(key: &str) -> Option<Self> {
        match key {
            "lowercase" => Some(BranchId::Lowercase),
            "capitals" => Some(BranchId::Capitals),
            "numbers" => Some(BranchId::Numbers),
            "prose_punctuation" => Some(BranchId::ProsePunctuation),
            "whitespace" => Some(BranchId::Whitespace),
            "code_symbols" => Some(BranchId::CodeSymbols),
            _ => None,
        }
    }

    pub fn all() -> &'static [BranchId] {
        &[
            BranchId::Lowercase,
            BranchId::Capitals,
            BranchId::Numbers,
            BranchId::ProsePunctuation,
            BranchId::Whitespace,
            BranchId::CodeSymbols,
        ]
    }
}

// --- Branch Status ---

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum BranchStatus {
    Locked,
    Available,
    InProgress,
    Complete,
}

// --- Static Definitions ---

pub struct LevelDefinition {
    pub name_key: &'static str,
    pub keys: &'static [char],
}

impl LevelDefinition {
    pub fn display_name(&self) -> String {
        crate::i18n::t!(self.name_key).to_string()
    }
}

pub struct BranchDefinition {
    pub id: BranchId,
    pub name_key: &'static str,
    pub levels: &'static [LevelDefinition],
}

impl BranchDefinition {
    pub fn display_name(&self) -> String {
        crate::i18n::t!(self.name_key).to_string()
    }
}

// Lowercase metadata remains for static branch lookup/UI labels. Runtime
// progression and unlock counts are driven by `SkillTree::primary_letters`.
const LOWERCASE_LEVELS: &[LevelDefinition] = &[LevelDefinition {
    name_key: "skill_tree.level_frequency_order",
    keys: &[
        'e', 't', 'a', 'o', 'i', 'n', 's', 'h', 'r', 'd', 'l', 'c', 'u', 'm', 'w', 'f', 'g', 'y',
        'p', 'b', 'v', 'k', 'j', 'x', 'q', 'z',
    ],
}];

const CAPITALS_LEVELS: &[LevelDefinition] = &[
    LevelDefinition {
        name_key: "skill_tree.level_common_sentence_capitals",
        keys: &['T', 'I', 'A', 'S', 'W', 'H', 'B', 'M'],
    },
    LevelDefinition {
        name_key: "skill_tree.level_name_capitals",
        keys: &['J', 'D', 'R', 'C', 'E', 'N', 'P', 'L', 'F', 'G'],
    },
    LevelDefinition {
        name_key: "skill_tree.level_remaining_capitals",
        keys: &['O', 'U', 'K', 'V', 'Y', 'X', 'Q', 'Z'],
    },
];

const NUMBERS_LEVELS: &[LevelDefinition] = &[
    LevelDefinition {
        name_key: "skill_tree.level_common_digits",
        keys: &['1', '2', '3', '4', '5'],
    },
    LevelDefinition {
        name_key: "skill_tree.level_all_digits",
        keys: &['0', '6', '7', '8', '9'],
    },
];

const PROSE_PUNCTUATION_LEVELS: &[LevelDefinition] = &[
    LevelDefinition {
        name_key: "skill_tree.level_essential",
        keys: &['.', ',', '\''],
    },
    LevelDefinition {
        name_key: "skill_tree.level_common",
        keys: &[';', ':', '"', '-'],
    },
    LevelDefinition {
        name_key: "skill_tree.level_expressive",
        keys: &['?', '!', '(', ')'],
    },
];

const WHITESPACE_LEVELS: &[LevelDefinition] = &[
    LevelDefinition {
        name_key: "skill_tree.level_enter_return",
        keys: &['\n'],
    },
    LevelDefinition {
        name_key: "skill_tree.level_tab_indent",
        keys: &['\t'],
    },
];

const CODE_SYMBOLS_LEVELS: &[LevelDefinition] = &[
    LevelDefinition {
        name_key: "skill_tree.level_arithmetic_assignment",
        keys: &['=', '+', '*', '/', '-'],
    },
    LevelDefinition {
        name_key: "skill_tree.level_grouping",
        keys: &['{', '}', '[', ']', '<', '>'],
    },
    LevelDefinition {
        name_key: "skill_tree.level_logic_reference",
        keys: &['&', '|', '^', '~', '!'],
    },
    LevelDefinition {
        name_key: "skill_tree.level_special",
        keys: &['@', '#', '$', '%', '_', '\\', '`'],
    },
];

pub const ALL_BRANCHES: &[BranchDefinition] = &[
    BranchDefinition {
        id: BranchId::Lowercase,
        name_key: "skill_tree.branch_primary_letters",
        levels: LOWERCASE_LEVELS,
    },
    BranchDefinition {
        id: BranchId::Capitals,
        name_key: "skill_tree.branch_capital_letters",
        levels: CAPITALS_LEVELS,
    },
    BranchDefinition {
        id: BranchId::Numbers,
        name_key: "skill_tree.branch_numbers",
        levels: NUMBERS_LEVELS,
    },
    BranchDefinition {
        id: BranchId::ProsePunctuation,
        name_key: "skill_tree.branch_prose_punctuation",
        levels: PROSE_PUNCTUATION_LEVELS,
    },
    BranchDefinition {
        id: BranchId::Whitespace,
        name_key: "skill_tree.branch_whitespace",
        levels: WHITESPACE_LEVELS,
    },
    BranchDefinition {
        id: BranchId::CodeSymbols,
        name_key: "skill_tree.branch_code_symbols",
        levels: CODE_SYMBOLS_LEVELS,
    },
];

/// Find which branch and level a key belongs to.
/// Returns (branch_def, level_def, 1-based position in level).
pub fn find_key_branch(
    ch: char,
) -> Option<(&'static BranchDefinition, &'static LevelDefinition, usize)> {
    for branch in ALL_BRANCHES {
        for level in branch.levels {
            if let Some(pos) = level.keys.iter().position(|&k| k == ch) {
                return Some((branch, level, pos + 1));
            }
        }
    }
    None
}

pub fn get_branch_definition(id: BranchId) -> &'static BranchDefinition {
    ALL_BRANCHES
        .iter()
        .find(|b| b.id == id)
        .expect("branch definition not found")
}

// --- Persisted Progress ---

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BranchProgress {
    pub status: BranchStatus,
    pub current_level: usize,
}

impl Default for BranchProgress {
    fn default() -> Self {
        Self {
            status: BranchStatus::Locked,
            current_level: 0,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SkillTreeProgress {
    pub branches: HashMap<String, BranchProgress>,
}

impl Default for SkillTreeProgress {
    fn default() -> Self {
        let mut branches = HashMap::new();
        // Lowercase starts as InProgress; everything else Locked
        branches.insert(
            BranchId::Lowercase.to_key().to_string(),
            BranchProgress {
                status: BranchStatus::InProgress,
                current_level: 0,
            },
        );
        for &id in &[
            BranchId::Capitals,
            BranchId::Numbers,
            BranchId::ProsePunctuation,
            BranchId::Whitespace,
            BranchId::CodeSymbols,
        ] {
            branches.insert(id.to_key().to_string(), BranchProgress::default());
        }
        Self { branches }
    }
}

// --- Skill Tree Engine ---

/// The scope for key collection and focus selection.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DrillScope {
    /// Global adaptive: all InProgress + Complete branches
    Global,
    /// Branch-specific drill: specific branch + primary-letter background
    Branch(BranchId),
}

pub struct SkillTree {
    pub progress: SkillTreeProgress,
    pub total_unique_keys: usize,
    primary_letters: Vec<char>,
}

/// Number of lowercase letters to start with before unlocking one-at-a-time
const LOWERCASE_MIN_KEYS: usize = 6;
const ALWAYS_UNLOCKED_KEYS: &[char] = &[SPACE, BACKSPACE];

impl SkillTree {
    pub fn new(progress: SkillTreeProgress) -> Self {
        Self::new_with_primary_sequence(progress, DEFAULT_LATIN_PRIMARY_SEQUENCE)
    }

    pub fn new_with_primary_sequence(progress: SkillTreeProgress, sequence: &str) -> Self {
        let primary_letters = Self::normalize_primary_sequence(sequence);
        let total_unique_keys = Self::compute_total_unique_keys(&primary_letters);
        Self {
            progress,
            total_unique_keys,
            primary_letters,
        }
    }

    fn normalize_primary_sequence(sequence: &str) -> Vec<char> {
        let normalized = normalized_primary_letter_sequence(sequence);
        if normalized.is_empty() {
            DEFAULT_LATIN_PRIMARY_SEQUENCE.chars().collect()
        } else {
            normalized
        }
    }

    fn compute_total_unique_keys(primary_letters: &[char]) -> usize {
        let mut all_keys: HashSet<char> = HashSet::new();
        for branch in ALL_BRANCHES {
            if branch.id == BranchId::Lowercase {
                continue;
            }
            for level in branch.levels {
                for &key in level.keys {
                    all_keys.insert(key);
                }
            }
        }
        all_keys.extend(primary_letters.iter().copied());
        all_keys.len()
    }

    pub fn primary_letters(&self) -> &[char] {
        &self.primary_letters
    }

    pub fn branch_status(&self, id: BranchId) -> &BranchStatus {
        self.progress
            .branches
            .get(id.to_key())
            .map(|bp| &bp.status)
            .unwrap_or(&BranchStatus::Locked)
    }

    pub fn branch_progress(&self, id: BranchId) -> &BranchProgress {
        static DEFAULT: BranchProgress = BranchProgress {
            status: BranchStatus::Locked,
            current_level: 0,
        };
        self.progress.branches.get(id.to_key()).unwrap_or(&DEFAULT)
    }

    pub fn branch_progress_mut(&mut self, id: BranchId) -> &mut BranchProgress {
        self.progress
            .branches
            .entry(id.to_key().to_string())
            .or_default()
    }

    /// Start a branch (transition Available -> InProgress).
    pub fn start_branch(&mut self, id: BranchId) {
        let bp = self.branch_progress_mut(id);
        if bp.status == BranchStatus::Available {
            bp.status = BranchStatus::InProgress;
            bp.current_level = 0;
        }
    }

    /// Collect all unlocked keys for the given scope.
    pub fn unlocked_keys(&self, scope: DrillScope) -> Vec<char> {
        match scope {
            DrillScope::Global => self.global_unlocked_keys(),
            DrillScope::Branch(id) => self.branch_unlocked_keys(id),
        }
    }

    fn global_unlocked_keys(&self) -> Vec<char> {
        let mut keys = ALWAYS_UNLOCKED_KEYS.to_vec();
        for branch_def in ALL_BRANCHES {
            let bp = self.branch_progress(branch_def.id);
            match bp.status {
                BranchStatus::InProgress => {
                    // For lowercase, use the progressive unlock system
                    if branch_def.id == BranchId::Lowercase {
                        keys.extend(self.lowercase_unlocked_keys());
                    } else {
                        // Include current level's keys + all prior levels
                        for (i, level) in branch_def.levels.iter().enumerate() {
                            if i <= bp.current_level {
                                keys.extend_from_slice(level.keys);
                            }
                        }
                    }
                }
                BranchStatus::Complete => {
                    if branch_def.id == BranchId::Lowercase {
                        keys.extend(self.primary_letters.iter().copied());
                    } else {
                        for level in branch_def.levels {
                            keys.extend_from_slice(level.keys);
                        }
                    }
                }
                _ => {}
            }
        }
        keys
    }

    fn branch_unlocked_keys(&self, id: BranchId) -> Vec<char> {
        let mut keys = ALWAYS_UNLOCKED_KEYS.to_vec();

        // Always include primary-letter background keys
        if id != BranchId::Lowercase {
            let lowercase_bp = self.branch_progress(BranchId::Lowercase);
            match lowercase_bp.status {
                BranchStatus::InProgress => keys.extend(self.lowercase_unlocked_keys()),
                BranchStatus::Complete => {
                    keys.extend(self.primary_letters.iter().copied());
                }
                _ => {}
            }
        }

        // Include keys from the target branch
        let branch_def = get_branch_definition(id);
        let bp = self.branch_progress(id);
        if id == BranchId::Lowercase {
            keys.extend(self.lowercase_unlocked_keys());
        } else {
            match bp.status {
                BranchStatus::InProgress => {
                    for (i, level) in branch_def.levels.iter().enumerate() {
                        if i <= bp.current_level {
                            keys.extend_from_slice(level.keys);
                        }
                    }
                }
                BranchStatus::Complete => {
                    for level in branch_def.levels {
                        keys.extend_from_slice(level.keys);
                    }
                }
                _ => {}
            }
        }

        keys
    }

    /// Get the progressively-unlocked lowercase keys (mirrors old LetterUnlock logic).
    fn lowercase_unlocked_keys(&self) -> Vec<char> {
        let bp = self.branch_progress(BranchId::Lowercase);
        let all_keys = self.primary_letters();

        match bp.status {
            BranchStatus::Complete => all_keys.to_vec(),
            BranchStatus::InProgress => {
                // current_level represents number of keys unlocked beyond LOWERCASE_MIN_KEYS
                let count = (LOWERCASE_MIN_KEYS + bp.current_level).min(all_keys.len());
                all_keys[..count].to_vec()
            }
            _ => Vec::new(),
        }
    }

    /// Number of unlocked lowercase letters (for display).
    pub fn lowercase_unlocked_count(&self) -> usize {
        self.lowercase_unlocked_keys().len()
    }

    /// Find the focused (weakest) key for the given scope.
    pub fn focused_key(&self, scope: DrillScope, stats: &KeyStatsStore) -> Option<char> {
        match scope {
            DrillScope::Global => self.global_focused_key(stats),
            DrillScope::Branch(id) => self.branch_focused_key(id, stats),
        }
    }

    fn global_focused_key(&self, stats: &KeyStatsStore) -> Option<char> {
        // Collect keys from all InProgress branches (current level only) + complete branches
        let mut focus_candidates = Vec::new();
        for branch_def in ALL_BRANCHES {
            let bp = self.branch_progress(branch_def.id);
            match bp.status {
                BranchStatus::InProgress => {
                    if branch_def.id == BranchId::Lowercase {
                        focus_candidates.extend(self.lowercase_unlocked_keys());
                    } else if bp.current_level < branch_def.levels.len() {
                        // Only current level keys are focus candidates
                        focus_candidates
                            .extend_from_slice(branch_def.levels[bp.current_level].keys);
                        // Plus prior level keys for reinforcement
                        for i in 0..bp.current_level {
                            focus_candidates.extend_from_slice(branch_def.levels[i].keys);
                        }
                    }
                }
                BranchStatus::Complete => {
                    if branch_def.id == BranchId::Lowercase {
                        focus_candidates.extend(self.primary_letters.iter().copied());
                    } else {
                        for level in branch_def.levels {
                            focus_candidates.extend_from_slice(level.keys);
                        }
                    }
                }
                _ => {}
            }
        }

        Self::weakest_key(&focus_candidates, stats)
    }

    fn branch_focused_key(&self, id: BranchId, stats: &KeyStatsStore) -> Option<char> {
        let branch_def = get_branch_definition(id);
        let bp = self.branch_progress(id);

        if id == BranchId::Lowercase {
            return Self::weakest_key(&self.lowercase_unlocked_keys(), stats);
        }

        match bp.status {
            BranchStatus::InProgress if bp.current_level < branch_def.levels.len() => {
                // Focus only within current level's keys
                let current_keys = branch_def.levels[bp.current_level].keys;
                Self::weakest_key(&current_keys.to_vec(), stats)
            }
            _ => None,
        }
    }

    fn weakest_key(keys: &[char], stats: &KeyStatsStore) -> Option<char> {
        keys.iter()
            .filter(|&&ch| !stats.is_mastered(ch))
            .min_by(|&&a, &&b| {
                stats
                    .get_confidence(a)
                    .partial_cmp(&stats.get_confidence(b))
                    .unwrap_or(std::cmp::Ordering::Equal)
            })
            .copied()
    }

    /// Update skill tree progress based on current key stats.
    /// Call after updating KeyStatsStore.
    ///
    /// `before_stats` is an optional snapshot of key stats *before* this drill's data was added.
    /// When provided, it's used to detect which keys transitioned from unmastered to mastered
    /// (the sticky `mastered` bit flipping false → true). Because mastery is sticky, a mastery
    /// milestone can fire at most once per key ever.
    /// Returns a `SkillTreeUpdate` describing which keys were newly unlocked or mastered.
    pub fn update(
        &mut self,
        stats: &KeyStatsStore,
        before_stats: Option<&KeyStatsStore>,
    ) -> SkillTreeUpdate {
        // Snapshot unlocked keys before tree structure changes
        let before_unlocked: HashSet<char> =
            self.unlocked_keys(DrillScope::Global).into_iter().collect();

        // Snapshot branch statuses before any updates
        let before_branch_statuses: HashMap<BranchId, BranchStatus> = BranchId::all()
            .iter()
            .map(|&id| (id, self.branch_status(id).clone()))
            .collect();
        let before_unlocked_count = self.total_unlocked_count();

        // Update lowercase branch (progressive unlock)
        self.update_lowercase(stats);

        // Check if lowercase is complete -> unlock other branches
        // Snapshot non-lowercase branch statuses before auto-unlock (canonical order)
        const NON_LOWERCASE_BRANCHES: &[BranchId] = &[
            BranchId::Capitals,
            BranchId::Numbers,
            BranchId::ProsePunctuation,
            BranchId::Whitespace,
            BranchId::CodeSymbols,
        ];
        let before_auto_unlock: Vec<(BranchId, BranchStatus)> = NON_LOWERCASE_BRANCHES
            .iter()
            .map(|&id| (id, self.branch_status(id).clone()))
            .collect();

        if *self.branch_status(BranchId::Lowercase) == BranchStatus::Complete {
            for &id in NON_LOWERCASE_BRANCHES {
                let bp = self.branch_progress_mut(id);
                if bp.status == BranchStatus::Locked {
                    bp.status = BranchStatus::Available;
                }
            }
        }

        // Detect Locked → Available transitions (maintains canonical order)
        let branches_newly_available: Vec<BranchId> = before_auto_unlock
            .iter()
            .filter(|(id, before_status)| {
                *before_status == BranchStatus::Locked
                    && *self.branch_status(*id) == BranchStatus::Available
            })
            .map(|(id, _)| *id)
            .collect();

        // Update InProgress branches (non-lowercase)
        for branch_def in ALL_BRANCHES {
            if branch_def.id == BranchId::Lowercase {
                continue;
            }
            let bp = self.branch_progress(branch_def.id).clone();
            if bp.status != BranchStatus::InProgress {
                continue;
            }
            self.update_branch_level(branch_def, stats);
        }

        // Detect branches that became Complete
        let branches_newly_completed: Vec<BranchId> = BranchId::all()
            .iter()
            .filter(|&&id| {
                before_branch_statuses
                    .get(&id)
                    .map(|s| *s != BranchStatus::Complete)
                    .unwrap_or(true)
                    && *self.branch_status(id) == BranchStatus::Complete
            })
            .copied()
            .collect();

        // Detect all keys unlocked
        let after_unlocked_count = self.total_unlocked_count();
        let all_keys_unlocked = after_unlocked_count == self.total_unique_keys
            && before_unlocked_count != self.total_unique_keys;

        // Detect all keys mastered
        let all_complete_now = BranchId::all()
            .iter()
            .all(|&id| *self.branch_status(id) == BranchStatus::Complete);
        let all_complete_before = BranchId::all()
            .iter()
            .all(|id| before_branch_statuses.get(id) == Some(&BranchStatus::Complete));
        let all_keys_mastered = all_complete_now && !all_complete_before;

        // Snapshot after
        let after_unlocked: HashSet<char> =
            self.unlocked_keys(DrillScope::Global).into_iter().collect();

        let newly_unlocked: Vec<char> = after_unlocked
            .difference(&before_unlocked)
            .copied()
            .collect();

        // Detect mastery: keys that were unlocked before and whose sticky `mastered` bit
        // transitioned from false to true. Because mastery is sticky, this fires at most
        // once per key across its lifetime.
        let newly_mastered: Vec<char> = if let Some(before) = before_stats {
            before_unlocked
                .iter()
                .filter(|&&ch| !before.is_mastered(ch) && stats.is_mastered(ch))
                .copied()
                .collect()
        } else {
            Vec::new()
        };

        SkillTreeUpdate {
            newly_unlocked,
            newly_mastered,
            branches_newly_available,
            branches_newly_completed,
            all_keys_unlocked,
            all_keys_mastered,
        }
    }

    fn update_lowercase(&mut self, stats: &KeyStatsStore) {
        let bp = self.branch_progress(BranchId::Lowercase).clone();
        if bp.status != BranchStatus::InProgress {
            return;
        }

        let all_keys = self.primary_letters.clone();
        let current_count = LOWERCASE_MIN_KEYS + bp.current_level;

        if current_count >= all_keys.len() {
            // All primary letters unlocked, check if all mastered (sticky)
            let all_mastered = all_keys.iter().all(|&ch| stats.is_mastered(ch));
            if all_mastered {
                let bp_mut = self.branch_progress_mut(BranchId::Lowercase);
                bp_mut.status = BranchStatus::Complete;
                bp_mut.current_level = all_keys.len() - LOWERCASE_MIN_KEYS;
            }
            return;
        }

        // Check if all current keys are mastered -> unlock next
        let current_keys = &all_keys[..current_count];
        let all_mastered = current_keys.iter().all(|&ch| stats.is_mastered(ch));

        if all_mastered {
            let bp_mut = self.branch_progress_mut(BranchId::Lowercase);
            bp_mut.current_level += 1;
        }
    }

    fn update_branch_level(&mut self, branch_def: &BranchDefinition, stats: &KeyStatsStore) {
        let bp = self.branch_progress(branch_def.id).clone();
        if bp.current_level >= branch_def.levels.len() {
            // Already past last level, mark complete
            let bp_mut = self.branch_progress_mut(branch_def.id);
            bp_mut.status = BranchStatus::Complete;
            return;
        }

        // Check if all keys in current level are mastered (sticky)
        let current_level_keys = branch_def.levels[bp.current_level].keys;
        let all_mastered = current_level_keys.iter().all(|&ch| stats.is_mastered(ch));

        if all_mastered {
            let bp_mut = self.branch_progress_mut(branch_def.id);
            bp_mut.current_level += 1;
            if bp_mut.current_level >= branch_def.levels.len() {
                bp_mut.status = BranchStatus::Complete;
            }
        }
    }

    /// Total number of unlocked unique keys across all branches.
    pub fn total_unlocked_count(&self) -> usize {
        let mut keys: HashSet<char> = HashSet::new();
        for branch_def in ALL_BRANCHES {
            let bp = self.branch_progress(branch_def.id);
            match bp.status {
                BranchStatus::InProgress => {
                    if branch_def.id == BranchId::Lowercase {
                        for key in self.lowercase_unlocked_keys() {
                            keys.insert(key);
                        }
                    } else {
                        for (i, level) in branch_def.levels.iter().enumerate() {
                            if i <= bp.current_level {
                                for &key in level.keys {
                                    keys.insert(key);
                                }
                            }
                        }
                    }
                }
                BranchStatus::Complete => {
                    if branch_def.id == BranchId::Lowercase {
                        for &key in self.primary_letters() {
                            keys.insert(key);
                        }
                    } else {
                        for level in branch_def.levels {
                            for &key in level.keys {
                                keys.insert(key);
                            }
                        }
                    }
                }
                _ => {}
            }
        }
        keys.len()
    }

    /// Complexity for scoring: total_unlocked / total_unique
    pub fn complexity(&self) -> f64 {
        (self.total_unlocked_count() as f64 / self.total_unique_keys as f64).max(0.1)
    }

    /// Get all branch definitions with their current progress (for UI).
    #[allow(dead_code)]
    pub fn all_branches_with_progress(&self) -> Vec<(&'static BranchDefinition, &BranchProgress)> {
        ALL_BRANCHES
            .iter()
            .map(|def| (def, self.branch_progress(def.id)))
            .collect()
    }

    /// Number of unlocked keys in a branch.
    pub fn branch_unlocked_count(&self, id: BranchId) -> usize {
        let def = get_branch_definition(id);
        let bp = self.branch_progress(id);
        match bp.status {
            BranchStatus::Complete => {
                if id == BranchId::Lowercase {
                    self.primary_letters().len()
                } else {
                    def.levels.iter().map(|l| l.keys.len()).sum()
                }
            }
            BranchStatus::InProgress => {
                if id == BranchId::Lowercase {
                    self.lowercase_unlocked_count()
                } else {
                    def.levels
                        .iter()
                        .enumerate()
                        .filter(|(i, _)| *i <= bp.current_level)
                        .map(|(_, l)| l.keys.len())
                        .sum()
                }
            }
            _ => 0,
        }
    }

    /// Total keys defined in a branch (across all levels).
    pub fn branch_total_keys(id: BranchId) -> usize {
        let def = get_branch_definition(id);
        def.levels.iter().map(|l| l.keys.len()).sum()
    }

    /// Total keys defined in a branch for this tree configuration.
    pub fn branch_total_keys_for(&self, id: BranchId) -> usize {
        if id == BranchId::Lowercase {
            self.primary_letters().len()
        } else {
            Self::branch_total_keys(id)
        }
    }

    /// Count of unique keys with sticky mastery across all branches.
    ///
    /// This intentionally excludes always-unlocked helper keys like space and
    /// backspace so aggregate totals match progression completion milestones.
    /// Invariant: when every branch's progression keys all satisfy
    /// `is_mastered`, the branch is Complete — so this count is monotonic
    /// across drills.
    pub fn total_mastered_keys(&self, stats: &KeyStatsStore) -> usize {
        let mut keys: HashSet<char> = HashSet::new();
        for &ch in self.primary_letters() {
            if stats.is_mastered(ch) {
                keys.insert(ch);
            }
        }
        for branch_def in ALL_BRANCHES {
            if branch_def.id == BranchId::Lowercase {
                continue;
            }
            for level in branch_def.levels {
                for &ch in level.keys {
                    if stats.is_mastered(ch) {
                        keys.insert(ch);
                    }
                }
            }
        }
        keys.len()
    }

    /// Count of sticky-mastered keys in a branch.
    pub fn branch_mastered_keys(&self, id: BranchId, stats: &KeyStatsStore) -> usize {
        if id == BranchId::Lowercase {
            self.primary_letters()
                .iter()
                .filter(|&&ch| stats.is_mastered(ch))
                .count()
        } else {
            let def = get_branch_definition(id);
            def.levels
                .iter()
                .flat_map(|l| l.keys.iter())
                .filter(|&&ch| stats.is_mastered(ch))
                .count()
        }
    }
}

impl Default for SkillTree {
    fn default() -> Self {
        Self::new(SkillTreeProgress::default())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::l10n::language_pack::language_packs;

    fn make_stats_mastered(stats: &mut KeyStatsStore, keys: &[char]) {
        for &ch in keys {
            for _ in 0..50 {
                stats.update_key_ranked(ch, 200.0);
            }
        }
    }

    #[test]
    fn test_initial_state() {
        let tree = SkillTree::default();
        assert_eq!(
            *tree.branch_status(BranchId::Lowercase),
            BranchStatus::InProgress
        );
        assert_eq!(
            *tree.branch_status(BranchId::Capitals),
            BranchStatus::Locked
        );
        assert_eq!(*tree.branch_status(BranchId::Numbers), BranchStatus::Locked);
    }

    #[test]
    fn test_total_unique_keys() {
        let tree = SkillTree::default();
        assert_eq!(tree.total_unique_keys, 96);
    }

    #[test]
    fn test_progress_totals_exclude_always_unlocked_helper_keys() {
        let tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();
        for _ in 0..50 {
            stats.update_key(SPACE, 200.0);
            stats.update_key(BACKSPACE, 200.0);
        }

        assert_eq!(tree.total_unlocked_count(), LOWERCASE_MIN_KEYS);
        assert_eq!(tree.total_mastered_keys(&stats), 0);
    }

    #[test]
    fn test_initial_lowercase_unlocked() {
        let tree = SkillTree::default();
        let keys = tree.unlocked_keys(DrillScope::Global);
        assert_eq!(keys.len(), LOWERCASE_MIN_KEYS + ALWAYS_UNLOCKED_KEYS.len());
        assert_eq!(&keys[2..8], &['e', 't', 'a', 'o', 'i', 'n']);
        assert!(keys.contains(&SPACE));
        assert!(keys.contains(&BACKSPACE));
    }

    #[test]
    fn test_custom_primary_sequence_drives_lowercase_progression() {
        let tree = SkillTree::new_with_primary_sequence(SkillTreeProgress::default(), "abcde");
        let keys = tree.unlocked_keys(DrillScope::Global);

        // With a shorter primary sequence, all primary letters are immediately unlocked.
        assert!(keys.contains(&'a'));
        assert!(keys.contains(&'e'));
        assert_eq!(tree.primary_letters(), &['a', 'b', 'c', 'd', 'e']);
        assert_eq!(
            tree.branch_total_keys_for(BranchId::Lowercase),
            tree.primary_letters().len()
        );
    }

    #[test]
    fn test_lowercase_progressive_unlock() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        // Make initial 6 keys mastered
        make_stats_mastered(&mut stats, &['e', 't', 'a', 'o', 'i', 'n']);
        tree.update(&stats, None);

        // Should unlock 7th key ('s')
        let keys = tree.unlocked_keys(DrillScope::Global);
        assert_eq!(keys.len(), 9);
        assert!(keys.contains(&'s'));
    }

    #[test]
    fn test_lowercase_completion_unlocks_branches() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        // Make all primary letters mastered.
        let all_primary = tree.primary_letters().to_vec();
        make_stats_mastered(&mut stats, &all_primary);

        // Need to repeatedly update as each unlock requires all current keys mastered
        for _ in 0..30 {
            tree.update(&stats, None);
        }

        assert_eq!(
            *tree.branch_status(BranchId::Lowercase),
            BranchStatus::Complete
        );
        assert_eq!(
            *tree.branch_status(BranchId::Capitals),
            BranchStatus::Available
        );
        assert_eq!(
            *tree.branch_status(BranchId::Numbers),
            BranchStatus::Available
        );
        assert_eq!(
            *tree.branch_status(BranchId::ProsePunctuation),
            BranchStatus::Available
        );
        assert_eq!(
            *tree.branch_status(BranchId::Whitespace),
            BranchStatus::Available
        );
        assert_eq!(
            *tree.branch_status(BranchId::CodeSymbols),
            BranchStatus::Available
        );
    }

    #[test]
    fn test_start_branch() {
        let mut tree = SkillTree::default();
        // Force capitals to Available
        tree.branch_progress_mut(BranchId::Capitals).status = BranchStatus::Available;

        tree.start_branch(BranchId::Capitals);
        assert_eq!(
            *tree.branch_status(BranchId::Capitals),
            BranchStatus::InProgress
        );
        assert_eq!(tree.branch_progress(BranchId::Capitals).current_level, 0);
    }

    #[test]
    fn test_branch_level_advancement() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        // Set capitals to InProgress at level 0
        let bp = tree.branch_progress_mut(BranchId::Capitals);
        bp.status = BranchStatus::InProgress;
        bp.current_level = 0;

        // Make level 1 capitals mastered: T I A S W H B M
        make_stats_mastered(&mut stats, &['T', 'I', 'A', 'S', 'W', 'H', 'B', 'M']);
        tree.update(&stats, None);

        assert_eq!(tree.branch_progress(BranchId::Capitals).current_level, 1);
        assert_eq!(
            *tree.branch_status(BranchId::Capitals),
            BranchStatus::InProgress
        );
    }

    #[test]
    fn test_branch_completion() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        let bp = tree.branch_progress_mut(BranchId::Capitals);
        bp.status = BranchStatus::InProgress;
        bp.current_level = 0;

        // Make all capital letter levels mastered
        let all_caps: Vec<char> = ('A'..='Z').collect();
        make_stats_mastered(&mut stats, &all_caps);

        // Update multiple times for level advancement
        for _ in 0..5 {
            tree.update(&stats, None);
        }

        assert_eq!(
            *tree.branch_status(BranchId::Capitals),
            BranchStatus::Complete
        );
    }

    #[test]
    fn test_shared_key_confidence() {
        let _tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        // '-' is shared between ProsePunctuation L2 and CodeSymbols L1
        // Master it once
        make_stats_mastered(&mut stats, &['-']);

        // Both branches should see the same live confidence
        assert!(stats.get_confidence('-') >= 1.0);
    }

    #[test]
    fn test_focused_key_global() {
        let tree = SkillTree::default();
        let stats = KeyStatsStore::default();

        // All keys at 0 confidence, focused should be first in order
        let focused = tree.focused_key(DrillScope::Global, &stats);
        assert!(focused.is_some());
        // Should be one of the initial 6 lowercase keys
        assert!(
            ['e', 't', 'a', 'o', 'i', 'n'].contains(&focused.unwrap()),
            "focused: {:?}",
            focused
        );
    }

    #[test]
    fn test_focused_key_branch() {
        let mut tree = SkillTree::default();
        let stats = KeyStatsStore::default();

        let bp = tree.branch_progress_mut(BranchId::Capitals);
        bp.status = BranchStatus::InProgress;
        bp.current_level = 0;

        let focused = tree.focused_key(DrillScope::Branch(BranchId::Capitals), &stats);
        assert!(focused.is_some());
        // Should be one of level 1 capitals
        assert!(
            ['T', 'I', 'A', 'S', 'W', 'H', 'B', 'M'].contains(&focused.unwrap()),
            "focused: {:?}",
            focused
        );
    }

    #[test]
    fn test_complexity_scales() {
        let tree = SkillTree::default();
        let initial_complexity = tree.complexity();
        assert!(initial_complexity > 0.0);
        assert!(initial_complexity < 1.0);

        // Full unlock should give complexity ~1.0
        let mut full_tree = SkillTree::default();
        for id in BranchId::all() {
            let bp = full_tree.branch_progress_mut(*id);
            bp.status = BranchStatus::Complete;
        }
        let full_complexity = full_tree.complexity();
        assert!((full_complexity - 1.0).abs() < 0.01);
    }

    #[test]
    fn test_branch_keys_for_drill() {
        let mut tree = SkillTree::default();

        // Set lowercase complete, capitals in progress at level 1
        tree.branch_progress_mut(BranchId::Lowercase).status = BranchStatus::Complete;
        let bp = tree.branch_progress_mut(BranchId::Capitals);
        bp.status = BranchStatus::InProgress;
        bp.current_level = 1;

        let keys = tree.unlocked_keys(DrillScope::Branch(BranchId::Capitals));
        // Should include full primary-letter background + Capitals L1 (8) + Capitals L2 (10)
        assert!(keys.contains(&tree.primary_letters()[0])); // primary-letter background
        assert!(keys.contains(&'T')); // Capitals L1
        assert!(keys.contains(&'J')); // Capitals L2 (current level)
        assert!(!keys.contains(&'O')); // Capitals L3 (locked)
    }

    #[test]
    fn test_branch_unlocked_count() {
        let tree = SkillTree::default();
        // Lowercase starts InProgress with LOWERCASE_MIN_KEYS
        assert_eq!(
            tree.branch_unlocked_count(BranchId::Lowercase),
            LOWERCASE_MIN_KEYS
        );

        // Locked branches return 0
        assert_eq!(tree.branch_unlocked_count(BranchId::Capitals), 0);
        assert_eq!(tree.branch_unlocked_count(BranchId::Numbers), 0);

        // InProgress non-lowercase branch
        let mut tree2 = SkillTree::default();
        let bp = tree2.branch_progress_mut(BranchId::Capitals);
        bp.status = BranchStatus::InProgress;
        bp.current_level = 1;
        // Level 0 (8 keys) + Level 1 (10 keys)
        assert_eq!(tree2.branch_unlocked_count(BranchId::Capitals), 18);

        // Complete branch returns all keys
        let mut tree3 = SkillTree::default();
        tree3.branch_progress_mut(BranchId::Numbers).status = BranchStatus::Complete;
        assert_eq!(tree3.branch_unlocked_count(BranchId::Numbers), 10);
    }

    #[test]
    fn test_selectable_branches_bounds() {
        use crate::ui::components::skill_tree::selectable_branches;

        let branches = selectable_branches();
        assert!(!branches.is_empty());
        assert_eq!(branches[0], BranchId::Lowercase);

        let tree = SkillTree::default();
        // Accessing branch_progress for every selectable branch should not panic
        for &branch_id in &branches {
            let _ = tree.branch_progress(branch_id);
            let _ = SkillTree::branch_total_keys(branch_id);
            let _ = tree.branch_unlocked_count(branch_id);
        }

        // Selection at 0 and at max index should be valid
        assert!(0 < branches.len());
        assert!(branches.len() - 1 < branches.len());
    }

    #[test]
    fn progression_is_monotonic_for_all_language_pack_sequences() {
        for pack in language_packs() {
            let mut tree = SkillTree::new_with_primary_sequence(
                SkillTreeProgress::default(),
                pack.primary_letter_sequence,
            );
            let primary = tree.primary_letters().to_vec();
            assert!(
                !primary.is_empty(),
                "primary sequence should be non-empty for {}",
                pack.language_key
            );

            let mut stats = KeyStatsStore::default();
            let mut previous_count = tree.lowercase_unlocked_count();
            assert!(
                previous_count <= primary.len(),
                "initial unlock count must be bounded for {}",
                pack.language_key
            );

            // Master keys in configured sequence order and verify unlocked count never decreases.
            for &ch in &primary {
                make_stats_mastered(&mut stats, &[ch]);
                for _ in 0..3 {
                    tree.update(&stats, None);
                    let current_count = tree.lowercase_unlocked_count();
                    assert!(
                        current_count >= previous_count,
                        "unlock count regressed for {}: {} -> {}",
                        pack.language_key,
                        previous_count,
                        current_count
                    );
                    previous_count = current_count;
                }
            }

            for _ in 0..30 {
                tree.update(&stats, None);
                let current_count = tree.lowercase_unlocked_count();
                assert!(
                    current_count >= previous_count,
                    "unlock count regressed in completion pass for {}: {} -> {}",
                    pack.language_key,
                    previous_count,
                    current_count
                );
                previous_count = current_count;
            }

            assert_eq!(
                tree.lowercase_unlocked_count(),
                primary.len(),
                "all primary letters should unlock for {}",
                pack.language_key
            );
            assert_eq!(
                *tree.branch_status(BranchId::Lowercase),
                BranchStatus::Complete,
                "lowercase branch should complete for {}",
                pack.language_key
            );
        }
    }

    #[test]
    fn test_update_returns_newly_unlocked() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        // Make initial 6 keys mastered
        make_stats_mastered(&mut stats, &['e', 't', 'a', 'o', 'i', 'n']);
        let result = tree.update(&stats, None);

        // Should unlock 7th key ('s')
        assert!(
            result.newly_unlocked.contains(&'s'),
            "newly_unlocked: {:?}",
            result.newly_unlocked
        );
    }

    #[test]
    fn test_update_returns_newly_mastered() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        // Snapshot before any key stats are added
        let before_stats = stats.clone();

        // Make first 5 keys mastered
        make_stats_mastered(&mut stats, &['e', 't', 'a', 'o', 'i']);
        let result = tree.update(&stats, Some(&before_stats));

        // The 5 keys that flipped their sticky mastered bit should be reported.
        for &ch in &['e', 't', 'a', 'o', 'i'] {
            assert!(
                result.newly_mastered.contains(&ch),
                "expected {} in newly_mastered: {:?}",
                ch,
                result.newly_mastered
            );
        }
    }

    #[test]
    fn test_find_key_branch_lowercase() {
        let result = find_key_branch('e');
        assert!(result.is_some());
        let (branch, level, pos) = result.unwrap();
        assert_eq!(branch.id, BranchId::Lowercase);
        assert_eq!(level.name_key, "skill_tree.level_frequency_order");
        assert_eq!(pos, 1); // 'e' is first in the frequency order
    }

    #[test]
    fn test_find_key_branch_capitals() {
        let result = find_key_branch('T');
        assert!(result.is_some());
        let (branch, level, pos) = result.unwrap();
        assert_eq!(branch.id, BranchId::Capitals);
        assert_eq!(level.name_key, "skill_tree.level_common_sentence_capitals");
        assert_eq!(pos, 1); // 'T' is first
    }

    #[test]
    fn test_find_key_branch_unknown() {
        assert!(find_key_branch('\x00').is_none());
    }

    #[test]
    fn test_branches_newly_available_on_lowercase_complete() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        let all_primary = tree.primary_letters().to_vec();
        make_stats_mastered(&mut stats, &all_primary);

        // Run updates to advance through progressive unlock
        let mut found_available = false;
        for _ in 0..30 {
            let result = tree.update(&stats, None);
            if !result.branches_newly_available.is_empty() {
                found_available = true;
                // Should contain exactly the 5 non-lowercase branches
                assert_eq!(result.branches_newly_available.len(), 5);
                assert!(
                    !result
                        .branches_newly_available
                        .contains(&BranchId::Lowercase)
                );
                assert!(
                    result
                        .branches_newly_available
                        .contains(&BranchId::Capitals)
                );
                assert!(result.branches_newly_available.contains(&BranchId::Numbers));
                assert!(
                    result
                        .branches_newly_available
                        .contains(&BranchId::ProsePunctuation)
                );
                assert!(
                    result
                        .branches_newly_available
                        .contains(&BranchId::Whitespace)
                );
                assert!(
                    result
                        .branches_newly_available
                        .contains(&BranchId::CodeSymbols)
                );
                break;
            }
        }
        assert!(found_available, "branches_newly_available should fire once");

        // Second update should NOT have branches_newly_available
        let result2 = tree.update(&stats, None);
        assert!(
            result2.branches_newly_available.is_empty(),
            "branches_newly_available should be empty on subsequent call"
        );
    }

    #[test]
    fn test_branches_newly_completed_on_branch_complete() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        // Set up: capitals InProgress
        tree.branch_progress_mut(BranchId::Capitals).status = BranchStatus::InProgress;

        // Make all capital letters mastered
        let all_caps: Vec<char> = ('A'..='Z').collect();
        make_stats_mastered(&mut stats, &all_caps);

        // Advance through levels
        let mut found_complete = false;
        for _ in 0..5 {
            let result = tree.update(&stats, None);
            if result
                .branches_newly_completed
                .contains(&BranchId::Capitals)
            {
                found_complete = true;
                break;
            }
        }
        assert!(
            found_complete,
            "branches_newly_completed should contain Capitals"
        );

        // Second update should not re-report
        let result2 = tree.update(&stats, None);
        assert!(
            !result2
                .branches_newly_completed
                .contains(&BranchId::Capitals),
            "should not re-report Capitals as newly completed"
        );
    }

    #[test]
    fn test_all_keys_unlocked_fires_once() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        // Set all branches to InProgress at last level with all keys mastered
        // First complete lowercase
        let all_primary = tree.primary_letters().to_vec();
        make_stats_mastered(&mut stats, &all_primary);
        for _ in 0..30 {
            tree.update(&stats, None);
        }

        // Start all branches and make their keys mastered
        for &id in &[
            BranchId::Capitals,
            BranchId::Numbers,
            BranchId::ProsePunctuation,
            BranchId::Whitespace,
            BranchId::CodeSymbols,
        ] {
            tree.start_branch(id);
            let def = get_branch_definition(id);
            for level in def.levels {
                make_stats_mastered(&mut stats, level.keys);
            }
        }

        // Advance all branches through their levels
        let mut found_all_unlocked = false;
        for _ in 0..20 {
            let result = tree.update(&stats, None);
            if result.all_keys_unlocked {
                found_all_unlocked = true;
                break;
            }
        }
        assert!(
            found_all_unlocked,
            "all_keys_unlocked should fire when last key becomes available"
        );

        // Subsequent call should not fire again
        let result = tree.update(&stats, None);
        assert!(
            !result.all_keys_unlocked,
            "all_keys_unlocked should not fire on subsequent calls"
        );
    }

    #[test]
    fn test_all_keys_mastered_fires_once() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        // Make all keys across all branches mastered
        for branch_def in ALL_BRANCHES {
            for level in branch_def.levels {
                make_stats_mastered(&mut stats, level.keys);
            }
        }

        // Complete lowercase first
        for _ in 0..30 {
            tree.update(&stats, None);
        }

        // Start and advance all other branches
        for &id in &[
            BranchId::Capitals,
            BranchId::Numbers,
            BranchId::ProsePunctuation,
            BranchId::Whitespace,
            BranchId::CodeSymbols,
        ] {
            tree.start_branch(id);
        }

        let mut found_all_mastered = false;
        for _ in 0..30 {
            let result = tree.update(&stats, None);
            if result.all_keys_mastered {
                found_all_mastered = true;
                break;
            }
        }
        assert!(
            found_all_mastered,
            "all_keys_mastered should fire when all branches complete"
        );

        // Subsequent call should not fire again
        let result = tree.update(&stats, None);
        assert!(
            !result.all_keys_mastered,
            "all_keys_mastered should not fire on subsequent calls"
        );
    }

    #[test]
    fn test_branches_newly_available_only_non_lowercase() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        let all_primary = tree.primary_letters().to_vec();
        make_stats_mastered(&mut stats, &all_primary);

        for _ in 0..30 {
            let result = tree.update(&stats, None);
            if !result.branches_newly_available.is_empty() {
                for &id in &result.branches_newly_available {
                    assert_ne!(
                        id,
                        BranchId::Lowercase,
                        "branches_newly_available should not contain Lowercase"
                    );
                }
                break;
            }
        }
    }

    #[test]
    fn test_branches_newly_available_canonical_order() {
        let mut tree = SkillTree::default();
        let mut stats = KeyStatsStore::default();

        let all_primary = tree.primary_letters().to_vec();
        make_stats_mastered(&mut stats, &all_primary);

        for _ in 0..30 {
            let result = tree.update(&stats, None);
            if !result.branches_newly_available.is_empty() {
                // Must be in canonical order: Capitals, Numbers, ProsePunctuation, Whitespace, CodeSymbols
                assert_eq!(
                    result.branches_newly_available,
                    vec![
                        BranchId::Capitals,
                        BranchId::Numbers,
                        BranchId::ProsePunctuation,
                        BranchId::Whitespace,
                        BranchId::CodeSymbols,
                    ],
                    "branches_newly_available must be in canonical order"
                );
                break;
            }
        }
    }
}
