use super::command::CommandDef;
use lru::LruCache;
use nucleo_matcher::{Matcher, Utf32String};
use std::num::NonZeroUsize;

#[derive(Clone)]
pub struct ScoredCommand {
    pub command: &'static CommandDef,
    pub score: u32,
    pub match_ranges: Vec<(usize, usize)>,
}

pub struct FuzzyMatcher {
    matcher: Matcher,
    cache: LruCache<String, Vec<ScoredCommand>>,
}

impl FuzzyMatcher {
    pub fn new() -> Self {
        let matcher = Matcher::default();
        // Cache size of 100 is safe (always non-zero)
        // This is a compile-time constant, so unwrap is safe
        let cache = LruCache::new(
            NonZeroUsize::new(100)
                .expect("Cache size must be non-zero (this is a compile-time constant)")
        );
        
        Self { matcher, cache }
    }
    
    pub fn filter_commands(
        &mut self,
        query: &str,
        commands: &[&'static CommandDef],
        workflow_context: Option<&crate::app::workflow::WorkflowContext>,
    ) -> Vec<ScoredCommand> {
        // Check cache first
        if let Some(cached) = self.cache.get(query) {
            return cached.to_vec();
        }
        
        if query.is_empty() {
            // Return all commands with neutral score
            let results: Vec<ScoredCommand> = commands
                .iter()
                .map(|cmd| ScoredCommand {
                    command: *cmd,
                    score: 1000,
                    match_ranges: Vec::new(),
                })
                .collect();
            self.cache.put(query.to_string(), results.clone());
            return results;
        }
        
        // Early termination for exact prefix matches
        let query_lower = query.to_lowercase();
        let mut exact_matches = Vec::new();
        let mut fuzzy_matches = Vec::new();
        
        for cmd in commands {
            let name_lower = cmd.name.to_lowercase();
            let key_lower = cmd.key.to_lowercase();
            
            // Check for exact prefix match (highest priority)
            if name_lower.starts_with(&query_lower) || key_lower.starts_with(&query_lower) {
                // Defensive: ensure match range is within bounds
                let match_end = query.len().min(name_lower.len()).min(cmd.name.len());
                exact_matches.push(ScoredCommand {
                    command: *cmd,
                    score: 2000,
                    match_ranges: vec![(0, match_end)],
                });
                continue;
            }
            
            // Check keywords
            let mut keyword_match = false;
            for keyword in cmd.keywords {
                if keyword.to_lowercase().starts_with(&query_lower) {
                    exact_matches.push(ScoredCommand {
                        command: *cmd,
                        score: 1800,
                        match_ranges: Vec::new(),
                    });
                    keyword_match = true;
                    break;
                }
            }
            if keyword_match {
                continue;
            }
            
            // Fuzzy match on name using nucleo-matcher
            let name_utf32 = Utf32String::from(cmd.name);
            let pattern_utf32 = Utf32String::from(query);
            // Use slice(..) to get Utf32Str from Utf32String
            let name_str = name_utf32.slice(..);
            let pattern_str = pattern_utf32.slice(..);
            
            // Try fuzzy match with indices for highlighting
            let mut indices = Vec::new();
            if let Some(score) = self.matcher.fuzzy_indices(name_str, pattern_str, &mut indices) {
                let ranges = self.indices_to_ranges_u32(&indices);
                fuzzy_matches.push(ScoredCommand {
                    command: *cmd,
                    score: score as u32,
                    match_ranges: ranges,
                });
            }
        }
        
        // Combine and sort: exact matches first, then fuzzy matches
        let mut results = exact_matches;
        results.append(&mut fuzzy_matches);
        
        // Apply context-aware scoring boost
        if let Some(context) = workflow_context {
            for result in &mut results {
                result.score += Self::get_context_boost(result.command, context);
            }
        }
        
        results.sort_by(|a, b| b.score.cmp(&a.score));
        
        // Cache results
        // Use the processed query (which may be truncated) as cache key
        // This prevents storing very long keys in cache while maintaining correctness
        // since we already checked cache with original query above
        let results_clone = results.iter().map(|r| ScoredCommand {
            command: r.command,
            score: r.score,
            match_ranges: r.match_ranges.clone(),
        }).collect();
        self.cache.put(query.to_string(), results_clone);
        
        results
    }
    
    fn indices_to_ranges_u32(&self, indices: &[u32]) -> Vec<(usize, usize)> {
        if indices.is_empty() {
            return Vec::new();
        }
        
        let mut ranges = Vec::new();
        let mut start = indices[0] as usize;
        let mut end = (indices[0] + 1) as usize;
        
        for &idx in indices.iter().skip(1) {
            let idx_usize = idx as usize;
            // Defensive: ensure indices are valid (shouldn't happen with nucleo-matcher, but be safe)
            if idx_usize == end {
                end = idx_usize + 1;
            } else {
                // Only add range if start < end (defensive check)
                if start < end {
                    ranges.push((start, end));
                }
                start = idx_usize;
                end = idx_usize + 1;
            }
        }
        // Final range
        if start < end {
            ranges.push((start, end));
        }
        ranges
    }
    
    /// Calculates a context-aware boost score for command ranking.
    /// 
    /// Commands are boosted based on the current Git workflow state to prioritize
    /// relevant actions. Higher scores appear first in the command palette.
    /// 
    /// **Scoring Strategy:**
    /// - Primary actions (most relevant): 500 points
    /// - Secondary actions (helpful): 300-400 points
    /// - Tertiary actions (related): 200 points
    /// - Unrelated actions: 0 points (no boost)
    /// 
    /// **Note:** Uses command name matching since not all actions have CommandId variants.
    fn get_context_boost(
        cmd: &CommandDef,
        context: &crate::app::workflow::WorkflowContext,
    ) -> u32 {
        use crate::app::workflow::WorkflowState;
        
        let cmd_name_lower = cmd.name.to_lowercase();
        
        match context.state {
            WorkflowState::Conflicts => Self::boost_for_conflicts(&cmd_name_lower),
            WorkflowState::RebaseInProgress => Self::boost_for_rebase(&cmd_name_lower),
            WorkflowState::MergeInProgress => Self::boost_for_merge(&cmd_name_lower),
            WorkflowState::Staging => Self::boost_for_staging(&cmd_name_lower),
            WorkflowState::Committing => Self::boost_for_committing(&cmd_name_lower),
            WorkflowState::CherryPickInProgress | WorkflowState::RevertInProgress => {
                Self::boost_for_cherry_pick_or_revert(&cmd_name_lower)
            }
            WorkflowState::Clean => Self::boost_for_clean_state(&cmd_name_lower),
        }
    }
    
    /// Boost scores for conflict resolution workflow.
    fn boost_for_conflicts(cmd_name: &str) -> u32 {
        if cmd_name.contains("conflict") || cmd_name.contains("resolve") {
            500 // Primary: resolve conflicts
        } else if cmd_name.contains("stage") {
            300 // Secondary: stage resolved files
        } else if cmd_name.contains("commit") {
            200 // Tertiary: commit after resolution
        } else {
            0
        }
    }
    
    /// Boost scores for rebase workflow.
    fn boost_for_rebase(cmd_name: &str) -> u32 {
        if cmd_name.contains("rebase") && cmd_name.contains("continue") {
            500 // Primary: continue rebase
        } else if cmd_name.contains("rebase") && cmd_name.contains("abort") {
            400 // Secondary: abort rebase
        } else if cmd_name.contains("rebase") && cmd_name.contains("todo") {
            300 // Tertiary: view rebase todo
        } else {
            0
        }
    }
    
    /// Boost scores for merge workflow.
    fn boost_for_merge(cmd_name: &str) -> u32 {
        if cmd_name.contains("commit") {
            500 // Primary: complete merge
        } else if cmd_name.contains("conflict") {
            400 // Secondary: resolve conflicts
        } else {
            0
        }
    }
    
    /// Boost scores for staging workflow.
    fn boost_for_staging(cmd_name: &str) -> u32 {
        if cmd_name.contains("commit") {
            500 // Primary: commit staged changes
        } else if cmd_name.contains("stage") {
            400 // Secondary: stage more files
        } else {
            0
        }
    }
    
    /// Boost scores for committing workflow.
    fn boost_for_committing(cmd_name: &str) -> u32 {
        if cmd_name.contains("commit") {
            500 // Primary: submit or cancel commit
        } else {
            0
        }
    }
    
    /// Boost scores for cherry-pick or revert workflow.
    fn boost_for_cherry_pick_or_revert(cmd_name: &str) -> u32 {
        if cmd_name.contains("commit") {
            500 // Primary: complete cherry-pick/revert
        } else {
            0
        }
    }
    
    /// Boost scores for clean state (no active workflow).
    fn boost_for_clean_state(cmd_name: &str) -> u32 {
        if cmd_name.contains("push") {
            300 // Secondary: push changes
        } else if cmd_name.contains("fetch") {
            200 // Tertiary: fetch updates
        } else {
            0
        }
    }
}

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