"""
Tests for the ereader layout system components.

This module tests:
- RenderingPosition: Position tracking and serialization
- ChapterNavigator: Chapter detection and navigation
- FontScaler: Font scaling utilities
- BidirectionalLayouter: Forward/backward page rendering
"""

import pytest
from pyWebLayout.layout.ereader_layout import (
    RenderingPosition,
    ChapterNavigator,
    ChapterInfo,
    FontScaler,
    BidirectionalLayouter
)
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel
from pyWebLayout.abstract.inline import Word
from pyWebLayout.style import Font
from pyWebLayout.style.page_style import PageStyle


# ============================================================================
# Fixtures
# ============================================================================

@pytest.fixture
def sample_font():
    """Create a standard font for testing."""
    return Font(font_size=12, colour=(0, 0, 0))


@pytest.fixture
def sample_blocks_with_headings(sample_font):
    """Create a sample document structure with headings for testing."""
    blocks = []

    # H1 - Chapter 1
    h1 = Heading(HeadingLevel.H1, sample_font)
    h1.add_word(Word("Chapter", sample_font))
    h1.add_word(Word("One", sample_font))
    blocks.append(h1)

    # Paragraph
    p1 = Paragraph(sample_font)
    p1.add_word(Word("This", sample_font))
    p1.add_word(Word("is", sample_font))
    p1.add_word(Word("content", sample_font))
    blocks.append(p1)

    # H2 - Section 1.1
    h2 = Heading(HeadingLevel.H2, sample_font)
    h2.add_word(Word("Section", sample_font))
    h2.add_word(Word("1.1", sample_font))
    blocks.append(h2)

    # Another paragraph
    p2 = Paragraph(sample_font)
    p2.add_word(Word("More", sample_font))
    p2.add_word(Word("text", sample_font))
    blocks.append(p2)

    # H1 - Chapter 2
    h1_2 = Heading(HeadingLevel.H1, sample_font)
    h1_2.add_word(Word("Chapter", sample_font))
    h1_2.add_word(Word("Two", sample_font))
    blocks.append(h1_2)

    return blocks


@pytest.fixture
def sample_page_style():
    """Create a standard page style for testing."""
    return PageStyle()


# ============================================================================
# RenderingPosition Tests
# ============================================================================

