"""
Integration tests for the PyOS demo gallery.

Drives the full demo app through every exhibit using the Espresso-esque
UI test harness.  Read it like a guided tour of pyos capabilities.

Uses:  MockScreen + HarnessApplication from pyos.testing
Under test:  pyos/examples/demo.py (the gallery app)
"""

import curses
import pytest

from pyos.testing import MockScreen, HarnessApplication
from pyos import Keys
from pyos.examples.demo import (
    DemoMenuActivity,
    ContactsBrowser,
    DataDashboard,
    NotepadDemo,
    TaskManagerDemo,
    CommentThreadDemo,
    AccordionShowcase,
    LayoutShowcase,
    ConstraintLayoutDemo,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def navigate_to(app, label):
    """From the DemoMenuActivity, scroll to *label* and press ENTER."""
    menu = app.current_activity()
    items = menu.items
    target = items.index(label)
    current = int(app.state("list").get("selected_index", 0))
    for _ in range(target - current):
        app.send_key(curses.KEY_DOWN)
    app.send_key(Keys.ENTER)
    app.drain()


# ===========================================================================
# 1. Gallery navigation -- the lobby
# ===========================================================================

class TestGalleryNavigation:
    """Start at the menu, scroll around, peek into an exhibit, come back."""

    def test_gallery_renders_on_launch(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        mock_screen.assert_text_on_screen("PyOS Gallery")
        mock_screen.assert_text_on_screen("Contacts Browser")
        mock_screen.assert_text_on_screen("Data Dashboard")
        mock_screen.assert_text_on_screen("Task Manager")

    def test_welcome_text_visible(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        mock_screen.assert_text_on_screen("Welcome to the PyOS Component Gallery!")

    def test_first_item_is_selected(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        mock_screen.assert_text_has_attr("Contacts Browser", curses.A_REVERSE)

    def test_scroll_through_gallery(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        app.send_key(curses.KEY_DOWN)
        mock_screen.assert_text_has_attr("Data Dashboard", curses.A_REVERSE)
        app.send_key(curses.KEY_DOWN)
        mock_screen.assert_text_has_attr("Notepad", curses.A_REVERSE)
        app.send_key(curses.KEY_DOWN)
        mock_screen.assert_text_has_attr("Task Manager", curses.A_REVERSE)

    def test_enter_exhibit_and_esc_back(self, app, mock_screen):
        """Open Contacts Browser, verify we arrived, ESC back to gallery."""
        app.start_activity(DemoMenuActivity())
        assert app.activity_stack_depth() == 1

        app.send_key(Keys.ENTER)  # opens "Contacts Browser" (first item)
        app.drain()
        assert app.activity_stack_depth() == 2
        mock_screen.assert_text_on_screen("Contacts Browser")

        app.send_key(Keys.ESC)
        app.drain()
        assert app.activity_stack_depth() == 1
        mock_screen.assert_text_on_screen("PyOS Gallery")

    def test_selection_counter_updates(self, app, mock_screen):
        """The bottom bar shows the selection position after scrolling."""
        app.start_activity(DemoMenuActivity())
        app.send_key(curses.KEY_DOWN)
        app.send_key(curses.KEY_DOWN)
        # After two downs we're at index 2 -> "3/9"
        mock_screen.assert_text_on_screen("3/9")


# ===========================================================================
# 2. Contacts Browser -- search + scroll list with TAB focus cycling
# ===========================================================================

class TestContactsBrowser:
    """Test the filterable contacts list with TAB focus cycling."""

    def test_renders_contacts(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Contacts Browser")
        mock_screen.assert_text_on_screen("Alice Johnson")
        mock_screen.assert_text_on_screen("Search")

    def test_contact_count_shown(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Contacts Browser")
        mock_screen.assert_text_on_screen("20 contacts")

    def test_filter_contacts_by_typing(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Contacts Browser")
        app.send_text("eng")
        # Should filter to Engineering dept contacts
        mock_screen.assert_text_on_screen("Engineering")
        # Non-engineering contacts should not be visible
        mock_screen.assert_text_not_on_screen("Frank Hughes")

    def test_filter_updates_count(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Contacts Browser")
        app.send_text("alice")
        mock_screen.assert_text_on_screen("1 contact")

    def test_tab_switches_focus_to_results(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Contacts Browser")
        # Initially search is focused
        ctx = app.state("search")
        assert ctx.get("focused") is True
        results_ctx = app.state("results")
        assert results_ctx.get("focused") is False

        # TAB switches to results
        app.send_key(Keys.TAB)
        ctx = app.state("search")
        assert ctx.get("focused") is False
        results_ctx = app.state("results")
        assert results_ctx.get("focused") is True

    def test_tab_cycles_back_to_search(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Contacts Browser")
        app.send_key(Keys.TAB)  # -> results
        app.send_key(Keys.TAB)  # -> search
        ctx = app.state("search")
        assert ctx.get("focused") is True

    def test_navigate_results_after_tab(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Contacts Browser")
        app.send_key(Keys.TAB)  # focus results
        app.send_key(curses.KEY_DOWN)
        app.send_key(curses.KEY_DOWN)
        results_ctx = app.state("results")
        assert results_ctx["selected_index"] == 2

    def test_esc_returns_to_gallery(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Contacts Browser")
        app.send_key(Keys.ESC)
        app.drain()
        mock_screen.assert_text_on_screen("PyOS Gallery")


# ===========================================================================
# 3. Data Dashboard -- table with summary
# ===========================================================================

class TestDataDashboard:
    """Open the dashboard and verify table + summary rendering."""

    def test_summary_visible(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Data Dashboard")
        mock_screen.assert_text_on_screen("Projects:")
        mock_screen.assert_text_on_screen("Active:")
        mock_screen.assert_text_on_screen("Open bugs:")

    def test_headers_visible_and_bold(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Data Dashboard")
        mock_screen.assert_text_on_screen("Project")
        mock_screen.assert_text_on_screen("Status")
        mock_screen.assert_text_on_screen("Coverage")
        mock_screen.assert_text_has_attr("Coverage", curses.A_BOLD)

    def test_first_row_highlighted(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Data Dashboard")
        mock_screen.assert_text_has_attr("auth-service", curses.A_REVERSE)

    def test_scroll_rows(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Data Dashboard")
        for _ in range(4):
            app.send_key(curses.KEY_DOWN)
        mock_screen.assert_text_on_screen("5/12")
        mock_screen.assert_text_on_screen("api-gateway")

    def test_delimiter_visible(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Data Dashboard")
        assert mock_screen.find_text("---")

    def test_esc_returns_to_gallery(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Data Dashboard")
        app.send_key(Keys.ESC)
        app.drain()
        mock_screen.assert_text_on_screen("PyOS Gallery")


# ===========================================================================
# 4. Notepad -- text input, live stats, submission log
# ===========================================================================

class TestNotepadDemo:
    """Test the notepad with live stats and submission log."""

    def test_input_label_visible(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Notepad")
        mock_screen.assert_text_on_screen("Note")

    def test_initial_stats(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Notepad")
        mock_screen.assert_text_on_screen("Chars: 0")
        mock_screen.assert_text_on_screen("Words: 0")

    def test_type_and_stats_update(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Notepad")
        app.send_text("hello world")
        mock_screen.assert_text_on_screen("Chars: 11")
        mock_screen.assert_text_on_screen("Words: 2")

    def test_submit_clears_input_and_logs(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Notepad")
        app.send_text("first note")
        app.send_key(Keys.ENTER)
        mock_screen.assert_text_on_screen("[1] first note")
        mock_screen.assert_text_on_screen("1 note submitted")
        # Input should be cleared
        mock_screen.assert_text_on_screen("Chars: 0")

    def test_multiple_submissions(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Notepad")
        app.send_text("alpha")
        app.send_key(Keys.ENTER)
        app.send_text("beta")
        app.send_key(Keys.ENTER)
        mock_screen.assert_text_on_screen("2 notes submitted")
        mock_screen.assert_text_on_screen("[1] alpha")
        mock_screen.assert_text_on_screen("[2] beta")

    def test_backspace_updates_stats(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Notepad")
        app.send_text("abc")
        mock_screen.assert_text_on_screen("Chars: 3")
        app.send_key(Keys.BACKSPACE)
        mock_screen.assert_text_on_screen("Chars: 2")

    def test_esc_returns_to_gallery(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Notepad")
        app.send_key(Keys.ESC)
        app.drain()
        mock_screen.assert_text_on_screen("PyOS Gallery")


# ===========================================================================
# 5. Task Manager -- context menus + state mutations
# ===========================================================================

class TestTaskManagerDemo:
    """Test the task list with context menus for complete/delete/view."""

    def test_tasks_visible(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        mock_screen.assert_text_on_screen("Set up CI pipeline")
        mock_screen.assert_text_on_screen("[HIGH]")

    def test_initial_counts(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        mock_screen.assert_text_on_screen("Total: 12")
        mock_screen.assert_text_on_screen("Done: 0")
        mock_screen.assert_text_on_screen("Pending: 12")

    def test_unchecked_boxes_visible(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        mock_screen.assert_text_on_screen("[ ]")

    def test_open_context_menu(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        app.send_key(ord(' '))
        mock_screen.assert_text_on_screen("[Complete]")
        mock_screen.assert_text_on_screen("[Delete]")
        mock_screen.assert_text_on_screen("[View]")

    def test_complete_task(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        app.send_key(ord(' '))  # open menu
        # "Complete" should be first item
        app.send_key(Keys.ENTER)  # pick Complete
        mock_screen.assert_text_on_screen("[x]")
        mock_screen.assert_text_on_screen("Done: 1")
        mock_screen.assert_text_on_screen("Pending: 11")

    def test_toggle_complete_back(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        # Complete then uncomplete
        app.send_key(ord(' '))
        app.send_key(Keys.ENTER)  # complete
        mock_screen.assert_text_on_screen("completed:")
        app.send_key(ord(' '))
        app.send_key(Keys.ENTER)  # uncomplete
        mock_screen.assert_text_on_screen("reopened:")
        mock_screen.assert_text_on_screen("Done: 0")

    def test_delete_task(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        app.send_key(ord(' '))          # open menu
        app.send_key(curses.KEY_RIGHT)  # -> Delete
        app.send_key(Keys.ENTER)        # pick Delete
        mock_screen.assert_text_on_screen("Total: 11")
        mock_screen.assert_text_on_screen("Deleted:")

    def test_view_task_detail(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        app.send_key(ord(' '))          # open menu
        app.send_key(curses.KEY_RIGHT)  # -> Delete
        app.send_key(curses.KEY_RIGHT)  # -> View
        app.send_key(Keys.ENTER)        # pick View
        app.drain()
        mock_screen.assert_text_on_screen("Task Detail")
        mock_screen.assert_text_on_screen("Set up CI pipeline")
        mock_screen.assert_text_on_screen("HIGH")

    def test_view_detail_and_return(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        app.send_key(ord(' '))
        app.send_key(curses.KEY_RIGHT)
        app.send_key(curses.KEY_RIGHT)
        app.send_key(Keys.ENTER)
        app.drain()
        assert app.activity_stack_depth() == 3
        app.send_key(Keys.ESC)
        app.drain()
        mock_screen.assert_text_on_screen("Task Manager")

    def test_navigate_menu_with_arrows(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        app.send_key(ord(' '))
        mock_screen.assert_text_has_attr("[Complete]", curses.A_REVERSE)
        app.send_key(curses.KEY_RIGHT)
        mock_screen.assert_text_has_attr("[Delete]", curses.A_REVERSE)
        app.send_key(curses.KEY_RIGHT)
        mock_screen.assert_text_has_attr("[View]", curses.A_REVERSE)

    def test_esc_closes_menu_first(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        app.send_key(ord(' '))
        mock_screen.assert_text_on_screen("[Complete]")
        app.send_key(Keys.ESC)
        mock_screen.assert_text_not_on_screen("[Complete]")
        mock_screen.assert_text_on_screen("Task Manager")

    def test_esc_returns_to_gallery(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Task Manager")
        app.send_key(Keys.ESC)
        app.drain()
        mock_screen.assert_text_on_screen("PyOS Gallery")


# ===========================================================================
# 6. Comment Thread -- hierarchical tree with multi-line items
# ===========================================================================

class TestCommentThreadDemo:
    """Test the comment thread with nested replies."""

    def test_comments_visible(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Comment Thread")
        mock_screen.assert_text_on_screen("@alice")
        mock_screen.assert_text_on_screen("layout engine")

    def test_replies_visible(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Comment Thread")
        mock_screen.assert_text_on_screen("@bob")
        mock_screen.assert_text_on_screen("flex constraints")

    def test_nested_replies_indented(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Comment Thread")
        # bob is depth 1, should be indented
        pos = mock_screen.find_text_at("@bob")
        assert pos is not None
        _, col = pos
        assert col >= 4  # depth 1 -> 4 spaces indent

    def test_deeper_nesting_more_indented(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Comment Thread")
        # carol is depth 2, should be more indented than bob
        pos_carol = mock_screen.find_text_at("@carol")
        assert pos_carol is not None
        _, col = pos_carol
        assert col >= 8  # depth 2 -> 8 spaces indent

    def test_scroll_through_comments(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Comment Thread")
        for _ in range(4):
            app.send_key(curses.KEY_DOWN)
        # Should have scrolled through several comment lines
        tree = app.state("thread")
        assert tree["selected_index"] == 4

    def test_esc_returns_to_gallery(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Comment Thread")
        app.send_key(Keys.ESC)
        app.drain()
        mock_screen.assert_text_on_screen("PyOS Gallery")


# ===========================================================================
# 7. Accordion Showcase -- collapsible sections
# ===========================================================================

class TestAccordionShowcase:
    """Test the accordion component with multiple sections."""

    def test_sections_visible(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Accordion Showcase")
        screen.assert_text_on_screen("About PyOS")
        screen.assert_text_on_screen("Features")
        screen.assert_text_on_screen("Keyboard Shortcuts")

    def test_about_expanded_by_default(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Accordion Showcase")
        screen.assert_text_on_screen("Python TUI framework")
        screen.assert_text_on_screen("collapse")

    def test_shortcuts_collapsed_by_default(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Accordion Showcase")
        ctx = app.state("shortcuts")
        assert ctx.get("collapsed") is True
        screen.assert_text_on_screen("expand")

    def test_empty_section_disabled(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Accordion Showcase")
        ctx = app.state("empty")
        assert ctx.get("disabled") is True

    def test_toggle_collapse_with_enter(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Accordion Showcase")
        # Focus is on "about" which is expanded
        ctx = app.state("about")
        assert ctx.get("collapsed") is False
        # Press Enter to collapse it
        app.send_key(Keys.ENTER)
        ctx = app.state("about")
        assert ctx.get("collapsed") is True

    def test_tab_cycles_through_sections(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Accordion Showcase")
        # Start focused on "about"
        assert app.state("about").get("focused") is True
        # TAB to features
        app.send_key(Keys.TAB)
        assert app.state("features").get("focused") is True
        assert app.state("about").get("focused") is False
        # TAB to shortcuts
        app.send_key(Keys.TAB)
        assert app.state("shortcuts").get("focused") is True
        # TAB wraps back to about
        app.send_key(Keys.TAB)
        assert app.state("about").get("focused") is True

    def test_expand_shortcuts_with_space(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Accordion Showcase")
        # Navigate to shortcuts (collapsed)
        app.send_key(Keys.TAB)  # -> features
        app.send_key(Keys.TAB)  # -> shortcuts
        app.send_key(ord(' '))  # toggle expand
        ctx = app.state("shortcuts")
        assert ctx.get("collapsed") is False

    def test_esc_returns_to_gallery(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Accordion Showcase")
        app.send_key(Keys.ESC)
        app.drain()
        screen.assert_text_on_screen("PyOS Gallery")


# ===========================================================================
# 8. Assertion demos -- intentionally wrong assertions to show failure messages
# ===========================================================================

class TestAssertionFailureDemos:
    """
    These tests intentionally trigger assertion failures to show what the
    harness error messages look like.
    """

    def test_wrong_text_gives_helpful_message(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        with pytest.raises(AssertionError, match="Expected to find"):
            mock_screen.assert_text_on_screen("This text is not on screen")

    def test_wrong_attr_gives_helpful_message(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        with pytest.raises(AssertionError, match="has attr"):
            mock_screen.assert_text_has_attr("PyOS Gallery", curses.A_DIM)

    def test_unexpected_text_still_present(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        with pytest.raises(AssertionError, match="NOT be on screen"):
            mock_screen.assert_text_not_on_screen("PyOS Gallery")

    def test_attr_present_when_expected_absent(self, app, mock_screen):
        app.start_activity(DemoMenuActivity())
        with pytest.raises(AssertionError, match="NOT to have"):
            mock_screen.assert_text_not_has_attr("Contacts Browser", curses.A_REVERSE)


# ===========================================================================
# 9. Speed run -- blast through every screen
# ===========================================================================

class TestSpeedRun:
    """
    The ultimate integration test: start at the gallery, visit every single
    exhibit, do something interesting in each, ESC back, and confirm the
    gallery is intact at the end.
    """

    def test_full_gallery_speed_run(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        screen.assert_text_on_screen("PyOS Gallery")

        # --- Contacts Browser ---
        navigate_to(app, "Contacts Browser")
        screen.assert_text_on_screen("Alice Johnson")
        app.send_text("eng")
        screen.assert_text_on_screen("Engineering")
        app.send_key(Keys.TAB)  # switch to results
        app.send_key(curses.KEY_DOWN)
        app.send_key(Keys.ESC)
        app.drain()

        # --- Data Dashboard ---
        navigate_to(app, "Data Dashboard")
        screen.assert_text_has_attr("Coverage", curses.A_BOLD)
        for _ in range(5):
            app.send_key(curses.KEY_DOWN)
        screen.assert_text_on_screen("6/12")
        app.send_key(Keys.ESC)
        app.drain()

        # --- Notepad ---
        navigate_to(app, "Notepad")
        app.send_text("speed run!")
        screen.assert_text_on_screen("Chars: 10")
        screen.assert_text_on_screen("Words: 2")
        app.send_key(Keys.ENTER)
        screen.assert_text_on_screen("1 note submitted")
        app.send_key(Keys.ESC)
        app.drain()

        # --- Task Manager ---
        navigate_to(app, "Task Manager")
        screen.assert_text_on_screen("Set up CI pipeline")
        app.send_key(ord(' '))
        screen.assert_text_on_screen("[Complete]")
        app.send_key(Keys.ENTER)  # complete first task
        screen.assert_text_on_screen("[x]")
        app.send_key(Keys.ESC)
        app.drain()

        # --- Comment Thread ---
        navigate_to(app, "Comment Thread")
        screen.assert_text_on_screen("@alice")
        for _ in range(3):
            app.send_key(curses.KEY_DOWN)
        app.send_key(Keys.ESC)
        app.drain()

        # --- Accordion Showcase ---
        navigate_to(app, "Accordion Showcase")
        screen.assert_text_on_screen("About PyOS")
        app.send_key(Keys.ENTER)  # toggle collapse
        app.send_key(Keys.TAB)    # cycle focus
        app.send_key(Keys.ESC)
        app.drain()

        # --- Layout Lab ---
        navigate_to(app, "Layout Lab")
        screen.assert_text_on_screen("Layout Lab")
        screen.assert_text_on_screen("Panel A")
        app.send_key(ord("2"))  # 50/50
        screen.assert_text_on_screen("A flex=1")
        app.send_key(Keys.ESC)
        app.drain()

        # --- Constraint Layout ---
        navigate_to(app, "Constraint Layout")
        screen.assert_text_on_screen("Constraint Layout")
        screen.assert_text_on_screen("Length")
        app.send_key(Keys.ESC)
        app.drain()

        # --- Back at the gallery, intact ---
        screen.assert_text_on_screen("PyOS Gallery")
        assert app.activity_stack_depth() == 1


# ===========================================================================
# 10. Layout Lab -- verify preset switching
# ===========================================================================

class TestLayoutShowcase:
    """Open the Layout Lab and toggle a couple of presets."""

    def test_default_panels_visible(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Layout Lab")
        screen.assert_text_on_screen("Panel A")
        screen.assert_text_on_screen("Panel B")
        screen.assert_text_on_screen("Layout Lab")

    def test_collapse_b_preset(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Layout Lab")
        app.send_key(ord("6"))  # Collapse B
        screen.assert_text_on_screen("Collapse B")

    def test_fifty_fifty_preset(self, make_app):
        app, screen = make_app(rows=30, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Layout Lab")
        app.send_key(ord("2"))  # 50/50 flex split
        screen.assert_text_on_screen("A flex=1 m=1 | B flex=1 m=1")


# ===========================================================================
# 11. Constraint Layout -- just make sure it renders
# ===========================================================================

class TestConstraintLayoutDemo:
    def test_renders_all_regions(self, make_app):
        app, screen = make_app(rows=40, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Constraint Layout")
        screen.assert_text_on_screen("Constraint Layout")
        screen.assert_text_on_screen("Length")
        screen.assert_text_on_screen("Percentage")
        screen.assert_text_on_screen("Fill")
        screen.assert_text_on_screen("Min")

    def test_esc_returns(self, make_app):
        app, screen = make_app(rows=40, cols=100)
        app.start_activity(DemoMenuActivity())
        navigate_to(app, "Constraint Layout")
        app.send_key(Keys.ESC)
        app.drain()
        screen.assert_text_on_screen("PyOS Gallery")
