"""Affix file parser for hunspell .aff files."""

import re
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Dict, List, Optional, Set, Union

from .exceptions import AffixFileError, FlagError


class AffixType(Enum):
    """Type of affix rule."""
    PREFIX = "PFX"
    SUFFIX = "SFX"


class FlagType(Enum):
    """Type of flag format used in the affix file."""
    CHAR = "char"      # Single character flags (default)
    LONG = "long"      # Two-character flags
    NUM = "num"        # Numeric flags


@dataclass
class AffixRule:
    """Represents a single affix rule."""
    flag: str
    cross_product: bool
    strip: str
    add: str
    condition: str
    compiled_condition: Optional[re.Pattern] = None
    
    def __post_init__(self) -> None:
        """Compile the condition regex pattern."""
        if self.condition and self.condition != ".":
            try:
                # Convert hunspell condition to Python regex
                pattern = self._convert_condition_to_regex(self.condition)
                self.compiled_condition = re.compile(pattern, re.IGNORECASE)
            except re.error as e:
                raise AffixFileError(f"Invalid condition pattern '{self.condition}': {e}")
    
    def _convert_condition_to_regex(self, condition: str) -> str:
        """Convert hunspell condition pattern to Python regex."""
        if condition == ".":
            return ".*"
        
        # Handle character classes and ranges
        pattern = condition
        
        # Convert [...] character classes
        pattern = re.sub(r'\[([^\]]+)\]', r'[\1]', pattern)
        
        # Convert [^...] negated character classes
        pattern = re.sub(r'\[\^([^\]]+)\]', r'[^\1]', pattern)
        
        # Escape special regex characters that aren't meant to be regex
        # but preserve character classes
        in_class = False
        escaped = []
        i = 0
        while i < len(pattern):
            char = pattern[i]
            if char == '[':
                in_class = True
                escaped.append(char)
            elif char == ']':
                in_class = False
                escaped.append(char)
            elif not in_class and char in r'.^$*+?{}|()':
                if char == '.':
                    escaped.append('.')  # Keep . as wildcard
                else:
                    escaped.append('\\' + char)
            else:
                escaped.append(char)
            i += 1
        
        return ''.join(escaped)
    
    def matches_condition(self, word: str, affix_type: AffixType) -> bool:
        """Check if word matches the condition for this affix rule."""
        if not self.compiled_condition:
            return True  # No condition means it matches everything
        
        if affix_type == AffixType.PREFIX:
            # For prefixes, check the beginning of the word
            test_part = word[:len(self.condition)] if len(word) >= len(self.condition) else word
            return bool(self.compiled_condition.match(test_part))
        else:
            # For suffixes, check the end of the word
            test_part = word[-len(self.condition):] if len(word) >= len(self.condition) else word
            return bool(self.compiled_condition.search(test_part))