class TestRenderingPosition:
    """Tests for the RenderingPosition dataclass."""

    def test_default_initialization(self):
        """Test RenderingPosition initializes with default values."""
        pos = RenderingPosition()
        assert pos.chapter_index == 0
        assert pos.block_index == 0
        assert pos.word_index == 0
        assert pos.table_row == 0
        assert pos.table_col == 0
        assert pos.list_item_index == 0
        assert pos.remaining_pretext is None
        assert pos.page_y_offset == 0

    def test_custom_initialization(self):
        """Test RenderingPosition with custom values."""
        pos = RenderingPosition(
            chapter_index=2,
            block_index=5,
            word_index=10,
            table_row=1,
            table_col=2,
            list_item_index=3,
            remaining_pretext="test",
            page_y_offset=100
        )
        assert pos.chapter_index == 2
        assert pos.block_index == 5
        assert pos.word_index == 10
        assert pos.table_row == 1
        assert pos.table_col == 2
        assert pos.list_item_index == 3
        assert pos.remaining_pretext == "test"
        assert pos.page_y_offset == 100

    def test_to_dict_serialization(self):
        """Test serialization to dictionary."""
        pos = RenderingPosition(
            chapter_index=1,
            block_index=2,
            word_index=3
        )
        result = pos.to_dict()

        assert isinstance(result, dict)
        assert result['chapter_index'] == 1
        assert result['block_index'] == 2
        assert result['word_index'] == 3
        assert 'table_row' in result
        assert 'remaining_pretext' in result

    def test_from_dict_deserialization(self):
        """Test deserialization from dictionary."""
        data = {
            'chapter_index': 2,
            'block_index': 4,
            'word_index': 6,
            'table_row': 1,
            'table_col': 0,
            'list_item_index': 0,
            'remaining_pretext': None,
            'page_y_offset': 50
        }
        pos = RenderingPosition.from_dict(data)

        assert pos.chapter_index == 2
        assert pos.block_index == 4
        assert pos.word_index == 6
        assert pos.page_y_offset == 50

    def test_round_trip_serialization(self):
        """Test serialization and deserialization round trip."""
        original = RenderingPosition(
            chapter_index=3,
            block_index=7,
            word_index=15,
            remaining_pretext="hyphen-",
            page_y_offset=200
        )

        # Serialize and deserialize
        data = original.to_dict()
        restored = RenderingPosition.from_dict(data)

        assert original == restored

    def test_copy_creates_independent_copy(self):
        """Test that copy() creates an independent copy."""
        original = RenderingPosition(
            chapter_index=1,
            block_index=2,
            word_index=3
        )

        copy = original.copy()

        # Verify values match
        assert copy.chapter_index == original.chapter_index
        assert copy.block_index == original.block_index
        assert copy.word_index == original.word_index

        # Modify copy and verify original is unchanged
        copy.chapter_index = 99
        copy.word_index = 100

        assert original.chapter_index == 1
        assert original.word_index == 3

    def test_equality_same_values(self):
        """Test equality comparison with same values."""
        pos1 = RenderingPosition(chapter_index=1, block_index=2)
        pos2 = RenderingPosition(chapter_index=1, block_index=2)

        assert pos1 == pos2

    def test_equality_different_values(self):
        """Test equality comparison with different values."""
        pos1 = RenderingPosition(chapter_index=1, block_index=2)
        pos2 = RenderingPosition(chapter_index=1, block_index=3)

        assert pos1 != pos2

    def test_equality_with_non_position(self):
        """Test equality comparison with non-RenderingPosition object."""
        pos = RenderingPosition()

        assert pos != "not a position"
        assert pos != 42
        assert pos is not None

    def test_hashability(self):
        """Test that RenderingPosition is hashable and can be used in sets/dicts."""
        pos1 = RenderingPosition(chapter_index=1, block_index=2)
        pos2 = RenderingPosition(chapter_index=1, block_index=2)
        pos3 = RenderingPosition(chapter_index=1, block_index=3)

        # Test in set
        position_set = {pos1, pos2, pos3}
        assert len(position_set) == 2  # pos1 and pos2 should be same

        # Test as dict key
        position_dict = {pos1: "value1"}
        assert position_dict[pos2] == "value1"  # pos2 should access same key

    def test_hash_consistency(self):
        """Test that hash values are consistent."""
        pos1 = RenderingPosition(chapter_index=5, block_index=10)
        pos2 = RenderingPosition(chapter_index=5, block_index=10)

        assert hash(pos1) == hash(pos2)


# ============================================================================
# ChapterNavigator Tests
# ============================================================================

