"""
ANSI Terminal Renderer.

Handles all terminal output with ANSI escape codes for colors and cursor control.
"""

import sys
from typing import Any, Dict, List, Optional

# ANSI Color codes
COLORS = {
    'black': '\033[30m',
    'red': '\033[31m',
    'green': '\033[32m',
    'yellow': '\033[33m',
    'blue': '\033[34m',
    'magenta': '\033[35m',
    'cyan': '\033[36m',
    'white': '\033[37m',
    'gray': '\033[90m',
    'bright_red': '\033[91m',
    'bright_green': '\033[92m',
    'bright_yellow': '\033[93m',
    'bright_blue': '\033[94m',
    'bright_magenta': '\033[95m',
    'bright_cyan': '\033[96m',
    'bright_white': '\033[97m',
}

# Background colors
BG_COLORS = {
    'black': '\033[40m',
    'red': '\033[41m',
    'green': '\033[42m',
    'yellow': '\033[43m',
    'blue': '\033[44m',
    'magenta': '\033[45m',
    'cyan': '\033[46m',
    'white': '\033[47m',
}

# ANSI control codes
RESET = '\033[0m'
BOLD = '\033[1m'
DIM = '\033[2m'
ITALIC = '\033[3m'
UNDERLINE = '\033[4m'
BLINK = '\033[5m'
REVERSE = '\033[7m'
HIDDEN = '\033[8m'


class ANSIRenderer:
    """
    Terminal renderer using ANSI escape codes.
    
    Provides methods for clearing screen, moving cursor, and rendering
    colored text in the terminal.
    """
    
    def __init__(self):
        self.alternate_buffer = False
        self._setup_terminal()
    
    def _setup_terminal(self) -> None:
        """Setup terminal for animation."""
        # Try to enable ANSI escape codes on Windows
        if sys.platform == 'win32':
            try:
                import ctypes
                kernel32 = ctypes.windll.kernel32
                kernel32.SetConsoleMode(
                    kernel32.GetStdHandle(-11), 
                    7  # ENABLE_VIRTUAL_TERMINAL_PROCESSING
                )
            except Exception:
                pass
    
    def clear_screen(self) -> str:
        """Return escape code to clear screen and move cursor to home."""
        return '\033[2J\033[H'
    
    def clear_line(self) -> str:
        """Return escape code to clear current line."""
        return '\033[2K'
    
    def move_cursor(self, x: int, y: int) -> str:
        """Return escape code to move cursor to position (1-indexed)."""
        return f'\033[{y};{x}H'
    
    def hide_cursor(self) -> str:
        """Return escape code to hide cursor."""
        return '\033[?25l'
    
    def show_cursor(self) -> str:
        """Return escape code to show cursor."""
        return '\033[?25h'
    
    def enter_alternate_buffer(self) -> str:
        """Enter alternate screen buffer (preserves main terminal)."""
        self.alternate_buffer = True
        return '\033[?1049h'
    
    def exit_alternate_buffer(self) -> str:
        """Exit alternate screen buffer."""
        self.alternate_buffer = False
        return '\033[?1049l'
    
    def reset_terminal(self) -> str:
        """Reset terminal to normal state."""
        output = self.show_cursor()
        if self.alternate_buffer:
            output += self.exit_alternate_buffer()
        output += RESET
        return output
    
    def color(self, text: str, fg: Optional[str] = None, 
              bg: Optional[str] = None, bold: bool = False) -> str:
        """Apply colors and styles to text."""
        output = ""
        
        if bold:
            output += BOLD
        if fg and fg in COLORS:
            output += COLORS[fg]
        if bg and bg in BG_COLORS:
            output += BG_COLORS[bg]
        
        output += text + RESET
        return output
    
    def color_256(self, text: str, fg: Optional[int] = None, 
                  bg: Optional[int] = None) -> str:
        """Apply 256-color palette to text."""
        output = ""
        
        if fg is not None:
            output += f'\033[38;5;{fg}m'
        if bg is not None:
            output += f'\033[48;5;{bg}m'
        
        output += text + RESET
        return output
    
    def color_rgb(self, text: str, fg: Optional[tuple] = None, 
                  bg: Optional[tuple] = None) -> str:
        """Apply true color (24-bit RGB) to text."""
        output = ""
        
        if fg is not None:
            r, g, b = fg
            output += f'\033[38;2;{r};{g};{b}m'
        if bg is not None:
            r, g, b = bg
            output += f'\033[48;2;{r};{g};{b}m'
        
        output += text + RESET
        return output
    
    def render_buffer(self, buffer: List[List[str]], 
                      particles: List[Any],
                      theme: Dict[str, Any]) -> str:
        """
        Render a 2D buffer to a string with colors applied.
        
        Args:
            buffer: 2D list of characters
            particles: List of particles for color information
            theme: Theme dictionary for color mapping
        
        Returns:
            Rendered string with ANSI codes
        """
        # Get theme colors or use defaults
        default_fg = theme.get('foreground', 'white')
        bg_color = theme.get('background', None)
        
        # Create particle position lookup for efficient coloring
        particle_map: Dict[tuple, str] = {}
        for particle in particles:
            key = (int(particle.x), int(particle.y))
            if hasattr(particle, 'color'):
                particle_map[key] = particle.color
        
        output_lines = []
        
        for y, row in enumerate(buffer):
            line = ""
            current_color = None
            
            for x, char in enumerate(row):
                # Get color for this position
                particle_color = particle_map.get((x, y))
                
                if particle_color and particle_color != current_color:
                    # Apply new color
                    if particle_color in COLORS:
                        line += COLORS[particle_color]
                    current_color = particle_color
                elif char == ' ' and current_color:
                    line += RESET
                    current_color = None
                
                line += char
            
            # Reset at end of line if needed
            if current_color:
                line += RESET
            
            output_lines.append(line)
        
        # Join with newlines
        rendered = '\n'.join(output_lines)
        
        # Apply background color if specified
        if bg_color:
            rendered = f"{BG_COLORS.get(bg_color, '')}{rendered}{RESET}"
        
        return rendered
    
    def render_status_bar(self, text: str, width: int, 
                          theme: Dict[str, Any]) -> str:
        """Render a status bar at the bottom of the screen."""
        # Pad or truncate to width
        text = text[:width].ljust(width)
        
        # Get status bar colors from theme
        fg = theme.get('status_fg', 'black')
        bg = theme.get('status_bg', 'white')
        
        return self.color(text, fg=fg, bg=bg)
    
    def gradient_text(self, text: str, 
                      start_color: tuple, 
                      end_color: tuple) -> str:
        """Apply a gradient color effect to text."""
        if not text:
            return ""
        
        output = ""
        length = len(text)
        
        for i, char in enumerate(text):
            # Interpolate color
            ratio = i / max(1, length - 1)
            r = int(start_color[0] + (end_color[0] - start_color[0]) * ratio)
            g = int(start_color[1] + (end_color[1] - start_color[1]) * ratio)
            b = int(start_color[2] + (end_color[2] - start_color[2]) * ratio)
            
            output += self.color_rgb(char, fg=(r, g, b))
        
        return output