class AffixFile:
    """Parser and container for hunspell affix files."""
    
    def __init__(self) -> None:
        self.encoding: str = "UTF-8"
        self.flag_type: FlagType = FlagType.CHAR
        self.try_chars: str = ""
        self.prefixes: Dict[str, List[AffixRule]] = {}
        self.suffixes: Dict[str, List[AffixRule]] = {}
        self.affix_aliases: Dict[str, List[str]] = {}  # AF directive
        self.flag_aliases: Dict[str, str] = {}  # Support for flag aliases
        self.compound_flag: Optional[str] = None
        self.fullstrip: bool = False
        self.max_compound_suggestions: int = 3
        
    @classmethod
    def load(cls, path: Union[str, Path]) -> "AffixFile":
        """Load and parse an affix file."""
        affix_file = cls()
        path = Path(path)
        
        if not path.exists():
            raise AffixFileError(f"Affix file not found: {path}")
        
        try:
            with open(path, 'r', encoding='utf-8', errors='ignore') as f:
                affix_file._parse_file(f.readlines())
        except UnicodeDecodeError:
            # Try with latin-1 encoding as fallback
            try:
                with open(path, 'r', encoding='latin-1') as f:
                    affix_file._parse_file(f.readlines())
            except Exception as e:
                raise AffixFileError(f"Could not read affix file {path}: {e}")
        except Exception as e:
            raise AffixFileError(f"Error loading affix file {path}: {e}")
        
        return affix_file
    
    def _parse_file(self, lines: List[str]) -> None:
        """Parse the lines of an affix file."""
        line_num = 0
        current_affix_type: Optional[AffixType] = None
        current_flag: Optional[str] = None
        current_cross_product: bool = False
        remaining_entries: int = 0
        
        for line in lines:
            line_num += 1
            line = line.strip()
            
            # Skip empty lines and comments
            if not line or line.startswith('#'):
                continue
            
            try:
                # Parse affix entries first (when we're expecting them)
                if remaining_entries > 0 and current_affix_type and current_flag:
                    parts = line.split()
                    if len(parts) < 4:
                        raise AffixFileError("Invalid affix entry", line_num, line)
                    
                    # Format: TYPE FLAG strip add condition
                    # parts[0] is SFX/PFX, parts[1] is flag, parts[2] is strip, parts[3] is add, parts[4] is condition
                    if len(parts) < 5:
                        raise AffixFileError("Invalid affix entry - need at least 5 parts", line_num, line)
                    
                    entry_type = parts[0]  # SFX or PFX
                    entry_flag = parts[1]  # Should match current_flag
                    strip = parts[2] if parts[2] != '0' else ''
                    add_raw = parts[3]
                    condition = parts[4] if len(parts) > 4 else '.'
                    
                    # Handle affix flags in the add part (e.g., "0/yoc", "tion/S")
                    affix_flags = []
                    if '/' in add_raw:
                        add, flag_part = add_raw.split('/', 1)
                        affix_flags = self._parse_flags(flag_part)
                    else:
                        add = add_raw
                    
                    # Only convert "0" to empty string if there are no slash flags
                    # If there are flags like "0/yoc", keep the literal "0"
                    if add == '0' and not affix_flags:
                        add = ''
                    
                    rule = AffixRule(
                        flag=current_flag,
                        cross_product=current_cross_product,
                        strip=strip,
                        add=add,
                        condition=condition
                    )
                    
                    if current_affix_type == AffixType.PREFIX:
                        self.prefixes[current_flag].append(rule)
                    else:
                        self.suffixes[current_flag].append(rule)
                    
                    remaining_entries -= 1
                    
                    if remaining_entries == 0:
                        current_affix_type = None
                        current_flag = None
                        current_cross_product = False
                    
                    continue

                # Parse header directives
                if line.startswith('SET '):
                    self.encoding = line[4:].strip()
                    continue
                
                if line.startswith('FLAG '):
                    flag_type = line[5:].strip().lower()
                    if flag_type == 'long':
                        self.flag_type = FlagType.LONG
                    elif flag_type == 'num':
                        self.flag_type = FlagType.NUM
                    else:
                        self.flag_type = FlagType.CHAR
                    continue
                
                if line.startswith('TRY '):
                    self.try_chars = line[4:].strip()
                    continue
                
                if line.startswith('FULLSTRIP'):
                    self.fullstrip = True
                    continue
                
                if line.startswith('COMPOUNDFLAG '):
                    self.compound_flag = line[13:].strip()
                    continue
                
                # Parse AF (affix aliases) directive
                if line.startswith('AF '):
                    parts = line.split()
                    if len(parts) == 2 and parts[1].isdigit():
                        # This is the count line: AF 417
                        continue
                    elif len(parts) >= 3 and '#' in line:
                        # This is an alias definition: AF TZ # 360
                        # Extract the flags (everything before #)
                        flags_part = line[3:].split('#')[0].strip()  # Everything after "AF " before "#"
                        # Extract the comment number
                        comment_part = line.split('#')[1].strip()
                        if comment_part.isdigit():
                            alias_num = comment_part
                            self.affix_aliases[alias_num] = self._parse_flags(flags_part)
                    continue
                
                # Parse affix definitions
                if line.startswith('PFX ') or line.startswith('SFX '):
                    parts = line.split()
                    if len(parts) < 4:
                        raise AffixFileError("Invalid affix definition", line_num, line)
                    
                    affix_type_str = parts[0]
                    flag = parts[1]
                    cross_product = parts[2].upper() == 'Y'
                    count = int(parts[3])
                    
                    current_affix_type = AffixType.PREFIX if affix_type_str == 'PFX' else AffixType.SUFFIX
                    current_flag = flag
                    current_cross_product = cross_product
                    remaining_entries = count
                    
                    # Initialize the affix list for this flag
                    if current_affix_type == AffixType.PREFIX:
                        if flag not in self.prefixes:
                            self.prefixes[flag] = []
                    else:
                        if flag not in self.suffixes:
                            self.suffixes[flag] = []
                    
                    continue
                        
            except (ValueError, IndexError) as e:
                raise AffixFileError(f"Parse error: {e}", line_num, line)
    
    def _parse_flags(self, flag_string: str) -> List[str]:
        """Parse a flag string according to the flag type."""
        if not flag_string:
            return []
        
        flags = []
        
        if self.flag_type == FlagType.CHAR:
            # Each character is a flag
            flags = list(flag_string)
        elif self.flag_type == FlagType.LONG:
            # Two characters per flag
            for i in range(0, len(flag_string), 2):
                if i + 1 < len(flag_string):
                    flags.append(flag_string[i:i+2])
                else:
                    flags.append(flag_string[i])
        elif self.flag_type == FlagType.NUM:
            # Comma-separated numeric flags
            flags = [f.strip() for f in flag_string.split(',')]
        
        return flags
    
    def resolve_flag_aliases(self, flags: List[str]) -> List[str]:
        """Resolve flag aliases to actual flags."""
        resolved = []
        for flag in flags:
            if flag in self.affix_aliases:
                resolved.extend(self.affix_aliases[flag])
            else:
                resolved.append(flag)
        return resolved
    
    def get_prefix_rules(self, flag: str) -> List[AffixRule]:
        """Get all prefix rules for a given flag."""
        return self.prefixes.get(flag, [])
    
    def get_suffix_rules(self, flag: str) -> List[AffixRule]:
        """Get all suffix rules for a given flag."""
        return self.suffixes.get(flag, [])
    
    def has_flag(self, flag: str) -> bool:
        """Check if a flag exists in the affix file."""
        return flag in self.prefixes or flag in self.suffixes
    
    def get_all_flags(self) -> Set[str]:
        """Get all flags defined in the affix file."""
        return set(self.prefixes.keys()) | set(self.suffixes.keys())