class TestChapterNavigator:
    """Tests for the ChapterNavigator class."""

    def test_initialization(self, sample_blocks_with_headings):
        """Test ChapterNavigator initialization."""
        navigator = ChapterNavigator(sample_blocks_with_headings)

        assert navigator.blocks == sample_blocks_with_headings
        assert len(navigator.chapters) > 0

    def test_build_chapter_map_finds_headings(self, sample_blocks_with_headings):
        """Test that chapter map correctly identifies headings."""
        navigator = ChapterNavigator(sample_blocks_with_headings)

        # Should find 3 headings: H1, H2, H1
        assert len(navigator.chapters) == 3

    def test_chapter_info_properties(self, sample_blocks_with_headings):
        """Test ChapterInfo contains correct properties."""
        navigator = ChapterNavigator(sample_blocks_with_headings)

        first_chapter = navigator.chapters[0]
        assert isinstance(first_chapter, ChapterInfo)
        assert first_chapter.title == "Chapter One"
        assert first_chapter.level == HeadingLevel.H1
        assert isinstance(first_chapter.position, RenderingPosition)
        assert first_chapter.block_index == 0

    def test_heading_text_extraction(self, sample_blocks_with_headings):
        """Test extraction of heading text from Word objects."""
        navigator = ChapterNavigator(sample_blocks_with_headings)

        # Check extracted titles
        titles = [ch.title for ch in navigator.chapters]
        assert "Chapter One" in titles
        assert "Section 1.1" in titles
        assert "Chapter Two" in titles

    def test_chapter_index_tracking(self, sample_blocks_with_headings):
        """Test that chapter indices are tracked correctly for H1 headings."""
        navigator = ChapterNavigator(sample_blocks_with_headings)

        # Note: The current implementation increments AFTER adding the chapter
        # So chapter_index values are: H1=0 (then inc to 1), H2=1, H1=1 (then inc to 2)
        # First H1 should be chapter 0
        assert navigator.chapters[0].position.chapter_index == 0
        # H2 gets chapter_index 1 (after first H1 increment)
        assert navigator.chapters[1].position.chapter_index == 1
        # Second H1 also gets chapter_index 1 (then increments to 2)
        assert navigator.chapters[2].position.chapter_index == 1

    def test_get_table_of_contents(self, sample_blocks_with_headings):
        """Test generation of table of contents."""
        navigator = ChapterNavigator(sample_blocks_with_headings)
        toc = navigator.get_table_of_contents()

        assert isinstance(toc, list)
        assert len(toc) == 3

        # Each entry should be (title, level, position)
        for entry in toc:
            assert isinstance(entry, tuple)
            assert len(entry) == 3
            assert isinstance(entry[0], str)  # title
            assert isinstance(entry[1], HeadingLevel)  # level
            assert isinstance(entry[2], RenderingPosition)  # position

    def test_get_chapter_position_exact_match(self, sample_blocks_with_headings):
        """Test finding chapter by exact title match."""
        navigator = ChapterNavigator(sample_blocks_with_headings)

        position = navigator.get_chapter_position("Chapter One")
        assert position is not None
        assert position.block_index == 0

    def test_get_chapter_position_case_insensitive(self, sample_blocks_with_headings):
        """Test case-insensitive chapter title matching."""
        navigator = ChapterNavigator(sample_blocks_with_headings)

        position = navigator.get_chapter_position("chapter one")
        assert position is not None

        position2 = navigator.get_chapter_position("CHAPTER ONE")
        assert position2 is not None
        assert position == position2

    def test_get_chapter_position_not_found(self, sample_blocks_with_headings):
        """Test getting position for non-existent chapter."""
        navigator = ChapterNavigator(sample_blocks_with_headings)

        position = navigator.get_chapter_position("Nonexistent Chapter")
        assert position is None

    def test_get_current_chapter_at_start(self, sample_blocks_with_headings):
        """Test getting current chapter at document start."""
        navigator = ChapterNavigator(sample_blocks_with_headings)

        position = RenderingPosition(chapter_index=0, block_index=0)
        current = navigator.get_current_chapter(position)

        assert current is not None
        assert current.title == "Chapter One"

    def test_get_current_chapter_in_middle(self, sample_blocks_with_headings):
        """Test getting current chapter in middle of document."""
        navigator = ChapterNavigator(sample_blocks_with_headings)

        # Position at block 3 should be in Chapter One
        position = RenderingPosition(chapter_index=0, block_index=3)
        current = navigator.get_current_chapter(position)

        assert current is not None
        assert "Chapter One" in current.title or "Section 1.1" in current.title

    def test_get_current_chapter_at_end(self, sample_blocks_with_headings):
        """Test getting current chapter at document end."""
        navigator = ChapterNavigator(sample_blocks_with_headings)

        position = RenderingPosition(chapter_index=1, block_index=4)
        current = navigator.get_current_chapter(position)

        assert current is not None
        assert current.title == "Chapter Two"

    def test_empty_document_no_chapters(self, sample_font):
        """Test navigator with document containing no headings."""
        # Document with only paragraphs
        blocks = [
            Paragraph(sample_font),
            Paragraph(sample_font)
        ]

        navigator = ChapterNavigator(blocks)

        assert len(navigator.chapters) == 0
        assert navigator.get_table_of_contents() == []

        position = RenderingPosition()
        assert navigator.get_current_chapter(position) is None

    def test_multiple_heading_levels(self, sample_font):
        """Test navigator with multiple heading levels H1-H6."""
        blocks = []

        # Create headings of each level
        for level in [HeadingLevel.H1, HeadingLevel.H2, HeadingLevel.H3,
                      HeadingLevel.H4, HeadingLevel.H5, HeadingLevel.H6]:
            heading = Heading(level, sample_font)
            heading.add_word(Word(f"Heading {level.value}", sample_font))
            blocks.append(heading)

        navigator = ChapterNavigator(blocks)

        # Should find all 6 headings
        assert len(navigator.chapters) == 6

        # Note: Due to implementation, H1 increments chapter_index AFTER being added
        # So: H1=0 (inc to 1), H2=1, H3=1, H4=1, H5=1, H6=1
        chapter_indices = [ch.position.chapter_index for ch in navigator.chapters]
        assert chapter_indices[0] == 0  # H1 gets 0, then increments
        assert all(idx == 1 for idx in chapter_indices[1:])  # H2-H6 all get 1


