from __future__ import annotations
from typing import List, Dict, Optional, Tuple, Union, Any
from enum import Enum
from .block import Block, BlockType, Heading, HeadingLevel, Paragraph
from ..style import Font, FontWeight, FontStyle, TextDecoration
from ..style.abstract_style import AbstractStyle, AbstractStyleRegistry, FontFamily, FontSize
from ..style.concrete_style import ConcreteStyleRegistry, RenderingContext, StyleResolver
from ..core import FontRegistry, MetadataContainer


class MetadataType(Enum):
    """Types of metadata that can be associated with a document"""
    TITLE = 1
    AUTHOR = 2
    DESCRIPTION = 3
    KEYWORDS = 4
    LANGUAGE = 5
    PUBLICATION_DATE = 6
    MODIFIED_DATE = 7
    PUBLISHER = 8
    IDENTIFIER = 9
    COVER_IMAGE = 10
    CUSTOM = 100


class Document(FontRegistry, MetadataContainer):
    """
    Abstract representation of a complete document like an HTML page or an ebook.
    This class manages the logical structure of the document without rendering concerns.

    Uses FontRegistry mixin for font caching.
    Uses MetadataContainer mixin for metadata management.
    """

    def __init__(
            self,
            title: Optional[str] = None,
            language: str = "en-US",
            default_style=None):
        """
        Initialize a new document.

        Args:
            title: The document title
            language: The document language code
            default_style: Optional default style for child blocks
        """
        super().__init__()
        self._blocks: List[Block] = []
        self._anchors: Dict[str, Block] = {}  # Named anchors for navigation
        self._resources: Dict[str, Any] = {}  # External resources like images
        self._stylesheets: List[Dict[str, Any]] = []  # CSS stylesheets
        self._scripts: List[str] = []  # JavaScript code

        # Style management with new abstract/concrete system
        self._abstract_style_registry = AbstractStyleRegistry()
        self._rendering_context = RenderingContext(default_language=language)
        self._style_resolver = StyleResolver(self._rendering_context)
        self._concrete_style_registry = ConcreteStyleRegistry(self._style_resolver)

        # Set default style
        if default_style is None:
            # Create a default abstract style
            default_style = self._abstract_style_registry.default_style
        elif isinstance(default_style, Font):
            # Convert Font to AbstractStyle for backward compatibility
            default_style = AbstractStyle(
                font_family=FontFamily.SERIF,  # Default assumption
                font_size=default_style.font_size,
                color=default_style.colour,
                language=default_style.language
            )
            style_id, default_style = self._abstract_style_registry.get_or_create_style(
                default_style)
        self._default_style = default_style

        # Set basic metadata
        if title:
            self.set_metadata(MetadataType.TITLE, title)
        self.set_metadata(MetadataType.LANGUAGE, language)

    @property
    def blocks(self) -> List[Block]:
        """Get the top-level blocks in this document"""
        return self._blocks

    @property
    def default_style(self):
        """Get the default style for this document"""
        return self._default_style

    @default_style.setter
    def default_style(self, style):
        """Set the default style for this document"""
        self._default_style = style

    def add_block(self, block: Block):
        """
        Add a block to this document.

        Args:
            block: The block to add
        """
        self._blocks.append(block)

    def create_paragraph(self, style=None) -> Paragraph:
        """
        Create a new paragraph and add it to this document.

        Args:
            style: Optional style override. If None, inherits from document

        Returns:
            The newly created Paragraph object
        """
        if style is None:
            style = self._default_style
        paragraph = Paragraph(style)
        self.add_block(paragraph)
        return paragraph

    def create_heading(
            self,
            level: HeadingLevel = HeadingLevel.H1,
            style=None) -> Heading:
        """
        Create a new heading and add it to this document.

        Args:
            level: The heading level
            style: Optional style override. If None, inherits from document

        Returns:
            The newly created Heading object
        """
        if style is None:
            style = self._default_style
        heading = Heading(level, style)
        self.add_block(heading)
        return heading

    def create_chapter(
            self,
            title: Optional[str] = None,
            level: int = 1,
            style=None) -> 'Chapter':
        """
        Create a new chapter with inherited style.

        Args:
            title: The chapter title
            level: The chapter level
            style: Optional style override. If None, inherits from document

        Returns:
            The newly created Chapter object
        """
        if style is None:
            style = self._default_style
        return Chapter(title, level, style)

    # set_metadata() and get_metadata() are provided by MetadataContainer mixin

    def add_anchor(self, name: str, target: Block):
        """
        Add a named anchor to this document.

        Args:
            name: The anchor name
            target: The target block
        """
        self._anchors[name] = target

    def get_anchor(self, name: str) -> Optional[Block]:
        """
        Get a named anchor from this document.

        Args:
            name: The anchor name

        Returns:
            The target block, or None if not found
        """
        return self._anchors.get(name)

    def add_resource(self, name: str, resource: Any):
        """
        Add a resource to this document.

        Args:
            name: The resource name
            resource: The resource data
        """
        self._resources[name] = resource

    def get_resource(self, name: str) -> Optional[Any]:
        """
        Get a resource from this document.

        Args:
            name: The resource name

        Returns:
            The resource data, or None if not found
        """
        return self._resources.get(name)

    def add_stylesheet(self, stylesheet: Dict[str, Any]):
        """
        Add a stylesheet to this document.

        Args:
            stylesheet: The stylesheet data
        """
        self._stylesheets.append(stylesheet)

    def add_script(self, script: str):
        """
        Add a script to this document.

        Args:
            script: The script code
        """
        self._scripts.append(script)

    def get_title(self) -> Optional[str]:
        """
        Get the document title.

        Returns:
            The document title, or None if not set
        """
        return self.get_metadata(MetadataType.TITLE)

    def set_title(self, title: str):
        """
        Set the document title.

        Args:
            title: The document title
        """
        self.set_metadata(MetadataType.TITLE, title)

    @property
    def title(self) -> Optional[str]:
        """
        Get the document title as a property.

        Returns:
            The document title, or None if not set
        """
        return self.get_title()

    @title.setter
    def title(self, title: str):
        """
        Set the document title as a property.

        Args:
            title: The document title
        """
        self.set_title(title)

    def find_blocks_by_type(self, block_type: BlockType) -> List[Block]:
        """
        Find all blocks of a specific type.

        Args:
            block_type: The type of blocks to find

        Returns:
            A list of matching blocks
        """
        result = []

        def _find_recursive(blocks: List[Block]):
            for block in blocks:
                if block.block_type == block_type:
                    result.append(block)

                # Check for child blocks based on block type
                if hasattr(block, '_blocks'):
                    _find_recursive(block._blocks)
                elif hasattr(block, '_items') and isinstance(block._items, list):
                    _find_recursive(block._items)

        _find_recursive(self._blocks)
        return result

    def find_headings(self) -> List[Heading]:
        """
        Find all headings in the document.

        Returns:
            A list of heading blocks
        """
        blocks = self.find_blocks_by_type(BlockType.HEADING)
        return [block for block in blocks if isinstance(block, Heading)]

    def generate_table_of_contents(self) -> List[Tuple[int, str, Block]]:
        """
        Generate a table of contents from headings.

        Returns:
            A list of tuples containing (level, title, heading_block)
        """
        headings = self.find_headings()

        toc = []
        for heading in headings:
            # Extract text from the heading
            title = ""
            for _, word in heading.words_iter():
                title += word.text + " "
            title = title.strip()

            # Add to TOC
            level = heading.level.value  # Get numeric value from HeadingLevel enum
            toc.append((level, title, heading))

        return toc

    def get_or_create_style(self,
                            font_family: FontFamily = FontFamily.SERIF,
                            font_size: Union[FontSize, int] = FontSize.MEDIUM,
                            font_weight: FontWeight = FontWeight.NORMAL,
                            font_style: FontStyle = FontStyle.NORMAL,
                            text_decoration: TextDecoration = TextDecoration.NONE,
                            color: Union[str, Tuple[int, int, int]] = "black",
                            background_color: Optional[Union[str, Tuple[int, int, int, int]]] = None,
                            language: str = "en-US",
                            **kwargs) -> Tuple[str, AbstractStyle]:
        """
        Get or create an abstract style with the specified properties.

        Args:
            font_family: Semantic font family
            font_size: Font size (semantic or numeric)
            font_weight: Font weight
            font_style: Font style
            text_decoration: Text decoration
            color: Text color (name or RGB tuple)
            background_color: Background color
            language: Language code
            **kwargs: Additional style properties

        Returns:
            Tuple of (style_id, AbstractStyle)
        """
        abstract_style = AbstractStyle(
            font_family=font_family,
            font_size=font_size,
            font_weight=font_weight,
            font_style=font_style,
            text_decoration=text_decoration,
            color=color,
            background_color=background_color,
            language=language,
            **kwargs
        )

        return self._abstract_style_registry.get_or_create_style(abstract_style)

    def get_font_for_style(self, abstract_style: AbstractStyle) -> Font:
        """
        Get a Font object for an AbstractStyle (for rendering).

        Args:
            abstract_style: The abstract style to get a font for

        Returns:
            Font object ready for rendering
        """
        return self._concrete_style_registry.get_font(abstract_style)

    def update_rendering_context(self, **kwargs):
        """
        Update the rendering context (user preferences, device settings, etc.).

        Args:
            **kwargs: Context properties to update (base_font_size, font_scale_factor, etc.)
        """
        self._style_resolver.update_context(**kwargs)

    def get_style_registry(self) -> AbstractStyleRegistry:
        """Get the abstract style registry for this document."""
        return self._abstract_style_registry

    def get_concrete_style_registry(self) -> ConcreteStyleRegistry:
        """Get the concrete style registry for this document."""
        return self._concrete_style_registry

    # get_or_create_font() is provided by FontRegistry mixin


