"""
High-performance ereader layout manager with sub-second page rendering.

This module provides the main interface for ereader applications, combining
position tracking, font scaling, chapter navigation, and intelligent page buffering
into a unified, easy-to-use API.
"""

from __future__ import annotations
from typing import List, Dict, Optional, Tuple, Any, Callable
import json
import logging
from pathlib import Path

from .ereader_layout import RenderingPosition, ChapterNavigator, ChapterInfo
from .page_buffer import BufferedPageRenderer
from pyWebLayout.abstract.block import Block, HeadingLevel, Image, BlockType
from pyWebLayout.concrete.page import Page
from pyWebLayout.concrete.image import RenderableImage
from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.style.fonts import BundledFont
from pyWebLayout.layout.document_layouter import image_layouter

logger = logging.getLogger(__name__)


class BookmarkManager:
    """
    Manages bookmarks and reading position persistence for ereader applications.
    """

    def __init__(self, document_id: str, bookmarks_dir: str = "bookmarks"):
        """
        Initialize bookmark manager.

        Args:
            document_id: Unique identifier for the document
            bookmarks_dir: Directory to store bookmark files
        """
        self.document_id = document_id
        self.bookmarks_dir = Path(bookmarks_dir)
        self.bookmarks_dir.mkdir(exist_ok=True)

        self.bookmarks_file = self.bookmarks_dir / f"{document_id}_bookmarks.json"
        self.position_file = self.bookmarks_dir / f"{document_id}_position.json"

        self._bookmarks: Dict[str, RenderingPosition] = {}
        self._load_bookmarks()

    def _load_bookmarks(self):
        """Load bookmarks from file"""
        if self.bookmarks_file.exists():
            try:
                with open(self.bookmarks_file, 'r') as f:
                    data = json.load(f)
                    self._bookmarks = {
                        name: RenderingPosition.from_dict(pos_data)
                        for name, pos_data in data.items()
                    }
            except Exception as e:
                print(f"Failed to load bookmarks: {e}")
                self._bookmarks = {}

    def _save_bookmarks(self):
        """Save bookmarks to file"""
        try:
            data = {
                name: position.to_dict()
                for name, position in self._bookmarks.items()
            }
            with open(self.bookmarks_file, 'w') as f:
                json.dump(data, f, indent=2)
        except Exception as e:
            print(f"Failed to save bookmarks: {e}")

    def add_bookmark(self, name: str, position: RenderingPosition):
        """
        Add a bookmark at the given position.

        Args:
            name: Bookmark name
            position: Position to bookmark
        """
        self._bookmarks[name] = position
        self._save_bookmarks()

    def remove_bookmark(self, name: str) -> bool:
        """
        Remove a bookmark.

        Args:
            name: Bookmark name to remove

        Returns:
            True if bookmark was removed, False if not found
        """
        if name in self._bookmarks:
            del self._bookmarks[name]
            self._save_bookmarks()
            return True
        return False

    def get_bookmark(self, name: str) -> Optional[RenderingPosition]:
        """
        Get a bookmark position.

        Args:
            name: Bookmark name

        Returns:
            Bookmark position or None if not found
        """
        return self._bookmarks.get(name)

    def list_bookmarks(self) -> List[Tuple[str, RenderingPosition]]:
        """
        Get all bookmarks.

        Returns:
            List of (name, position) tuples
        """
        return list(self._bookmarks.items())

    def save_reading_position(self, position: RenderingPosition):
        """
        Save the current reading position.

        Args:
            position: Current reading position
        """
        try:
            with open(self.position_file, 'w') as f:
                json.dump(position.to_dict(), f, indent=2)
        except Exception as e:
            print(f"Failed to save reading position: {e}")

    def load_reading_position(self) -> Optional[RenderingPosition]:
        """
        Load the last reading position.

        Returns:
            Last reading position or None if not found
        """
        if self.position_file.exists():
            try:
                with open(self.position_file, 'r') as f:
                    data = json.load(f)
                    return RenderingPosition.from_dict(data)
            except Exception as e:
                print(f"Failed to load reading position: {e}")
        return None