# ============================================================================
# FontScaler Tests
# ============================================================================

class TestFontScaler:
    """Tests for the FontScaler utility class."""

    def test_scale_font_no_change(self, sample_font):
        """Test scaling font with factor 1.0 returns same font."""
        scaled = FontScaler.scale_font(sample_font, 1.0)

        # Should return the original font when scale is 1.0
        assert scaled == sample_font

    def test_scale_font_double_size(self, sample_font):
        """Test scaling font to double size."""
        original_size = sample_font.font_size
        scaled = FontScaler.scale_font(sample_font, 2.0)

        assert scaled.font_size == original_size * 2

    def test_scale_font_half_size(self, sample_font):
        """Test scaling font to half size."""
        original_size = sample_font.font_size
        scaled = FontScaler.scale_font(sample_font, 0.5)

        assert scaled.font_size == int(original_size * 0.5)

    def test_scale_font_preserves_color(self, sample_font):
        """Test that font scaling preserves color."""
        scaled = FontScaler.scale_font(sample_font, 1.5)

        assert scaled.colour == sample_font.colour

    def test_scale_font_preserves_properties(self):
        """Test that font scaling preserves all font properties."""
        font = Font(
            font_size=14,
            colour=(255, 0, 0),
            weight="bold",
            style="italic"
        )

        scaled = FontScaler.scale_font(font, 1.5)

        assert scaled.colour == font.colour
        assert scaled.weight == font.weight
        assert scaled.style == font.style

    def test_scale_font_minimum_size(self):
        """Test that font scaling maintains minimum size of 1."""
        font = Font(font_size=2)

        # Scale to very small
        scaled = FontScaler.scale_font(font, 0.1)

        # Should be at least 1
        assert scaled.font_size >= 1

    def test_scale_word_spacing_no_change(self):
        """Test scaling word spacing with factor 1.0."""
        spacing = (5, 10)
        scaled = FontScaler.scale_word_spacing(spacing, 1.0)

        assert scaled == spacing

    def test_scale_word_spacing_double(self):
        """Test scaling word spacing to double."""
        spacing = (5, 10)
        scaled = FontScaler.scale_word_spacing(spacing, 2.0)

        assert scaled == (10, 20)

    def test_scale_word_spacing_maintains_minimum(self):
        """Test that word spacing maintains minimum values."""
        spacing = (2, 4)
        scaled = FontScaler.scale_word_spacing(spacing, 0.1)

        # Min spacing should be at least 1, max at least 2
        assert scaled[0] >= 1
        assert scaled[1] >= 2

    def test_scale_font_with_large_factor(self):
        """Test scaling with very large factor."""
        font = Font(font_size=12)
        scaled = FontScaler.scale_font(font, 10.0)

        assert scaled.font_size == 120

    def test_scale_font_with_small_factor(self):
        """Test scaling with very small factor."""
        font = Font(font_size=12)
        scaled = FontScaler.scale_font(font, 0.05)

        # Should still be at least 1
        assert scaled.font_size >= 1


# ============================================================================
# BidirectionalLayouter Tests (Basic)
# ============================================================================

