# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Renderer registry for automatic discovery and registration of renderers.

Renderers are indexed by (format, outputStyle) so that document generation
and code generation each get the correct renderer for the same format.
"""

import logging
import importlib
from typing import Dict, Type, List, Optional, Tuple
from .documentRendererBaseTemplate import BaseRenderer

logger = logging.getLogger(__name__)


class RendererRegistry:
    """Registry for automatic renderer discovery and management.
    
    Maintains separate renderer mappings per outputStyle ('document', 'code', etc.)
    so that document-generation and code-generation paths each resolve to the
    correct renderer, even when both support the same format (e.g. 'csv').
    """
    
    def __init__(self):
        # Key: (formatName, outputStyle) -> rendererClass
        self._renderers: Dict[Tuple[str, str], Type[BaseRenderer]] = {}
        self._format_mappings: Dict[str, str] = {}
        self._discovered = False
    
    def discoverRenderers(self) -> None:
        """Automatically discover and register all renderers by scanning files."""
        if self._discovered:
            return
        
        try:
            from pathlib import Path
            
            currentDir = Path(__file__).parent
            packageName = __name__.rsplit('.', 1)[0]
            
            for filePath in currentDir.glob("*.py"):
                if filePath.name in ['registry.py', 'documentRendererBaseTemplate.py', 'codeRendererBaseTemplate.py', '__init__.py']:
                    continue
                
                moduleName = filePath.stem
                
                try:
                    fullModuleName = f"{packageName}.{moduleName}"
                    module = importlib.import_module(fullModuleName)
                    
                    for attrName in dir(module):
                        attr = getattr(module, attrName)
                        if (isinstance(attr, type) and 
                            issubclass(attr, BaseRenderer) and 
                            attr != BaseRenderer and
                            hasattr(attr, 'getSupportedFormats')):
                            self._registerRendererClass(attr)
                
                except Exception as e:
                    logger.warning(f"Could not load renderer from {moduleName}: {str(e)}")
                    continue
            
            self._discovered = True
            
        except Exception as e:
            logger.error(f"Error during renderer discovery: {str(e)}")
            self._discovered = True
    
    def _registerRendererClass(self, rendererClass: Type[BaseRenderer]) -> None:
        """Register a renderer class keyed by (format, outputStyle)."""
        try:
            supportedFormats = rendererClass.getSupportedFormats()
            outputStyle = rendererClass.getOutputStyle() if hasattr(rendererClass, 'getOutputStyle') else 'document'
            priority = rendererClass.getPriority() if hasattr(rendererClass, 'getPriority') else 0
            
            for formatName in supportedFormats:
                formatKey = formatName.lower()
                registryKey = (formatKey, outputStyle)
                
                if registryKey in self._renderers:
                    existingRenderer = self._renderers[registryKey]
                    existingPriority = existingRenderer.getPriority() if hasattr(existingRenderer, 'getPriority') else 0
                    
                    if priority > existingPriority:
                        logger.debug(f"Replacing {existingRenderer.__name__} with {rendererClass.__name__} for ({formatKey}, {outputStyle}) (priority {priority} > {existingPriority})")
                        self._renderers[registryKey] = rendererClass
                    else:
                        logger.debug(f"Keeping {existingRenderer.__name__} for ({formatKey}, {outputStyle}) (priority {existingPriority} >= {priority})")
                else:
                    self._renderers[registryKey] = rendererClass
                
                # Register aliases
                if hasattr(rendererClass, 'getFormatAliases'):
                    aliases = rendererClass.getFormatAliases()
                    for alias in aliases:
                        self._format_mappings[alias.lower()] = formatKey
            
            logger.debug(f"Registered {rendererClass.__name__} for formats={supportedFormats}, style={outputStyle}, priority={priority}")
            
        except Exception as e:
            logger.error(f"Error registering renderer {rendererClass.__name__}: {str(e)}")
    
    def getRenderer(self, outputFormat: str, services=None, outputStyle: str = None) -> Optional[BaseRenderer]:
        """Get a renderer instance for the specified format and style.
        
        Args:
            outputFormat: Format name (e.g. 'csv', 'json', 'pdf')
            services: Services instance passed to renderer constructor
            outputStyle: 'document' or 'code'. If None, returns the first match
                         with preference: document > code (most callers are document path).
        """
        if not self._discovered:
            self.discoverRenderers()
        
        formatName = outputFormat.lower().strip()
        if formatName in self._format_mappings:
            formatName = self._format_mappings[formatName]
        
        rendererClass = None
        
        if outputStyle:
            # Exact match by style
            rendererClass = self._renderers.get((formatName, outputStyle))
        else:
            # No style specified — prefer 'document', then 'code', then any
            for style in ['document', 'code']:
                rendererClass = self._renderers.get((formatName, style))
                if rendererClass:
                    break
            # Fallback: check any registered style
            if not rendererClass:
                for key, cls in self._renderers.items():
                    if key[0] == formatName:
                        rendererClass = cls
                        break
        
        if rendererClass:
            try:
                return rendererClass(services=services)
            except Exception as e:
                logger.error(f"Error creating renderer instance for {formatName}: {str(e)}")
                return None
        
        logger.warning(f"No renderer found for format={outputFormat}, style={outputStyle}")
        return None
    
    def getSupportedFormats(self) -> List[str]:
        """Get list of all supported formats."""
        if not self._discovered:
            self.discoverRenderers()
        
        formats = set()
        for (fmt, _style) in self._renderers.keys():
            formats.add(fmt)
        formats.update(self._format_mappings.keys())
        return sorted(formats)
    
    def getRendererInfo(self) -> Dict[str, Dict[str, str]]:
        """Get information about all registered renderers."""
        if not self._discovered:
            self.discoverRenderers()
        
        info = {}
        for (formatName, style), rendererClass in self._renderers.items():
            key = f"{formatName}:{style}"
            info[key] = {
                'class_name': rendererClass.__name__,
                'module': rendererClass.__module__,
                'outputStyle': style,
                'description': getattr(rendererClass, '__doc__', 'No description').strip().split('\n')[0] if rendererClass.__doc__ else 'No description'
            }
        
        return info
    
    def getOutputStyle(self, outputFormat: str) -> Optional[str]:
        """
        Get the output style classification for a given format.
        When both 'document' and 'code' renderers exist for a format,
        returns the default ('document') since this is called during document generation.
        """
        if not self._discovered:
            self.discoverRenderers()
        
        formatName = outputFormat.lower().strip()
        if formatName in self._format_mappings:
            formatName = self._format_mappings[formatName]
        
        # Check document first, then code
        for style in ['document', 'code']:
            rendererClass = self._renderers.get((formatName, style))
            if rendererClass:
                try:
                    return rendererClass.getOutputStyle(formatName)
                except Exception:
                    pass
        
        # Fallback: any style
        for key, rendererClass in self._renderers.items():
            if key[0] == formatName:
                try:
                    return rendererClass.getOutputStyle(formatName)
                except Exception:
                    pass
        
        logger.warning(f"No renderer found for format: {outputFormat}, cannot determine output style")
        return None


# Global registry instance
_registry = RendererRegistry()


def getRenderer(outputFormat: str, services=None, outputStyle: str = None) -> Optional[BaseRenderer]:
    """Get a renderer instance for the specified format and style.
    
    Args:
        outputFormat: Format name (e.g. 'csv', 'json', 'pdf')
        services: Services instance
        outputStyle: 'document' or 'code'. If None, prefers document renderer.
    """
    return _registry.getRenderer(outputFormat, services, outputStyle=outputStyle)


def getSupportedFormats() -> List[str]:
    """Get list of all supported formats."""
    return _registry.getSupportedFormats()


def getRendererInfo() -> Dict[str, Dict[str, str]]:
    """Get information about all registered renderers."""
    return _registry.getRendererInfo()


def getOutputStyle(outputFormat: str) -> Optional[str]:
    """Get the output style classification for a given format."""
    return _registry.getOutputStyle(outputFormat)
