#!/usr/bin/env python3
"""
Simple ereader application interface for pyWebLayout.

This module provides a user-friendly wrapper around the ereader infrastructure,
making it easy to build ebook reader applications with all essential features.

Example:
    from pyWebLayout.layout.ereader_application import EbookReader
    
    # Create reader
    reader = EbookReader(page_size=(800, 1000))
    
    # Load an EPUB
    reader.load_epub("mybook.epub")
    
    # Navigate
    reader.next_page()
    reader.previous_page()
    
    # Get current page
    page_image = reader.get_current_page()
    
    # Modify styling
    reader.increase_font_size()
    reader.set_line_spacing(8)
    
    # Chapter navigation
    chapters = reader.get_chapters()
    reader.jump_to_chapter("Chapter 1")
    
    # Position management
    reader.save_position("bookmark1")
    reader.load_position("bookmark1")
"""

from __future__ import annotations
from typing import List, Tuple, Optional, Dict, Any, Union
from pathlib import Path
import os

from PIL import Image

from pyWebLayout.abstract.block import Block, HeadingLevel
from pyWebLayout.layout.ereader_manager import EreaderLayoutManager
from pyWebLayout.layout.ereader_layout import RenderingPosition
from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.concrete.page import Page
from pyWebLayout.core.query import QueryResult, SelectionRange
from pyWebLayout.core.highlight import Highlight, HighlightColor, create_highlight_from_query_result

from .gesture import TouchEvent, GestureType, GestureResponse, ActionType
from .state import OverlayState
from .managers import DocumentManager, SettingsManager, HighlightCoordinator
from .handlers import GestureRouter
from .overlays import NavigationOverlay, SettingsOverlay, TOCOverlay