class TestBidirectionalLayouter:
    """Tests for the BidirectionalLayouter class."""

    def test_initialization(self, sample_blocks_with_headings, sample_page_style):
        """Test BidirectionalLayouter initialization."""
        layouter = BidirectionalLayouter(
            sample_blocks_with_headings,
            sample_page_style,
            page_size=(800, 600)
        )

        assert layouter.blocks == sample_blocks_with_headings
        assert layouter.page_style == sample_page_style
        assert layouter.page_size == (800, 600)
        assert isinstance(layouter.chapter_navigator, ChapterNavigator)

    def test_position_compare_equal(self):
        """Test position comparison for equal positions."""
        layouter = BidirectionalLayouter([], PageStyle())

        pos1 = RenderingPosition(chapter_index=1, block_index=2, word_index=3)
        pos2 = RenderingPosition(chapter_index=1, block_index=2, word_index=3)

        assert layouter._position_compare(pos1, pos2) == 0

    def test_position_compare_chapter_difference(self):
        """Test position comparison with different chapters."""
        layouter = BidirectionalLayouter([], PageStyle())

        pos1 = RenderingPosition(chapter_index=1, block_index=2, word_index=3)
        pos2 = RenderingPosition(chapter_index=2, block_index=2, word_index=3)

        assert layouter._position_compare(pos1, pos2) == -1
        assert layouter._position_compare(pos2, pos1) == 1

    def test_position_compare_block_difference(self):
        """Test position comparison with different blocks."""
        layouter = BidirectionalLayouter([], PageStyle())

        pos1 = RenderingPosition(chapter_index=1, block_index=2, word_index=3)
        pos2 = RenderingPosition(chapter_index=1, block_index=5, word_index=3)

        assert layouter._position_compare(pos1, pos2) == -1
        assert layouter._position_compare(pos2, pos1) == 1

    def test_position_compare_word_difference(self):
        """Test position comparison with different words."""
        layouter = BidirectionalLayouter([], PageStyle())

        pos1 = RenderingPosition(chapter_index=1, block_index=2, word_index=3)
        pos2 = RenderingPosition(chapter_index=1, block_index=2, word_index=10)

        assert layouter._position_compare(pos1, pos2) == -1
        assert layouter._position_compare(pos2, pos1) == 1

    def test_scale_block_fonts_no_scaling(self, sample_font):
        """Test block font scaling with factor 1.0."""
        layouter = BidirectionalLayouter([], PageStyle())

        paragraph = Paragraph(sample_font)
        paragraph.add_word(Word("test", sample_font))

        scaled = layouter._scale_block_fonts(paragraph, 1.0)

        # Should return same block
        assert scaled == paragraph

    def test_estimate_page_start(self):
        """Test estimation of page start position."""
        layouter = BidirectionalLayouter([], PageStyle())

        end_pos = RenderingPosition(chapter_index=0, block_index=20, word_index=0)

        estimated = layouter._estimate_page_start(end_pos, 1.0)

        # Should estimate some blocks before the end position
        assert estimated.block_index < end_pos.block_index
        assert estimated.block_index >= 0

    def test_estimate_page_start_with_font_scale(self):
        """Test that font scale affects page start estimation."""
        layouter = BidirectionalLayouter([], PageStyle())

        end_pos = RenderingPosition(chapter_index=0, block_index=20, word_index=0)

        est_normal = layouter._estimate_page_start(end_pos, 1.0)
        est_large = layouter._estimate_page_start(end_pos, 2.0)

        # Larger font should estimate fewer blocks
        assert est_large.block_index >= est_normal.block_index

    def test_scale_block_fonts_paragraph(self, sample_font):
        """Test scaling fonts in a paragraph block."""
        layouter = BidirectionalLayouter([], PageStyle())

        paragraph = Paragraph(sample_font)
        paragraph.add_word(Word("Hello", sample_font))
        paragraph.add_word(Word("World", sample_font))

        scaled = layouter._scale_block_fonts(paragraph, 2.0)

        # Should be a new paragraph
        assert isinstance(scaled, Paragraph)
        assert scaled != paragraph

        # Check that words were scaled (words is a list, not a method)
        words = scaled.words if hasattr(
            scaled,
            'words') and isinstance(
            scaled.words,
            list) else list(
            scaled.words_iter())
        assert len(words) >= 2

    def test_scale_block_fonts_heading(self, sample_font):
        """Test scaling fonts in a heading block."""
        layouter = BidirectionalLayouter([], PageStyle())

        heading = Heading(HeadingLevel.H1, sample_font)
        heading.add_word(Word("Title", sample_font))

        scaled = layouter._scale_block_fonts(heading, 1.5)

        # Should be a new heading
        assert isinstance(scaled, Heading)
        assert scaled.level == HeadingLevel.H1

    def test_layout_block_on_page_unknown_type(self, sample_font):
        """Test layout of unknown block type skips gracefully."""
        layouter = BidirectionalLayouter([], PageStyle())

        # Create a mock block that's not a known type
        from pyWebLayout.concrete.page import Page
        from pyWebLayout.abstract.block import Block, BlockType
        page = Page(size=(800, 600), style=PageStyle())
        position = RenderingPosition()

        # Use a simple block (not Paragraph, Heading, Table, or HList)
        unknown_block = Block(BlockType.HORIZONTAL_RULE)

        success, new_pos = layouter._layout_block_on_page(
            unknown_block, page, position, 1.0)

        # Should skip and move to next block
        assert success is True
        assert new_pos.block_index == 1

    def test_layout_table_on_page(self, sample_font):
        """Test table layout on page (currently skips)."""
        layouter = BidirectionalLayouter([], PageStyle())

        from pyWebLayout.concrete.page import Page
        from pyWebLayout.abstract.block import Table

        page = Page(size=(800, 600), style=PageStyle())
        table = Table()
        position = RenderingPosition()

        success, new_pos = layouter._layout_table_on_page(table, page, position, 1.0)

        # Currently skips tables
        assert success is True
        assert new_pos.block_index == 1
        assert new_pos.table_row == 0
        assert new_pos.table_col == 0

    def test_layout_list_on_page(self, sample_font):
        """Test list layout on page (currently skips)."""
        layouter = BidirectionalLayouter([], PageStyle())

        from pyWebLayout.concrete.page import Page
        from pyWebLayout.abstract.block import HList

        page = Page(size=(800, 600), style=PageStyle())
        hlist = HList()
        position = RenderingPosition()

        success, new_pos = layouter._layout_list_on_page(hlist, page, position, 1.0)

        # Currently skips lists
        assert success is True
        assert new_pos.block_index == 1
        assert new_pos.list_item_index == 0

    def test_render_page_forward_simple(
            self,
            sample_blocks_with_headings,
            sample_page_style):
        """Test forward page rendering with simple blocks."""
        layouter = BidirectionalLayouter(
            sample_blocks_with_headings,
            sample_page_style,
            page_size=(800, 600)
        )

        position = RenderingPosition()
        page, next_pos = layouter.render_page_forward(position, font_scale=1.0)

        # Should render a page
        from pyWebLayout.concrete.page import Page
        assert isinstance(page, Page)

        # Position should advance
        assert next_pos.block_index >= position.block_index

    def test_render_page_forward_with_font_scale(
            self, sample_blocks_with_headings, sample_page_style):
        """Test forward rendering with font scaling."""
        layouter = BidirectionalLayouter(
            sample_blocks_with_headings,
            sample_page_style,
            page_size=(800, 600)
        )

        position = RenderingPosition()

        # Render with normal font
        page1, next_pos1 = layouter.render_page_forward(position, font_scale=1.0)

        # Render with larger font (should fit less content)
        page2, next_pos2 = layouter.render_page_forward(position, font_scale=2.0)

        # Both should produce pages
        assert page1 is not None
        assert page2 is not None

    def test_render_page_forward_at_end(
            self,
            sample_blocks_with_headings,
            sample_page_style):
        """Test forward rendering at end of document."""
        layouter = BidirectionalLayouter(
            sample_blocks_with_headings,
            sample_page_style,
            page_size=(800, 600)
        )

        # Position at last block
        position = RenderingPosition(block_index=len(sample_blocks_with_headings) - 1)
        page, next_pos = layouter.render_page_forward(position, font_scale=1.0)

        # Should still render a page
        assert page is not None

    def test_render_page_forward_beyond_end(
            self, sample_blocks_with_headings, sample_page_style):
        """Test forward rendering beyond document end."""
        layouter = BidirectionalLayouter(
            sample_blocks_with_headings,
            sample_page_style,
            page_size=(800, 600)
        )

        # Position beyond last block
        position = RenderingPosition(block_index=len(sample_blocks_with_headings) + 10)
        page, next_pos = layouter.render_page_forward(position, font_scale=1.0)

        # Should handle gracefully
        assert page is not None

    def test_render_page_backward_simple(
            self,
            sample_blocks_with_headings,
            sample_page_style):
        """Test backward page rendering."""
        layouter = BidirectionalLayouter(
            sample_blocks_with_headings,
            sample_page_style,
            page_size=(800, 600)
        )

        # Start from middle of document
        end_position = RenderingPosition(block_index=3)
        page, start_pos = layouter.render_page_backward(end_position, font_scale=1.0)

        # Should render a page
        assert page is not None

        # Start position should be before or at end position
        assert start_pos.block_index <= end_position.block_index

    def test_adjust_start_estimate_overshot(self):
        """Test adjustment when forward render overshoots target."""
        layouter = BidirectionalLayouter([], PageStyle())

        current_start = RenderingPosition(block_index=5)
        target_end = RenderingPosition(block_index=10)
        actual_end = RenderingPosition(block_index=12)  # Overshot (went too far)

        adjusted = layouter._adjust_start_estimate(
            current_start, target_end, actual_end)

        # Overshot means we rendered too far forward
        # So we need to start EARLIER (decrease block_index) to not go as far
        assert adjusted.block_index < current_start.block_index

    def test_adjust_start_estimate_undershot(self):
        """Test adjustment when forward render undershoots target."""
        layouter = BidirectionalLayouter([], PageStyle())

        current_start = RenderingPosition(block_index=5)
        target_end = RenderingPosition(block_index=10)
        actual_end = RenderingPosition(block_index=8)  # Undershot (didn't go far enough)

        adjusted = layouter._adjust_start_estimate(
            current_start, target_end, actual_end)

        # Undershot means we didn't render far enough forward
        # So we need to start LATER (increase block_index) to include more content
        assert adjusted.block_index > current_start.block_index

    def test_adjust_start_estimate_exact(self):
        """Test adjustment when forward render hits target exactly."""
        layouter = BidirectionalLayouter([], PageStyle())

        current_start = RenderingPosition(block_index=5)
        target_end = RenderingPosition(block_index=10)
        actual_end = RenderingPosition(block_index=10)  # Exact

        adjusted = layouter._adjust_start_estimate(
            current_start, target_end, actual_end)

        # Should return same or similar position
        assert adjusted.block_index >= 0

    def test_layout_paragraph_on_page_with_pretext(
            self, sample_font, sample_page_style):
        """Test paragraph layout with pretext (hyphenated word continuation)."""
        layouter = BidirectionalLayouter([], sample_page_style, page_size=(800, 600))

        from pyWebLayout.concrete.page import Page

        paragraph = Paragraph(sample_font)
        paragraph.add_word(Word("continuation", sample_font))

        page = Page(size=(800, 600), style=sample_page_style)
        position = RenderingPosition(remaining_pretext="pre-")

        success, new_pos = layouter._layout_paragraph_on_page(
            paragraph, page, position, 1.0)

        # Should attempt to layout
        assert isinstance(success, bool)
        assert isinstance(new_pos, RenderingPosition)

    def test_layout_paragraph_success(self, sample_font, sample_page_style):
        """Test successful paragraph layout."""
        layouter = BidirectionalLayouter([], sample_page_style, page_size=(800, 600))

        from pyWebLayout.concrete.page import Page

        paragraph = Paragraph(sample_font)
        paragraph.add_word(Word("Short", sample_font))
        paragraph.add_word(Word("text", sample_font))

        page = Page(size=(800, 600), style=sample_page_style)
        position = RenderingPosition()

        success, new_pos = layouter._layout_paragraph_on_page(
            paragraph, page, position, 1.0)

        # Should complete successfully
        assert isinstance(success, bool)

    def test_layout_heading_on_page(self, sample_font, sample_page_style):
        """Test heading layout delegates to paragraph layout."""
        layouter = BidirectionalLayouter([], sample_page_style, page_size=(800, 600))

        from pyWebLayout.concrete.page import Page

        heading = Heading(HeadingLevel.H1, sample_font)
        heading.add_word(Word("Heading", sample_font))
        heading.add_word(Word("Text", sample_font))

        page = Page(size=(800, 600), style=sample_page_style)
        position = RenderingPosition()

        success, new_pos = layouter._layout_heading_on_page(
            heading, page, position, 1.0)

        # Should attempt to layout like a paragraph
        assert isinstance(success, bool)
        assert isinstance(new_pos, RenderingPosition)

    def test_empty_blocks_list(self, sample_page_style):
        """Test rendering with empty blocks list."""
        layouter = BidirectionalLayouter([], sample_page_style, page_size=(800, 600))

        position = RenderingPosition()
        page, next_pos = layouter.render_page_forward(position, font_scale=1.0)

        # Should handle empty document
        assert page is not None
        assert next_pos == position  # No progress possible


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
