"""
Magic: The Gathering Rules Engine - Keyword Validator

This module provides validation functionality for Magic keywords.
It checks if keywords are valid, finds all keywords in text, and
provides detailed analysis of keyword usage.

Usage:
    from mtg_rules_engine.validator import KeywordValidator
    
    # Check if a keyword is valid
    validator = KeywordValidator()
    if validator.is_valid_keyword("flying"):
        print("flying is a valid keyword")
    
    # Find all keywords in text
    text = "Flying creatures can't be blocked except by flying creatures."
    found = validator.find_keywords_in_text(text)
    print(f"Found keywords: {found}")
    
    # Validate card text
    card_text = "Flying creature. Haste. Flying and trample."
    errors = validator.validate_card_text(card_text)
    print(f"Validation errors: {errors}")
"""

from typing import Dict, List, Optional, Set, Tuple, Any
from .keywords import (
    ABILITY_WORDS,
    KEYWORD_ACTIONS,
    KEYWORD_ABILITIES,
    KEYWORD_VARIANTS,
    get_all_keywords,
    get_keyword_info,
    is_valid_keyword,
    get_keyword_type,
)


class KeywordValidator:
    """
    Validates and analyzes Magic keyword usage in rules text and card text.
    
    This validator can:
    - Check if a keyword is valid
    - Find all keywords in a given text
    - Validate card text for unrecognized keywords
    - Analyze keyword patterns in rules text
    """
    
    def __init__(self):
        """Initialize the validator with all keywords."""
        self._all_keywords: Set[str] = get_all_keywords()
        self._keyword_info_cache: Dict[str, Dict[str, Any]] = {}
        
    def is_valid_keyword(self, keyword: str) -> bool:
        """
        Check if a keyword is a valid Magic keyword.
        
        Args:
            keyword: The keyword to check
            
        Returns:
            True if the keyword is valid, False otherwise
        """
        return keyword.lower() in self._all_keywords
    
    def get_keyword_type(self, keyword: str) -> str:
        """
        Get the type of a keyword (ability_word, keyword_action, or keyword_ability).
        
        Args:
            keyword: The keyword to check
            
        Returns:
            The type of the keyword, or "unknown" if not found
        """
        return get_keyword_type(keyword.lower())
    
    def get_keyword_info(self, keyword: str) -> Optional[Dict[str, Any]]:
        """
        Get detailed information about a keyword.
        
        Args:
            keyword: The keyword to look up
            
        Returns:
            Dictionary with keyword information, or None if not found
        """
        keyword = keyword.lower()
        if keyword not in self._keyword_info_cache:
            self._keyword_info_cache[keyword] = get_keyword_info(keyword)
        return self._keyword_info_cache[keyword]
    
    def find_keywords_in_text(self, text: str, 
                              case_sensitive: bool = False) -> Dict[str, int]:
        """
        Find all keywords present in the given text.
        
        Args:
            text: The text to search
            case_sensitive: Whether the search should be case-sensitive
            
        Returns:
            Dictionary mapping keyword names to their counts in the text
        """
        keywords_found: Dict[str, int] = {}
        
        if case_sensitive:
            words = text.split()
            for word in words:
                # Clean punctuation from words
                clean_word = ''.join(c for c in word if c.isalnum() or c == "'")
                if clean_word in self._all_keywords:
                    keywords_found[clean_word] = keywords_found.get(clean_word, 0) + 1
        else:
            words = text.lower().split()
            for word in words:
                # Clean punctuation from words
                clean_word = ''.join(c for c in word if c.isalnum() or c == "'")
                if clean_word in self._all_keywords:
                    keywords_found[clean_word] = keywords_found.get(clean_word, 0) + 1
        
        return keywords_found
    
    def find_keyword_occurrences(self, text: str, 
                                 keyword: str,
                                 case_sensitive: bool = False) -> List[Tuple[int, int]]:
        """
        Find all occurrences of a keyword in the given text.
        
        Args:
            text: The text to search
            keyword: The keyword to find
            case_sensitive: Whether the search should be case-sensitive
            
        Returns:
            List of (start_index, end_index) tuples for each occurrence
        """
        occurrences = []
        search_text = text if case_sensitive else text.lower()
        search_keyword = keyword if case_sensitive else keyword.lower()
        
        start = 0
        while True:
            start = search_text.find(search_keyword, start)
            if start == -1:
                break
            end = start + len(search_keyword)
            occurrences.append((start, end))
            start = end
        
        return occurrences
    
    def validate_card_text(self, card_text: str, 
                          ignore_unrecognized: bool = False) -> List[Dict[str, str]]:
        """
        Validate a card's rules text for keyword usage.
        
        Args:
            card_text: The card's rules text
            ignore_unrecognized: If True, don't report unrecognized keywords
            
        Returns:
            List of validation errors (empty if no errors)
        """
        errors: List[Dict[str, str]] = []
        keywords_found = self.find_keywords_in_text(card_text)
        
        for keyword, count in keywords_found.items():
            info = self.get_keyword_info(keyword)
            if info is None:
                if not ignore_unrecognized:
                    errors.append({
                        "keyword": keyword,
                        "message": f"Unrecognized keyword: '{keyword}' (found {count} time(s))",
                        "type": "unrecognized_keyword"
                    })
            else:
                # Check for common issues
                info_type = info.get("category", "unknown")
                if info_type == "keyword_action":
                    # Check if keyword is used as a verb in the text
                    pass  # Actions are typically used as verbs
                elif info_type == "keyword_ability":
                    pass  # Abilities are typically used as adjectives or nouns
        
        return errors
    
    def analyze_rules_text(self, text: str) -> Dict[str, Any]:
        """
        Perform a comprehensive analysis of keywords in rules text.
        
        Args:
            text: The rules text to analyze
            
        Returns:
            Dictionary with analysis results
        """
        keywords_found = self.find_keywords_in_text(text)
        
        analysis = {
            "total_keywords": len(keywords_found),
            "keywords_found": keywords_found,
            "ability_words_found": {},
            "keyword_actions_found": {},
            "keyword_abilities_found": {},
        }
        
        for keyword, count in keywords_found.items():
            info = self.get_keyword_info(keyword)
            if info is None:
                continue
            
            category = info.get("category", "unknown")
            if category == "ability_word":
                analysis["ability_words_found"][keyword] = count
            elif category == "keyword_action":
                analysis["keyword_actions_found"][keyword] = count
            elif category == "keyword_ability":
                analysis["keyword_abilities_found"][keyword] = count
        
        return analysis
    
    def check_for_misspelled_keywords(self, text: str) -> List[Dict[str, Any]]:
        """
        Check for potentially misspelled keywords in text.
        
        Args:
            text: The text to check
            
        Returns:
            List of potential misspellings with suggestions
        """
        words = text.lower().split()
        misspellings = []
        
        for word in words:
            clean_word = ''.join(c for c in word if c.isalnum() or c == "'")
            if len(clean_word) < 3:
                continue
                
            if clean_word not in self._all_keywords:
                # Try to find similar keywords
                similar = self._find_similar_keywords(clean_word)
                if similar:
                    misspellings.append({
                        "word": clean_word,
                        "suggestions": similar[:3],  # Top 3 suggestions
                        "message": f"Did you mean one of: {', '.join(similar[:3])}"
                    })
        
        return misspellings
    
    def _find_similar_keywords(self, word: str) -> List[str]:
        """Find keywords that are similar to the given word."""
        similar = []
        for keyword in self._all_keywords:
            # Use simple Levenshtein distance
            distance = self._levenshtein_distance(word, keyword)
            if distance <= 3 and len(keyword) <= len(word) + 2:
                similar.append((keyword, distance))
        
        # Sort by distance and return unique keywords
        similar.sort(key=lambda x: x[1])
        return [k for k, _ in similar[:10]]
    
    @staticmethod
    def _levenshtein_distance(s1: str, s2: str) -> int:
        """Calculate the Levenshtein distance between two strings."""
        if len(s1) < len(s2):
            return KeywordValidator._levenshtein_distance(s2, s1)
        
        if len(s2) == 0:
            return len(s1)
        
        previous_row = range(len(s2) + 1)
        for i, c1 in enumerate(s1):
            current_row = [i + 1]
            for j, c2 in enumerate(s2):
                insertions = previous_row[j + 1] + 1
                deletions = current_row[j] + 1
                substitutions = previous_row[j] + (c1 != c2)
                current_row.append(min(insertions, deletions, substitutions))
            previous_row = current_row
        
        return previous_row[-1]
    
    def get_all_keywords_by_type(self) -> Dict[str, Set[str]]:
        """
        Get all keywords grouped by type.
        
        Returns:
            Dictionary mapping keyword types to sets of keywords
        """
        result = {
            "ability_words": set(ABILITY_WORDS.keys()),
            "keyword_actions": set(KEYWORD_ACTIONS.keys()),
            "keyword_abilities": set(KEYWORD_ABILITIES.keys()),
        }
        return result
    
    def export_keywords(self) -> Dict[str, Any]:
        """
        Export all keywords as a structured dictionary.
        
        Returns:
            Dictionary with all keyword data
        """
        return {
            "total_keywords": len(self._all_keywords),
            "ability_words": list(ABILITY_WORDS.keys()),
            "keyword_actions": list(KEYWORD_ACTIONS.keys()),
            "keyword_abilities": list(KEYWORD_ABILITIES.keys()),
            "keyword_variants": {
                k: v.get("variants", []) 
                for k, v in KEYWORD_ABILITIES.items() if v.get("variants")
            },
        }