class Chapter(FontRegistry, MetadataContainer):
    """
    Represents a chapter or section in a document.
    A chapter contains a sequence of blocks and has metadata.

    Uses FontRegistry mixin for font caching with parent delegation.
    Uses MetadataContainer mixin for metadata management.
    """

    def __init__(
            self,
            title: Optional[str] = None,
            level: int = 1,
            style=None,
            parent=None):
        """
        Initialize a new chapter.

        Args:
            title: The chapter title
            level: The chapter level (1 = top level, 2 = subsection, etc.)
            style: Optional default style for child blocks
            parent: Parent container (e.g., Document or Book)
        """
        super().__init__()
        self._title = title
        self._level = level
        self._blocks: List[Block] = []
        self._style = style
        self._parent = parent

    @property
    def title(self) -> Optional[str]:
        """Get the chapter title"""
        return self._title

    @title.setter
    def title(self, title: str):
        """Set the chapter title"""
        self._title = title

    @property
    def level(self) -> int:
        """Get the chapter level"""
        return self._level

    @property
    def blocks(self) -> List[Block]:
        """Get the blocks in this chapter"""
        return self._blocks

    @property
    def style(self):
        """Get the default style for this chapter"""
        return self._style

    @style.setter
    def style(self, style):
        """Set the default style for this chapter"""
        self._style = style

    def add_block(self, block: Block):
        """
        Add a block to this chapter.

        Args:
            block: The block to add
        """
        self._blocks.append(block)

    def create_paragraph(self, style=None) -> Paragraph:
        """
        Create a new paragraph and add it to this chapter.

        Args:
            style: Optional style override. If None, inherits from chapter

        Returns:
            The newly created Paragraph object
        """
        if style is None:
            style = self._style
        paragraph = Paragraph(style)
        self.add_block(paragraph)
        return paragraph

    def create_heading(
            self,
            level: HeadingLevel = HeadingLevel.H1,
            style=None) -> Heading:
        """
        Create a new heading and add it to this chapter.

        Args:
            level: The heading level
            style: Optional style override. If None, inherits from chapter

        Returns:
            The newly created Heading object
        """
        if style is None:
            style = self._style
        heading = Heading(level, style)
        self.add_block(heading)
        return heading

    # set_metadata() and get_metadata() are provided by MetadataContainer mixin
    # get_or_create_font() is provided by FontRegistry mixin