class EbookReader:
    """
    Simple ereader application with all essential features.
    
    Features:
    - Load EPUB files
    - Forward/backward page navigation
    - Position save/load (based on abstract document structure)
    - Chapter navigation
    - Font size and spacing control
    - Current page retrieval as PIL Image
    
    The reader maintains position using abstract document structure (chapter/block/word indices),
    ensuring positions remain valid across font size and styling changes.
    """
    
    def __init__(self,
                 page_size: Tuple[int, int] = (800, 1000),
                 margin: int = 40,
                 background_color: Tuple[int, int, int] = (255, 255, 255),
                 line_spacing: int = 5,
                 inter_block_spacing: int = 15,
                 bookmarks_dir: str = "ereader_bookmarks",
                 highlights_dir: str = "highlights",
                 buffer_size: int = 5):
        """
        Initialize the ebook reader.

        Args:
            page_size: Page dimensions (width, height) in pixels
            margin: Page margin in pixels
            background_color: Background color as RGB tuple
            line_spacing: Spacing between lines in pixels
            inter_block_spacing: Spacing between blocks in pixels
            bookmarks_dir: Directory to store bookmarks and positions
            highlights_dir: Directory to store highlights
            buffer_size: Number of pages to cache for performance
        """
        self.page_size = page_size
        self.bookmarks_dir = bookmarks_dir
        self.highlights_dir = highlights_dir
        self.buffer_size = buffer_size

        # Create page style
        self.page_style = PageStyle(
            background_color=background_color,
            border_width=margin,
            border_color=(200, 200, 200),
            padding=(10, 10, 10, 10),
            line_spacing=line_spacing,
            inter_block_spacing=inter_block_spacing
        )

        # Core managers (NEW: Refactored into separate modules)
        self.doc_manager = DocumentManager()
        self.settings_manager = SettingsManager()
        self.highlight_coordinator: Optional[HighlightCoordinator] = None
        self.gesture_router = GestureRouter(self)

        # Layout manager (initialized after loading)
        self.manager: Optional[EreaderLayoutManager] = None

        # Legacy compatibility properties
        self.blocks: Optional[List[Block]] = None
        self.document_id: Optional[str] = None
        self.book_title: Optional[str] = None
        self.book_author: Optional[str] = None
        self.highlight_manager = None  # Will delegate to highlight_coordinator

        # Font scale state (delegated to settings_manager but kept for compatibility)
        self.base_font_scale = 1.0
        self.font_scale_step = 0.1

        # Overlay sub-applications
        self._overlay_subapps = {
            OverlayState.NAVIGATION: NavigationOverlay(self),
            OverlayState.SETTINGS: SettingsOverlay(self),
            OverlayState.TOC: TOCOverlay(self),
        }
        self._active_overlay = None  # Current active overlay sub-application
        self.current_overlay_state = OverlayState.NONE
    
    def load_epub(self, epub_path: str) -> bool:
        """
        Load an EPUB file into the reader.

        Args:
            epub_path: Path to the EPUB file

        Returns:
            True if loaded successfully, False otherwise
        """
        # Use DocumentManager to load the EPUB
        success = self.doc_manager.load_epub(epub_path)

        if not success:
            return False

        # Set compatibility properties
        self.book_title = self.doc_manager.title
        self.book_author = self.doc_manager.author
        self.document_id = self.doc_manager.document_id
        self.blocks = self.doc_manager.blocks

        # Initialize the ereader manager
        self.manager = EreaderLayoutManager(
            blocks=self.blocks,
            page_size=self.page_size,
            document_id=self.document_id,
            buffer_size=self.buffer_size,
            page_style=self.page_style,
            bookmarks_dir=self.bookmarks_dir
        )

        # Initialize managers that depend on layout manager
        self.settings_manager.set_manager(self.manager)

        # Initialize highlight coordinator for this document
        self.highlight_coordinator = HighlightCoordinator(
            document_id=self.document_id,
            highlights_dir=self.highlights_dir
        )
        self.highlight_coordinator.set_layout_manager(self.manager)
        self.highlight_manager = self.highlight_coordinator.highlight_manager  # Compatibility

        return True
    
    def load_html(self, html_string: str, title: str = "HTML Document", author: str = "Unknown", document_id: str = "html_doc") -> bool:
        """
        Load HTML content directly into the reader.

        This is useful for rendering library screens, menus, or other HTML-based UI elements
        using the same rendering engine as the ebook reader.

        Args:
            html_string: HTML content to render
            title: Document title (for metadata)
            author: Document author (for metadata)
            document_id: Unique identifier for this HTML document

        Returns:
            True if loaded successfully, False otherwise
        """
        # Use DocumentManager to load HTML
        success = self.doc_manager.load_html(html_string, title, author, document_id)

        if not success:
            return False

        # Set compatibility properties
        self.book_title = self.doc_manager.title
        self.book_author = self.doc_manager.author
        self.document_id = self.doc_manager.document_id
        self.blocks = self.doc_manager.blocks

        # Initialize the ereader manager
        self.manager = EreaderLayoutManager(
            blocks=self.blocks,
            page_size=self.page_size,
            document_id=self.document_id,
            buffer_size=self.buffer_size,
            page_style=self.page_style,
            bookmarks_dir=self.bookmarks_dir
        )

        # Initialize managers that depend on layout manager
        self.settings_manager.set_manager(self.manager)

        # Initialize highlight coordinator for this document
        self.highlight_coordinator = HighlightCoordinator(
            document_id=self.document_id,
            highlights_dir=self.highlights_dir
        )
        self.highlight_coordinator.set_layout_manager(self.manager)
        self.highlight_manager = self.highlight_coordinator.highlight_manager  # Compatibility

        return True

    def is_loaded(self) -> bool:
        """Check if a book is currently loaded."""
        return self.manager is not None

    def get_current_page(self, include_highlights: bool = True) -> Optional[Image.Image]:
        """
        Get the current page as a PIL Image.

        If an overlay is currently open, returns the composited overlay image.
        Otherwise returns the base reading page.

        Args:
            include_highlights: Whether to overlay highlights on the page (only applies to base page)

        Returns:
            PIL Image of the current page (or overlay), or None if no book is loaded
        """
        if not self.manager:
            return None

        # If an overlay is open, return the cached composited overlay image
        if self.is_overlay_open() and self._active_overlay:
            # Return the composited overlay from the sub-application
            if self._active_overlay._cached_base_page and self._active_overlay._cached_overlay_image:
                return self._active_overlay.composite_overlay(
                    self._active_overlay._cached_base_page,
                    self._active_overlay._cached_overlay_image
                )

        try:
            page = self.manager.get_current_page()
            img = page.render()

            # Overlay highlights if requested and available
            if include_highlights and self.highlight_manager:
                # Get page bounds
                page_bounds = (0, 0, self.page_size[0], self.page_size[1])
                highlights = self.highlight_manager.get_highlights_for_page(page_bounds)

                if highlights:
                    img = self._render_highlights(img, highlights)

            return img
        except Exception as e:
            print(f"Error rendering page: {e}")
            return None
    
    def next_page(self) -> Optional[Image.Image]:
        """
        Navigate to the next page.
        
        Returns:
            PIL Image of the next page, or None if at end of book
        """
        if not self.manager:
            return None
        
        try:
            page = self.manager.next_page()
            if page:
                return page.render()
            return None
        except Exception as e:
            print(f"Error navigating to next page: {e}")
            return None
    
    def previous_page(self) -> Optional[Image.Image]:
        """
        Navigate to the previous page.
        
        Returns:
            PIL Image of the previous page, or None if at beginning of book
        """
        if not self.manager:
            return None
        
        try:
            page = self.manager.previous_page()
            if page:
                return page.render()
            return None
        except Exception as e:
            print(f"Error navigating to previous page: {e}")
            return None
    
    def save_position(self, name: str = "current_position") -> bool:
        """
        Save the current reading position with a name.
        
        The position is saved based on abstract document structure (chapter, block, word indices),
        making it stable across font size and styling changes.
        
        Args:
            name: Name for this saved position
            
        Returns:
            True if saved successfully, False otherwise
        """
        if not self.manager:
            return False
        
        try:
            self.manager.add_bookmark(name)
            return True
        except Exception as e:
            print(f"Error saving position: {e}")
            return False
    
    def load_position(self, name: str = "current_position") -> Optional[Image.Image]:
        """
        Load a previously saved reading position.
        
        Args:
            name: Name of the saved position
            
        Returns:
            PIL Image of the page at the loaded position, or None if not found
        """
        if not self.manager:
            return None
        
        try:
            page = self.manager.jump_to_bookmark(name)
            if page:
                return page.render()
            return None
        except Exception as e:
            print(f"Error loading position: {e}")
            return None
    
    def list_saved_positions(self) -> List[str]:
        """
        Get a list of all saved position names.
        
        Returns:
            List of position names
        """
        if not self.manager:
            return []
        
        try:
            bookmarks = self.manager.list_bookmarks()
            return [name for name, _ in bookmarks]
        except Exception as e:
            print(f"Error listing positions: {e}")
            return []
    
    def delete_position(self, name: str) -> bool:
        """
        Delete a saved position.
        
        Args:
            name: Name of the position to delete
            
        Returns:
            True if deleted, False otherwise
        """
        if not self.manager:
            return False
        
        return self.manager.remove_bookmark(name)
    
    def get_chapters(self) -> List[Tuple[str, int]]:
        """
        Get a list of all chapters with their indices.
        
        Returns:
            List of (chapter_title, chapter_index) tuples
        """
        if not self.manager:
            return []
        
        try:
            toc = self.manager.get_table_of_contents()
            # Convert to simplified format (title, index)
            chapters = []
            for i, (title, level, position) in enumerate(toc):
                chapters.append((title, i))
            return chapters
        except Exception as e:
            print(f"Error getting chapters: {e}")
            return []
    
    def get_chapter_positions(self) -> List[Tuple[str, RenderingPosition]]:
        """
        Get chapter titles with their exact rendering positions.
        
        Returns:
            List of (title, position) tuples
        """
        if not self.manager:
            return []
        
        try:
            toc = self.manager.get_table_of_contents()
            return [(title, position) for title, level, position in toc]
        except Exception as e:
            print(f"Error getting chapter positions: {e}")
            return []
    
    def jump_to_chapter(self, chapter: Union[str, int]) -> Optional[Image.Image]:
        """
        Navigate to a specific chapter by title or index.
        
        Args:
            chapter: Chapter title (string) or chapter index (integer)
            
        Returns:
            PIL Image of the first page of the chapter, or None if not found
        """
        if not self.manager:
            return None
        
        try:
            if isinstance(chapter, int):
                page = self.manager.jump_to_chapter_index(chapter)
            else:
                page = self.manager.jump_to_chapter(chapter)
            
            if page:
                return page.render()
            return None
        except Exception as e:
            print(f"Error jumping to chapter: {e}")
            return None
    
    def set_font_size(self, scale: float) -> Optional[Image.Image]:
        """
        Set the font size scale and re-render current page.

        Args:
            scale: Font scale factor (1.0 = normal, 2.0 = double size, 0.5 = half size)

        Returns:
            PIL Image of the re-rendered page with new font size
        """
        result = self.settings_manager.set_font_size(scale)
        if result:
            self.base_font_scale = self.settings_manager.font_scale  # Sync compatibility property
        return result

    def increase_font_size(self) -> Optional[Image.Image]:
        """
        Increase font size by one step and re-render.

        Returns:
            PIL Image of the re-rendered page
        """
        result = self.settings_manager.increase_font_size()
        if result:
            self.base_font_scale = self.settings_manager.font_scale
        return result

    def decrease_font_size(self) -> Optional[Image.Image]:
        """
        Decrease font size by one step and re-render.

        Returns:
            PIL Image of the re-rendered page
        """
        result = self.settings_manager.decrease_font_size()
        if result:
            self.base_font_scale = self.settings_manager.font_scale
        return result

    def get_font_size(self) -> float:
        """
        Get the current font size scale.

        Returns:
            Current font scale factor
        """
        return self.settings_manager.get_font_size()
    
    def set_line_spacing(self, spacing: int) -> Optional[Image.Image]:
        """
        Set line spacing using pyWebLayout's native support.

        Args:
            spacing: Line spacing in pixels

        Returns:
            PIL Image of the re-rendered page
        """
        return self.settings_manager.set_line_spacing(spacing)

    def set_inter_block_spacing(self, spacing: int) -> Optional[Image.Image]:
        """
        Set inter-block spacing using pyWebLayout's native support.

        Args:
            spacing: Inter-block spacing in pixels

        Returns:
            PIL Image of the re-rendered page
        """
        return self.settings_manager.set_inter_block_spacing(spacing)

    def set_word_spacing(self, spacing: int) -> Optional[Image.Image]:
        """
        Set word spacing using pyWebLayout's native support.

        Args:
            spacing: Word spacing in pixels

        Returns:
            PIL Image of the re-rendered page
        """
        return self.settings_manager.set_word_spacing(spacing)

    def get_position_info(self) -> Dict[str, Any]:
        """
        Get detailed information about the current position.
        
        Returns:
            Dictionary with position details including:
            - position: RenderingPosition details (chapter_index, block_index, word_index)
            - chapter: Current chapter info (title, level)
            - progress: Reading progress (0.0 to 1.0)
            - font_scale: Current font scale
            - book_title: Book title
            - book_author: Book author
        """
        if not self.manager:
            return {}
        
        try:
            info = self.manager.get_position_info()
            info['book_title'] = self.book_title
            info['book_author'] = self.book_author
            return info
        except Exception as e:
            print(f"Error getting position info: {e}")
            return {}
    
    def get_reading_progress(self) -> float:
        """
        Get reading progress as a percentage.
        
        Returns:
            Progress from 0.0 (beginning) to 1.0 (end)
        """
        if not self.manager:
            return 0.0
        
        return self.manager.get_reading_progress()
    
    def get_current_chapter_info(self) -> Optional[Dict[str, Any]]:
        """
        Get information about the current chapter.
        
        Returns:
            Dictionary with chapter info (title, level) or None
        """
        if not self.manager:
            return None
        
        try:
            chapter = self.manager.get_current_chapter()
            if chapter:
                return {
                    'title': chapter.title,
                    'level': chapter.level,
                    'block_index': chapter.block_index
                }
            return None
        except Exception as e:
            print(f"Error getting current chapter: {e}")
            return None
    
    def render_to_file(self, output_path: str) -> bool:
        """
        Save the current page to an image file.
        
        Args:
            output_path: Path where to save the image (e.g., "page.png")
            
        Returns:
            True if saved successfully, False otherwise
        """
        page_image = self.get_current_page()
        if page_image:
            try:
                page_image.save(output_path)
                return True
            except Exception as e:
                print(f"Error saving image: {e}")
                return False
        return False
    
    def get_book_info(self) -> Dict[str, Any]:
        """
        Get information about the loaded book.

        Returns:
            Dictionary with book information
        """
        return {
            'title': self.book_title,
            'author': self.book_author,
            'document_id': self.document_id,
            'total_blocks': len(self.blocks) if self.blocks else 0,
            'total_chapters': len(self.get_chapters()),
            'page_size': self.page_size,
            'font_scale': self.base_font_scale
        }

    # ===== Settings Persistence =====

    def get_current_settings(self) -> Dict[str, Any]:
        """
        Get current rendering settings.

        Returns:
            Dictionary with all current settings
        """
        return self.settings_manager.get_current_settings()

    def apply_settings(self, settings: Dict[str, Any]) -> bool:
        """
        Apply rendering settings from a settings dictionary.

        This should be called after loading a book to restore user preferences.

        Args:
            settings: Dictionary with settings (font_scale, line_spacing, etc.)

        Returns:
            True if settings applied successfully, False otherwise
        """
        success = self.settings_manager.apply_settings(settings)
        if success:
            # Sync compatibility property
            self.base_font_scale = self.settings_manager.font_scale
        return success

    # ===== Gesture Handling =====
    # All business logic for touch input is handled here

    def handle_touch(self, event: TouchEvent) -> GestureResponse:
        """
        Handle a touch event from HAL.

        **This is the main business logic entry point for all touch interactions.**
        Flask should call this and use the response to generate HTML/JSON.

        Args:
            event: TouchEvent from HAL with gesture type and coordinates

        Returns:
            GestureResponse with action and data for UI to process
        """
        # Delegate to gesture router
        return self.gesture_router.handle_touch(event)

    def query_pixel(self, x: int, y: int) -> Optional[QueryResult]:
        """
        Direct pixel query for debugging/tools.

        Args:
            x, y: Pixel coordinates

        Returns:
            QueryResult or None if nothing at that location
        """
        if not self.manager:
            return None

        page = self.manager.get_current_page()
        return page.query_point((x, y))


    def _handle_overlay_tap(self, x: int, y: int) -> GestureResponse:
        """
        Handle tap when overlay is open.

        Delegates to the active overlay sub-application for handling.
        If the response indicates the overlay should be closed, closes it.
        """
        if not self._active_overlay:
            # No active overlay, close legacy overlay if any
            self.close_overlay()
            return GestureResponse(ActionType.OVERLAY_CLOSED, {})

        # Delegate to the active overlay sub-application
        response = self._active_overlay.handle_tap(x, y)

        # If the response indicates overlay should be closed, close it
        if response.action in (ActionType.OVERLAY_CLOSED, ActionType.CHAPTER_SELECTED,
                               ActionType.BOOKMARK_SELECTED):
            self.close_overlay()

        return response


    # ===================================================================
    # Highlighting API
    # ===================================================================

    def highlight_word(self, x: int, y: int,
                      color: Tuple[int, int, int, int] = None,
                      note: Optional[str] = None,
                      tags: Optional[List[str]] = None) -> Optional[str]:
        """
        Highlight a word at the given pixel location.

        Args:
            x: X coordinate
            y: Y coordinate
            color: RGBA color tuple (defaults to yellow)
            note: Optional annotation for this highlight
            tags: Optional categorization tags

        Returns:
            Highlight ID if successful, None otherwise
        """
        if not self.manager or not self.highlight_manager:
            return None

        try:
            # Query the pixel to find the word
            result = self.query_pixel(x, y)
            if not result or not result.text:
                return None

            # Use default color if not provided
            if color is None:
                color = HighlightColor.YELLOW.value

            # Create highlight from query result
            highlight = create_highlight_from_query_result(
                result,
                color=color,
                note=note,
                tags=tags
            )

            # Add to manager
            self.highlight_manager.add_highlight(highlight)

            return highlight.id
        except Exception as e:
            print(f"Error highlighting word: {e}")
            return None

    def highlight_selection(self, start: Tuple[int, int], end: Tuple[int, int],
                           color: Tuple[int, int, int, int] = None,
                           note: Optional[str] = None,
                           tags: Optional[List[str]] = None) -> Optional[str]:
        """
        Highlight a range of words between two points.

        Args:
            start: Starting (x, y) coordinates
            end: Ending (x, y) coordinates
            color: RGBA color tuple (defaults to yellow)
            note: Optional annotation
            tags: Optional categorization tags

        Returns:
            Highlight ID if successful, None otherwise
        """
        if not self.manager or not self.highlight_manager:
            return None

        try:
            page = self.manager.get_current_page()
            selection_range = page.query_range(start, end)

            if not selection_range.results:
                return None

            # Use default color if not provided
            if color is None:
                color = HighlightColor.YELLOW.value

            # Create highlight from selection range
            highlight = create_highlight_from_query_result(
                selection_range,
                color=color,
                note=note,
                tags=tags
            )

            # Add to manager
            self.highlight_manager.add_highlight(highlight)

            return highlight.id
        except Exception as e:
            print(f"Error highlighting selection: {e}")
            return None

    def remove_highlight(self, highlight_id: str) -> bool:
        """
        Remove a highlight by ID.

        Args:
            highlight_id: ID of the highlight to remove

        Returns:
            True if removed successfully, False otherwise
        """
        if not self.highlight_manager:
            return False

        return self.highlight_manager.remove_highlight(highlight_id)

    def list_highlights(self) -> List[Highlight]:
        """
        Get all highlights for the current document.

        Returns:
            List of Highlight objects
        """
        if not self.highlight_manager:
            return []

        return self.highlight_manager.list_highlights()

    def get_highlights_for_current_page(self) -> List[Highlight]:
        """
        Get highlights that appear on the current page.

        Returns:
            List of Highlight objects on this page
        """
        if not self.manager or not self.highlight_manager:
            return []

        page_bounds = (0, 0, self.page_size[0], self.page_size[1])
        return self.highlight_manager.get_highlights_for_page(page_bounds)

    def clear_highlights(self) -> None:
        """Remove all highlights from the current document."""
        if self.highlight_manager:
            self.highlight_manager.clear_all()

    def _render_highlights(self, image: Image.Image, highlights: List[Highlight]) -> Image.Image:
        """
        Render highlight overlays on an image using multiply blend mode.

        This preserves text contrast by multiplying the highlight color with the
        underlying pixels, like a real highlighter pen.

        Args:
            image: Base PIL Image to draw on
            highlights: List of Highlight objects to render

        Returns:
            New PIL Image with highlights overlaid
        """
        import numpy as np

        # Convert to RGB for processing (we'll add alpha back later if needed)
        original_mode = image.mode
        if image.mode == 'RGBA':
            # Separate alpha channel
            rgb_image = image.convert('RGB')
            alpha_channel = image.split()[-1]
        else:
            rgb_image = image.convert('RGB')
            alpha_channel = None

        # Convert to numpy array for efficient processing
        img_array = np.array(rgb_image, dtype=np.float32)

        # Process each highlight
        for highlight in highlights:
            # Extract RGB components from highlight color (ignore alpha)
            h_r, h_g, h_b = highlight.color[0], highlight.color[1], highlight.color[2]

            # Create highlight multiplier (normalize to 0-1 range)
            highlight_color = np.array([h_r / 255.0, h_g / 255.0, h_b / 255.0], dtype=np.float32)

            for hx, hy, hw, hh in highlight.bounds:
                # Ensure bounds are within image
                hx, hy = max(0, hx), max(0, hy)
                x2, y2 = min(rgb_image.width, hx + hw), min(rgb_image.height, hy + hh)

                if x2 <= hx or y2 <= hy:
                    continue

                # Extract the region to highlight
                region = img_array[hy:y2, hx:x2, :]

                # Multiply with highlight color (like a real highlighter)
                # This darkens the image proportionally to the highlight color
                highlighted = region * highlight_color

                # Put the highlighted region back
                img_array[hy:y2, hx:x2, :] = highlighted

        # Convert back to uint8 and create PIL Image
        img_array = np.clip(img_array, 0, 255).astype(np.uint8)
        result = Image.fromarray(img_array, mode='RGB')

        # Restore alpha channel if original had one
        if alpha_channel is not None and original_mode == 'RGBA':
            result = result.convert('RGBA')
            result.putalpha(alpha_channel)

        return result

    # ===================================================================
    # Overlay Management API
    # ===================================================================

    def open_toc_overlay(self) -> Optional[Image.Image]:
        """
        Open the table of contents overlay.

        Returns:
            Composited image with TOC overlay on top of current page, or None if no book loaded
        """
        if not self.is_loaded():
            return None

        # Get current page as base
        base_page = self.get_current_page(include_highlights=False)
        if not base_page:
            return None

        # Get chapters
        chapters = self.get_chapters()

        # Use the TOC sub-application
        overlay_subapp = self._overlay_subapps[OverlayState.TOC]
        result = overlay_subapp.open(base_page, chapters=chapters)

        # Update state
        self._active_overlay = overlay_subapp
        self.current_overlay_state = OverlayState.TOC

        return result

    def open_settings_overlay(self) -> Optional[Image.Image]:
        """
        Open the settings overlay with current settings values.

        Returns:
            Composited image with settings overlay on top of current page, or None if no book loaded
        """
        if not self.is_loaded():
            return None

        # Get current page as base
        base_page = self.get_current_page(include_highlights=False)
        if not base_page:
            return None

        # Get current settings
        font_scale = self.base_font_scale
        line_spacing = self.page_style.line_spacing
        inter_block_spacing = self.page_style.inter_block_spacing
        word_spacing = self.page_style.word_spacing

        # Use the Settings sub-application
        overlay_subapp = self._overlay_subapps[OverlayState.SETTINGS]
        result = overlay_subapp.open(
            base_page,
            font_scale=font_scale,
            line_spacing=line_spacing,
            inter_block_spacing=inter_block_spacing,
            word_spacing=word_spacing
        )

        # Update state
        self._active_overlay = overlay_subapp
        self.current_overlay_state = OverlayState.SETTINGS

        return result

    def open_bookmarks_overlay(self) -> Optional[Image.Image]:
        """
        Open the bookmarks overlay.

        This is a convenience method that opens the navigation overlay with the bookmarks tab active.

        Returns:
            Composited image with bookmarks overlay on top of current page, or None if no book loaded
        """
        return self.open_navigation_overlay(active_tab="bookmarks")

    def open_navigation_overlay(self, active_tab: str = "contents") -> Optional[Image.Image]:
        """
        Open the unified navigation overlay with Contents and Bookmarks tabs.

        This is the new unified overlay that replaces separate TOC and Bookmarks overlays.
        It provides a tabbed interface for switching between table of contents and bookmarks.

        Args:
            active_tab: Which tab to show initially ("contents" or "bookmarks")

        Returns:
            Composited image with navigation overlay on top of current page, or None if no book loaded
        """
        if not self.is_loaded():
            return None

        # Get current page as base
        base_page = self.get_current_page(include_highlights=False)
        if not base_page:
            return None

        # Get chapters for Contents tab
        chapters = self.get_chapters()

        # Get bookmarks for Bookmarks tab
        bookmark_names = self.list_saved_positions()
        bookmarks = [
            {"name": name, "position": f"Saved position"}
            for name in bookmark_names
        ]

        # Use the Navigation sub-application
        overlay_subapp = self._overlay_subapps[OverlayState.NAVIGATION]
        result = overlay_subapp.open(
            base_page,
            chapters=chapters,
            bookmarks=bookmarks,
            active_tab=active_tab
        )

        # Update state
        self._active_overlay = overlay_subapp
        self.current_overlay_state = OverlayState.NAVIGATION

        return result

    def switch_navigation_tab(self, new_tab: str) -> Optional[Image.Image]:
        """
        Switch between tabs in the navigation overlay.

        Args:
            new_tab: Tab to switch to ("contents" or "bookmarks")

        Returns:
            Updated image with new tab active, or None if navigation overlay is not open
        """
        if self.current_overlay_state != OverlayState.NAVIGATION:
            return None

        # Delegate to the Navigation sub-application
        if isinstance(self._active_overlay, NavigationOverlay):
            result = self._active_overlay.switch_tab(new_tab)
            return result if result else self.get_current_page()

        return None

    def close_overlay(self) -> Optional[Image.Image]:
        """
        Close the current overlay and return to reading view.

        Returns:
            Base page image without overlay, or None if no overlay was open
        """
        if self.current_overlay_state == OverlayState.NONE:
            return None

        # Close the active overlay sub-application
        if self._active_overlay:
            self._active_overlay.close()
            self._active_overlay = None

        # Update state
        self.current_overlay_state = OverlayState.NONE

        # Return fresh current page
        return self.get_current_page()

    def is_overlay_open(self) -> bool:
        """Check if an overlay is currently open."""
        return self.current_overlay_state != OverlayState.NONE

    def get_overlay_state(self) -> OverlayState:
        """Get current overlay state."""
        return self.current_overlay_state

    def close(self):
        """
        Close the reader and save current position.
        Should be called when done with the reader.
        """
        if self.manager:
            self.manager.shutdown()
            self.manager = None
    
    def __enter__(self):
        """Context manager support."""
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager cleanup."""
        self.close()
    
    def __del__(self):
        """Cleanup on deletion."""
        self.close()


# Convenience function
def create_ebook_reader(page_size: Tuple[int, int] = (800, 1000), **kwargs) -> EbookReader:
    """
    Create an ebook reader with sensible defaults.
    
    Args:
        page_size: Page dimensions (width, height) in pixels
        **kwargs: Additional arguments passed to EbookReader
        
    Returns:
        Configured EbookReader instance
    """
    return EbookReader(page_size=page_size, **kwargs)
