"""
Abstract base class for Information Space renderers.

This module provides the foundation for rendering resolved markdown content
to various output formats, with support for theming and caching.
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Dict, Any, Optional, List, Set
import hashlib


class RenderFormat(Enum):
    """Supported output formats for rendering."""

    HTML = "html"
    PDF = "pdf"  # Future
    DOCX = "docx"  # Future
    LATEX = "latex"  # Future


@dataclass
class ThemeConfig:
    """
    Configuration for rendering themes.

    Attributes:
        name: Theme name (e.g., 'github', 'academic', 'minimal')
        layers: List of theme layers to combine (highest priority first)
        custom_css: Optional custom CSS to append
        custom_properties: Theme property overrides
    """

    name: str = "default"
    layers: List[str] = field(default_factory=lambda: ["basic"])
    custom_css: Optional[str] = None
    custom_properties: Dict[str, Any] = field(default_factory=dict)


@dataclass
class RenderConfig:
    """
    Configuration for rendering operations.

    Attributes:
        format: Output format
        theme: Theme configuration
        include_toc: Whether to include table of contents
        highlight_code: Whether to apply syntax highlighting
        image_max_width: Maximum image width
        image_max_height: Maximum image height
        embed_assets: Whether to embed assets inline
        base_url: Base URL for relative links
    """

    format: RenderFormat = RenderFormat.HTML
    theme: ThemeConfig = field(default_factory=ThemeConfig)
    include_toc: bool = False
    highlight_code: bool = True
    image_max_width: str = "100%"
    image_max_height: str = "auto"
    embed_assets: bool = False
    base_url: Optional[str] = None

    # Advanced options
    sanitize_html: bool = True
    link_target_blank: bool = True
    generate_heading_ids: bool = True


@dataclass
class RenderResult:
    """
    Result of a rendering operation.

    Attributes:
        content: The rendered content
        format: The output format
        content_hash: Hash of the rendered content
        source_hash: Hash of the source content
        document_id: ID of the rendered document
        space_id: ID of the containing space
        dependencies: Document IDs this render depends on
        metadata: Additional rendering metadata
    """

    content: str
    format: RenderFormat
    content_hash: str
    source_hash: str
    document_id: str
    space_id: str
    dependencies: Set[str] = field(default_factory=set)
    metadata: Dict[str, Any] = field(default_factory=dict)

    @staticmethod
    def compute_hash(content: str) -> str:
        """Compute a hash of content."""
        return hashlib.sha256(content.encode('utf-8')).hexdigest()[:16]


class SpaceRenderer(ABC):
    """
    Abstract base class for space content renderers.

    Renderers transform resolved markdown content into output formats
    like HTML, PDF, or other document formats.
    """

    def __init__(self, config: Optional[RenderConfig] = None):
        """
        Initialize the renderer.

        Args:
            config: Rendering configuration
        """
        self.config = config or RenderConfig()

    @property
    @abstractmethod
    def supported_formats(self) -> List[RenderFormat]:
        """Return list of formats this renderer supports."""
        pass

    @abstractmethod
    def render(
        self,
        content: str,
        document_id: str,
        space_id: str,
        dependencies: Optional[Set[str]] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> RenderResult:
        """
        Render markdown content.

        Args:
            content: Resolved markdown content to render
            document_id: ID of the document being rendered
            space_id: ID of the containing space
            dependencies: Document IDs this content depends on
            metadata: Additional metadata for rendering

        Returns:
            RenderResult with rendered content
        """
        pass

    def supports_format(self, format: RenderFormat) -> bool:
        """Check if this renderer supports a format."""
        return format in self.supported_formats

    def validate_config(self) -> List[str]:
        """
        Validate the current configuration.

        Returns:
            List of validation errors (empty if valid)
        """
        errors = []

        if self.config.format not in self.supported_formats:
            errors.append(
                f"Format {self.config.format.value} not supported. "
                f"Supported: {[f.value for f in self.supported_formats]}"
            )

        return errors


class CompositeRenderer:
    """
    Manages multiple renderers and delegates to the appropriate one.

    Allows rendering to different formats using a unified interface.
    """

    def __init__(self):
        """Initialize the composite renderer."""
        self._renderers: Dict[RenderFormat, SpaceRenderer] = {}

    def register(self, renderer: SpaceRenderer) -> None:
        """
        Register a renderer for its supported formats.

        Args:
            renderer: Renderer to register
        """
        for format in renderer.supported_formats:
            self._renderers[format] = renderer

    def get_renderer(self, format: RenderFormat) -> Optional[SpaceRenderer]:
        """Get the renderer for a format."""
        return self._renderers.get(format)

    def render(
        self,
        content: str,
        document_id: str,
        space_id: str,
        format: RenderFormat = RenderFormat.HTML,
        dependencies: Optional[Set[str]] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> RenderResult:
        """
        Render content using the appropriate renderer.

        Args:
            content: Markdown content to render
            document_id: Document ID
            space_id: Space ID
            format: Target output format
            dependencies: Document dependencies
            metadata: Additional metadata

        Returns:
            RenderResult

        Raises:
            ValueError: If no renderer registered for format
        """
        renderer = self._renderers.get(format)
        if not renderer:
            raise ValueError(f"No renderer registered for format: {format.value}")

        return renderer.render(
            content=content,
            document_id=document_id,
            space_id=space_id,
            dependencies=dependencies,
            metadata=metadata,
        )

    def supported_formats(self) -> List[RenderFormat]:
        """Get all supported formats."""
        return list(self._renderers.keys())