class Book(Document):
    """
    Abstract representation of an ebook.
    A book is a document that contains chapters.
    """

    def __init__(self, title: Optional[str] = None, author: Optional[str] = None,
                 language: str = "en-US", default_style=None):
        """
        Initialize a new book.

        Args:
            title: The book title
            author: The book author
            language: The book language code
            default_style: Optional default style for child chapters and blocks
        """
        super().__init__(title, language, default_style)
        self._chapters: List[Chapter] = []

        if author:
            self.set_metadata(MetadataType.AUTHOR, author)

    @property
    def chapters(self) -> List[Chapter]:
        """Get the chapters in this book"""
        return self._chapters

    def add_chapter(self, chapter: Chapter):
        """
        Add a chapter to this book.

        Args:
            chapter: The chapter to add
        """
        self._chapters.append(chapter)

    def create_chapter(
            self,
            title: Optional[str] = None,
            level: int = 1,
            style=None) -> Chapter:
        """
        Create and add a new chapter with inherited style.

        Args:
            title: The chapter title
            level: The chapter level
            style: Optional style override. If None, inherits from book

        Returns:
            The new chapter
        """
        if style is None:
            style = self._default_style
        chapter = Chapter(title, level, style)
        self.add_chapter(chapter)
        return chapter

    def get_author(self) -> Optional[str]:
        """
        Get the book author.

        Returns:
            The book author, or None if not set
        """
        return self.get_metadata(MetadataType.AUTHOR)

    def set_author(self, author: str):
        """
        Set the book author.

        Args:
            author: The book author
        """
        self.set_metadata(MetadataType.AUTHOR, author)

    def generate_table_of_contents(self) -> List[Tuple[int, str, Chapter]]:
        """
        Generate a table of contents from chapters.

        Returns:
            A list of tuples containing (level, title, chapter)
        """
        toc = []
        for chapter in self._chapters:
            if chapter.title:
                toc.append((chapter.level, chapter.title, chapter))

        return toc