class EreaderLayoutManager:
    """
    High-level ereader layout manager providing a complete interface for ereader applications.

    Features:
    - Sub-second page rendering with intelligent buffering
    - Font scaling support
    - Dynamic font family switching (Sans, Serif, Monospace)
    - Chapter navigation
    - Bookmark management
    - Position persistence
    - Progress tracking
    """

    def __init__(self,
                 blocks: List[Block],
                 page_size: Tuple[int, int],
                 document_id: str = "default",
                 buffer_size: int = 5,
                 page_style: Optional[PageStyle] = None,
                 bookmarks_dir: str = "bookmarks"):
        """
        Initialize the ereader layout manager.

        Args:
            blocks: Document blocks to render
            page_size: Page size (width, height) in pixels
            document_id: Unique identifier for the document (for bookmarks/position)
            buffer_size: Number of pages to cache in each direction
            page_style: Custom page styling (uses default if None)
            bookmarks_dir: Directory to store bookmark files
        """
        self.blocks = blocks
        self.page_size = page_size
        self.document_id = document_id

        # Initialize page style
        if page_style is None:
            page_style = PageStyle()
        self.page_style = page_style

        # Initialize core components
        self.renderer = BufferedPageRenderer(blocks, page_style, buffer_size, page_size)
        self.chapter_navigator = ChapterNavigator(blocks)
        self.bookmark_manager = BookmarkManager(document_id, bookmarks_dir)

        # Current state
        self.current_position = RenderingPosition()
        self.font_scale = 1.0

        # Cover page handling
        self._has_cover = self._detect_cover()
        self._on_cover_page = self._has_cover  # Start on cover if one exists

        # Page position history for fast backward navigation
        # List of (position, font_scale) tuples representing the start of each page visited
        self._page_history: List[Tuple[RenderingPosition, float]] = []
        self._max_history_size = 50  # Keep last 50 page positions

        # Load last reading position if available
        saved_position = self.bookmark_manager.load_reading_position()
        if saved_position:
            self.current_position = saved_position
            self._on_cover_page = False  # If we have a saved position, we're past the cover

        # Callbacks for UI updates
        self.position_changed_callback: Optional[Callable[[
            RenderingPosition], None]] = None
        self.chapter_changed_callback: Optional[Callable[[
            Optional[ChapterInfo]], None]] = None

    def prewarm_caches(self, max_words: int = 2000,
                       budget_bytes: Optional[int] = None) -> Tuple[int, int]:
        """
        Preload the text caches with this document's most frequent words.

        Counts how often each word occurs in the book and rasterises the most
        common ones ahead of time, so that the work lands at open time rather than
        on the first page turns. Entries are seeded with their document frequency,
        which is what keeps them resident under usage-ranked eviction.

        Safe to call again after a font change; the fonts differ, so the new
        entries simply take their place in the eviction order alongside the old.

        Args:
            max_words: Maximum distinct words to preload.
            budget_bytes: Bytes of glyph cache to fill. Defaults to half the budget.

        Returns:
            Tuple of (words preloaded, bytes preloaded).
        """
        from collections import Counter
        from pyWebLayout.concrete.text import prewarm_text_caches
        from .ereader_layout import FontScaler

        override = getattr(self.renderer.layouter, 'font_family_override', None)

        # Count by (style, text): the same word in a heading and in body text is a
        # different rasterisation, and both are worth counting separately.
        counts: Dict[Tuple[int, str], int] = Counter()
        styles: Dict[int, Any] = {}
        for block in self.blocks:
            words = getattr(block, '_words', None)
            if not words:
                continue
            for word in words:
                style = word.style
                if style is None:
                    continue
                key = id(style)
                styles.setdefault(key, style)
                counts[(key, word.text)] += 1

        # Resolve each distinct style once through the same scaling the layouter
        # applies, so the preloaded keys match what rendering will look up.
        scaled: Dict[int, Any] = {}
        for key, style in styles.items():
            try:
                scaled[key] = FontScaler.scale_font(style, self.font_scale, override)
            except Exception:
                continue

        entries = []
        for (style_key, text), count in counts.items():
            font = scaled.get(style_key)
            if font is None:
                continue
            entries.append((font.font, text, font.colour, count))

        return prewarm_text_caches(entries, budget_bytes=budget_bytes,
                                   max_words=max_words)

    def set_position_changed_callback(
            self, callback: Callable[[RenderingPosition], None]):
        """Set callback for position changes"""
        self.position_changed_callback = callback

    def set_chapter_changed_callback(
            self, callback: Callable[[Optional[ChapterInfo]], None]):
        """Set callback for chapter changes"""
        self.chapter_changed_callback = callback

    def _detect_cover(self) -> bool:
        """
        Detect if the document has a cover page.

        A cover is detected if:
        1. The first block is an Image block, OR
        2. The document has cover metadata (future enhancement)

        Returns:
            True if a cover page should be rendered
        """
        if not self.blocks:
            return False

        # Check if first block is an image - treat it as a cover
        first_block = self.blocks[0]
        if isinstance(first_block, Image):
            return True

        return False

    def _render_cover_page(self) -> Page:
        """
        Render a dedicated cover page.

        The cover page displays the first image block (if it exists)
        using the standard image layouter with maximum dimensions to fill the page.

        Returns:
            Rendered cover page
        """
        # Create a new page for the cover
        page = Page(self.page_size, self.page_style)

        if not self.blocks or not isinstance(self.blocks[0], Image):
            # No cover image, return blank page
            return page

        cover_image_block = self.blocks[0]

        # Use the image layouter to render the cover image
        # Use full page dimensions (minus borders/padding) for cover
        try:
            max_width = self.page_size[0] - 2 * self.page_style.border_width
            max_height = self.page_size[1] - 2 * self.page_style.border_width

            # Layout the image on the page
            success = image_layouter(
                image=cover_image_block,
                page=page,
                max_width=max_width,
                max_height=max_height
            )

            if not success:
                print("Warning: Failed to layout cover image")

        except Exception as e:
            # If image loading fails, just return the blank page
            print(f"Warning: Failed to load cover image: {e}")

        return page

    def _notify_position_changed(self):
        """Notify UI of position change"""
        if self.position_changed_callback:
            self.position_changed_callback(self.current_position)

        # Check if chapter changed
        current_chapter = self.chapter_navigator.get_current_chapter(
            self.current_position)
        if self.chapter_changed_callback:
            self.chapter_changed_callback(current_chapter)

        # Auto-save reading position
        self.bookmark_manager.save_reading_position(self.current_position)

    def get_current_page(self) -> Page:
        """
        Get the page at the current reading position.

        If on the cover page, returns the rendered cover.
        Otherwise, returns the regular content page.

        Returns:
            Rendered page
        """
        # Check if we're on the cover page
        if self._on_cover_page and self._has_cover:
            return self._render_cover_page()

        page, _ = self.renderer.render_page(self.current_position, self.font_scale)
        return page

    def next_page(self) -> Optional[Page]:
        """
        Advance to the next page.

        If currently on the cover page, advances to the first content page.
        Otherwise, advances to the next content page.

        Returns:
            Next page or None if at end of document
        """
        # Special case: transitioning from cover to first content page
        if self._on_cover_page and self._has_cover:
            self._on_cover_page = False
            # If first block is an image (the cover), skip it and start from block 1
            if self.blocks and isinstance(self.blocks[0], Image):
                self.current_position = RenderingPosition(chapter_index=0, block_index=1)
            else:
                self.current_position = RenderingPosition()
            self._notify_position_changed()
            return self.get_current_page()

        # Save current position to history before moving forward
        self._add_to_history(self.current_position, self.font_scale)

        page, next_position = self.renderer.render_page(
            self.current_position, self.font_scale)

        # Check if we made progress
        if next_position != self.current_position:
            self.current_position = next_position
            self._notify_position_changed()
            return self.get_current_page()

        # No progress. That is the correct answer only at the end of the
        # document; anywhere else a block has failed to lay out and would trap
        # the reader on this page. Skipping the block costs one block, not the
        # rest of the book.
        if self.current_position.block_index < len(self.blocks):
            logger.error(
                "Block %d made no layout progress; skipping it. This is a layout "
                "bug - the block placed nothing and reported no resume point.",
                self.current_position.block_index)
            self.current_position = RenderingPosition(
                chapter_index=self.current_position.chapter_index,
                block_index=self.current_position.block_index + 1)
            self._notify_position_changed()
            return self.get_current_page()

        return None  # At end of document

    def previous_page(self) -> Optional[Page]:
        """
        Go to the previous page.

        Uses cached page history for instant navigation when available,
        falls back to iterative refinement algorithm when needed.
        Can navigate back to the cover page if it exists.

        Returns:
            Previous page or None if at beginning of document (or on cover)
        """
        # Special case: if at the beginning of content and there's a cover, go back to it
        if self._has_cover and self._is_at_beginning() and not self._on_cover_page:
            self._on_cover_page = True
            self._notify_position_changed()
            return self.get_current_page()

        # Can't go before the cover
        if self._on_cover_page:
            return None

        if self._is_at_beginning():
            return None

        # Fast path: Check if we have this position in history
        previous_position = self._get_from_history(self.current_position, self.font_scale)

        if previous_position is not None:
            # Cache hit! Use the cached position for instant navigation
            self.current_position = previous_position
            self._notify_position_changed()
            return self.get_current_page()

        # Slow path: Use backward rendering to find the previous page
        # This uses the iterative refinement algorithm we just fixed
        page, start_position = self.renderer.render_page_backward(
            self.current_position, self.font_scale)

        if start_position != self.current_position:
            # Save this calculated position to history for future use
            self._add_to_history(start_position, self.font_scale)

            self.current_position = start_position
            self._notify_position_changed()
            return page

        return None  # At beginning of document

    def _is_at_beginning(self) -> bool:
        """
        Check if we're at the beginning of the document content.

        If a cover exists (first block is an Image), the beginning of content
        is at block_index=1. Otherwise, it's at block_index=0.
        """
        # Determine the first content block index
        first_content_block = 1 if (self._has_cover and self.blocks and isinstance(self.blocks[0], Image)) else 0

        return (self.current_position.chapter_index == 0 and
                self.current_position.block_index == first_content_block and
                self.current_position.word_index == 0)

    def jump_to_position(self, position: RenderingPosition) -> Page:
        """
        Jump to a specific position in the document.

        Args:
            position: Position to jump to

        Returns:
            Page at the new position
        """
        self.current_position = position
        self._on_cover_page = False  # Jumping to a position means we're past the cover
        self._notify_position_changed()
        return self.get_current_page()

    def jump_to_chapter(self, chapter_title: str) -> Optional[Page]:
        """
        Jump to a specific chapter by title.

        Args:
            chapter_title: Title of the chapter to jump to

        Returns:
            Page at chapter start or None if chapter not found
        """
        position = self.chapter_navigator.get_chapter_position(chapter_title)
        if position:
            return self.jump_to_position(position)
        return None

    def jump_to_chapter_index(self, chapter_index: int) -> Optional[Page]:
        """
        Jump to a chapter by index.

        Args:
            chapter_index: Index of the chapter (0-based)

        Returns:
            Page at chapter start or None if index invalid
        """
        chapters = self.chapter_navigator.chapters
        if 0 <= chapter_index < len(chapters):
            return self.jump_to_position(chapters[chapter_index].position)
        return None

    def _add_to_history(self, position: RenderingPosition, font_scale: float):
        """
        Add a page position to the navigation history.

        Args:
            position: The page start position to remember
            font_scale: The font scale at this position
        """
        # Only add if it's different from the last entry
        if not self._page_history or \
           self._page_history[-1][0] != position or \
           self._page_history[-1][1] != font_scale:

            self._page_history.append((position.copy(), font_scale))

            # Trim history if it exceeds max size
            if len(self._page_history) > self._max_history_size:
                self._page_history.pop(0)

    def _get_from_history(
            self,
            current_position: RenderingPosition,
            current_font_scale: float) -> Optional[RenderingPosition]:
        """
        Get the previous page position from history.

        Searches backward through history to find the last position that
        comes before the current position at the same font scale.

        Args:
            current_position: Current page position
            current_font_scale: Current font scale

        Returns:
            Previous page position or None if not found in history
        """
        # Search backward through history
        for i in range(len(self._page_history) - 1, -1, -1):
            hist_position, hist_font_scale = self._page_history[i]

            # Must match font scale
            if hist_font_scale != current_font_scale:
                continue

            # Must be before current position
            if (hist_position.chapter_index < current_position.chapter_index or
                (hist_position.chapter_index == current_position.chapter_index and
                 hist_position.block_index < current_position.block_index) or
                (hist_position.chapter_index == current_position.chapter_index and
                 hist_position.block_index == current_position.block_index and
                 hist_position.word_index < current_position.word_index)):

                # Found a previous position - remove it and everything after from history
                # since we're navigating backward
                self._page_history = self._page_history[:i]
                return hist_position.copy()

        return None

    def _clear_history(self):
        """Clear the page navigation history."""
        self._page_history.clear()

    def set_font_scale(self, scale: float) -> Page:
        """
        Change the font scale and re-render current page.

        Clears page history since font changes invalidate all cached positions.

        Args:
            scale: Font scaling factor (1.0 = normal, 2.0 = double size, etc.)

        Returns:
            Re-rendered page with new font scale
        """
        if scale != self.font_scale:
            self.font_scale = scale
            # Clear history since font scale changes invalidate all cached positions
            self._clear_history()
            # The renderer will handle cache invalidation

        return self.get_current_page()

    def get_font_scale(self) -> float:
        """Get the current font scale"""
        return self.font_scale

    def set_font_family(self, family: Optional[BundledFont]) -> Page:
        """
        Change the font family and re-render current page.

        Switches all text in the document to use the specified bundled font family
        while preserving font weights, styles, sizes, and other attributes.
        Clears page history and cache since font changes invalidate all cached positions.

        Args:
            family: Bundled font family to use (SANS, SERIF, MONOSPACE, or None for original fonts)

        Returns:
            Re-rendered page with new font family

        Example:
            >>> from pyWebLayout.style.fonts import BundledFont
            >>> manager.set_font_family(BundledFont.SERIF)  # Switch to serif
            >>> manager.set_font_family(BundledFont.SANS)   # Switch to sans
            >>> manager.set_font_family(None)  # Restore original fonts
        """
        # Update the renderer's font family
        self.renderer.set_font_family(family)

        # Clear history since font changes invalidate all cached positions
        self._clear_history()

        return self.get_current_page()

    def get_font_family(self) -> Optional[BundledFont]:
        """
        Get the current font family override.

        Returns:
            Current font family (SANS, SERIF, MONOSPACE) or None if using original fonts
        """
        return self.renderer.get_font_family()

    def increase_line_spacing(self, amount: int = 2) -> Page:
        """
        Increase line spacing and re-render current page.

        Clears page history since spacing changes invalidate all cached positions.

        Args:
            amount: Pixels to add to line spacing (default: 2)

        Returns:
            Re-rendered page with increased line spacing
        """
        self.page_style.line_spacing += amount
        self.renderer.page_style = self.page_style  # Update renderer's reference
        self.renderer.buffer.invalidate_all()  # Clear cache to force re-render
        self._clear_history()  # Clear position history
        return self.get_current_page()

    def decrease_line_spacing(self, amount: int = 2) -> Page:
        """
        Decrease line spacing and re-render current page.

        Clears page history since spacing changes invalidate all cached positions.

        Args:
            amount: Pixels to remove from line spacing (default: 2)

        Returns:
            Re-rendered page with decreased line spacing
        """
        self.page_style.line_spacing = max(0, self.page_style.line_spacing - amount)
        self.renderer.page_style = self.page_style  # Update renderer's reference
        self.renderer.buffer.invalidate_all()  # Clear cache to force re-render
        self._clear_history()  # Clear position history
        return self.get_current_page()

    def increase_inter_block_spacing(self, amount: int = 5) -> Page:
        """
        Increase spacing between blocks and re-render current page.

        Clears page history since spacing changes invalidate all cached positions.

        Args:
            amount: Pixels to add to inter-block spacing (default: 5)

        Returns:
            Re-rendered page with increased block spacing
        """
        self.page_style.inter_block_spacing += amount
        self.renderer.page_style = self.page_style  # Update renderer's reference
        self.renderer.buffer.invalidate_all()  # Clear cache to force re-render
        self._clear_history()  # Clear position history
        return self.get_current_page()

    def decrease_inter_block_spacing(self, amount: int = 5) -> Page:
        """
        Decrease spacing between blocks and re-render current page.

        Clears page history since spacing changes invalidate all cached positions.

        Args:
            amount: Pixels to remove from inter-block spacing (default: 5)

        Returns:
            Re-rendered page with decreased block spacing
        """
        self.page_style.inter_block_spacing = max(
            0, self.page_style.inter_block_spacing - amount)
        self.renderer.page_style = self.page_style  # Update renderer's reference
        self.renderer.buffer.invalidate_all()  # Clear cache to force re-render
        self._clear_history()  # Clear position history
        return self.get_current_page()

    def increase_word_spacing(self, amount: int = 2) -> Page:
        """
        Increase spacing between words and re-render current page.

        Clears page history since spacing changes invalidate all cached positions.

        Args:
            amount: Pixels to add to word spacing (default: 2)

        Returns:
            Re-rendered page with increased word spacing
        """
        self.page_style.word_spacing += amount
        self.renderer.page_style = self.page_style  # Update renderer's reference
        self.renderer.buffer.invalidate_all()  # Clear cache to force re-render
        self._clear_history()  # Clear position history
        return self.get_current_page()

    def decrease_word_spacing(self, amount: int = 2) -> Page:
        """
        Decrease spacing between words and re-render current page.

        Clears page history since spacing changes invalidate all cached positions.

        Args:
            amount: Pixels to remove from word spacing (default: 2)

        Returns:
            Re-rendered page with decreased word spacing
        """
        self.page_style.word_spacing = max(0, self.page_style.word_spacing - amount)
        self.renderer.page_style = self.page_style  # Update renderer's reference
        self.renderer.buffer.invalidate_all()  # Clear cache to force re-render
        self._clear_history()  # Clear position history
        return self.get_current_page()

    def get_table_of_contents(
            self) -> List[Tuple[str, HeadingLevel, RenderingPosition]]:
        """
        Get the table of contents.

        Returns:
            List of (title, level, position) tuples
        """
        return self.chapter_navigator.get_table_of_contents()

    def get_current_chapter(self) -> Optional[ChapterInfo]:
        """
        Get information about the current chapter.

        Returns:
            Current chapter info or None if no chapters
        """
        return self.chapter_navigator.get_current_chapter(self.current_position)

    def add_bookmark(self, name: str) -> bool:
        """
        Add a bookmark at the current position.

        Args:
            name: Bookmark name

        Returns:
            True if bookmark was added successfully
        """
        try:
            self.bookmark_manager.add_bookmark(name, self.current_position)
            return True
        except Exception:
            return False

    def remove_bookmark(self, name: str) -> bool:
        """
        Remove a bookmark.

        Args:
            name: Bookmark name

        Returns:
            True if bookmark was removed
        """
        return self.bookmark_manager.remove_bookmark(name)

    def jump_to_bookmark(self, name: str) -> Optional[Page]:
        """
        Jump to a bookmark.

        Args:
            name: Bookmark name

        Returns:
            Page at bookmark position or None if bookmark not found
        """
        position = self.bookmark_manager.get_bookmark(name)
        if position:
            return self.jump_to_position(position)
        return None

    def list_bookmarks(self) -> List[Tuple[str, RenderingPosition]]:
        """
        Get all bookmarks.

        Returns:
            List of (name, position) tuples
        """
        return self.bookmark_manager.list_bookmarks()

    def get_reading_progress(self) -> float:
        """
        Get reading progress as a percentage.

        Returns:
            Progress from 0.0 to 1.0
        """
        if not self.blocks:
            return 0.0

        # Simple progress calculation based on block index
        # A more sophisticated version would consider word positions
        total_blocks = len(self.blocks)
        current_block = min(self.current_position.block_index, total_blocks - 1)

        return current_block / max(1, total_blocks - 1)

    def has_cover(self) -> bool:
        """
        Check if the document has a cover page.

        Returns:
            True if a cover page is available
        """
        return self._has_cover

    def is_on_cover(self) -> bool:
        """
        Check if currently viewing the cover page.

        Returns:
            True if on the cover page
        """
        return self._on_cover_page

    def jump_to_cover(self) -> Optional[Page]:
        """
        Jump to the cover page if one exists.

        Returns:
            Cover page or None if no cover exists
        """
        if not self._has_cover:
            return None

        self._on_cover_page = True
        self._notify_position_changed()
        return self.get_current_page()

    def get_position_info(self) -> Dict[str, Any]:
        """
        Get detailed information about the current position.

        Returns:
            Dictionary with position details
        """
        current_chapter = self.get_current_chapter()
        font_family = self.get_font_family()

        return {
            'position': self.current_position.to_dict(),
            'on_cover': self._on_cover_page,
            'has_cover': self._has_cover,
            'chapter': {
                'title': current_chapter.title if current_chapter else None,
                'level': current_chapter.level if current_chapter else None,
                'index': current_chapter.block_index if current_chapter else None
            },
            'progress': self.get_reading_progress(),
            'font_scale': self.font_scale,
            'font_family': font_family.value if font_family else None,
            'page_size': self.page_size
        }

    def get_cache_stats(self) -> Dict[str, Any]:
        """
        Get cache statistics for debugging/monitoring.

        Returns:
            Dictionary with cache statistics
        """
        return self.renderer.get_cache_stats()

    def shutdown(self):
        """
        Shutdown the ereader manager and clean up resources.
        Call this when the application is closing.
        """
        # Save current position
        self.bookmark_manager.save_reading_position(self.current_position)

        # Shutdown renderer and buffer
        self.renderer.shutdown()

    def __del__(self):
        """Cleanup on destruction"""
        self.shutdown()


# Convenience function for quick setup
def create_ereader_manager(blocks: List[Block],
                           page_size: Tuple[int, int],
                           document_id: str = "default",
                           **kwargs) -> EreaderLayoutManager:
    """
    Convenience function to create an ereader manager with sensible defaults.

    Args:
        blocks: Document blocks to render
        page_size: Page size (width, height) in pixels
        document_id: Unique identifier for the document
        **kwargs: Additional arguments passed to EreaderLayoutManager

    Returns:
        Configured EreaderLayoutManager instance
    """
    return EreaderLayoutManager(blocks, page_size, document_id, **kwargs)
