import pytest

from textual.app import App
from textual.screen import Screen
from textual.widget import Widget


class Focusable(Widget, can_focus=True):
    pass


class NonFocusable(Widget, can_focus=False, can_focus_children=False):
    pass


class ChildrenFocusableOnly(Widget, can_focus=False, can_focus_children=True):
    pass


@pytest.fixture
def screen() -> Screen:
    app = App()
    app._set_active()
    app.push_screen(Screen())

    screen = app.screen

    # The classes even/odd alternate along the focus chain.
    # The classes in/out identify nested widgets.
    screen._add_children(
        Focusable(id="foo", classes="a"),
        NonFocusable(id="bar"),
        Focusable(Focusable(id="Paul", classes="c"), id="container1", classes="b"),
        NonFocusable(Focusable(id="Jessica", classes="a"), id="container2"),
        Focusable(id="baz", classes="b"),
        ChildrenFocusableOnly(Focusable(id="child", classes="c")),
    )

    return screen


def test_focus_chain():
    app = App()
    app._set_active()
    app.push_screen(Screen())

    screen = app.screen

    # Check empty focus chain
    assert not screen.focus_chain

    app.screen._add_children(
        Focusable(id="foo"),
        NonFocusable(id="bar"),
        Focusable(Focusable(id="Paul"), id="container1"),
        NonFocusable(Focusable(id="Jessica"), id="container2"),
        Focusable(id="baz"),
        ChildrenFocusableOnly(Focusable(id="child")),
    )

    focus_chain = [widget.id for widget in screen.focus_chain]
    assert focus_chain == ["foo", "container1", "Paul", "baz", "child"]


def test_focus_next_and_previous(screen: Screen):
    assert screen.focus_next().id == "foo"
    assert screen.focus_next().id == "container1"
    assert screen.focus_next().id == "Paul"
    assert screen.focus_next().id == "baz"
    assert screen.focus_next().id == "child"

    assert screen.focus_previous().id == "baz"
    assert screen.focus_previous().id == "Paul"
    assert screen.focus_previous().id == "container1"
    assert screen.focus_previous().id == "foo"


def test_focus_next_wrap_around(screen: Screen):
    """Ensure focusing the next widget wraps around the focus chain."""
    screen.set_focus(screen.query_one("#child"))
    assert screen.focused.id == "child"

    assert screen.focus_next().id == "foo"


def test_focus_previous_wrap_around(screen: Screen):
    """Ensure focusing the previous widget wraps around the focus chain."""
    screen.set_focus(screen.query_one("#foo"))
    assert screen.focused.id == "foo"

    assert screen.focus_previous().id == "child"


def test_wrap_around_selector(screen: Screen):
    """Ensure moving focus in both directions wraps around the focus chain."""
    screen.set_focus(screen.query_one("#foo"))
    assert screen.focused.id == "foo"

    assert screen.focus_previous("#Paul").id == "Paul"
    assert screen.focus_next("#foo").id == "foo"


def test_no_focus_empty_selector(screen: Screen):
    """Ensure focus is cleared when selector matches nothing."""
    assert screen.focus_next("#bananas") is None
    assert screen.focus_previous("#bananas") is None

    screen.set_focus(screen.query_one("#foo"))
    assert screen.focused is not None
    assert screen.focus_next("bananas") is None
    assert screen.focused is None

    screen.set_focus(screen.query_one("#foo"))
    assert screen.focused is not None
    assert screen.focus_previous("bananas") is None
    assert screen.focused is None


def test_focus_next_and_previous_with_type_selector(screen: Screen):
    """Move focus with a selector that matches the currently focused node."""
    screen.set_focus(screen.query_one("#Paul"))
    assert screen.focused.id == "Paul"

    assert screen.focus_next(Focusable).id == "baz"
    assert screen.focus_next(Focusable).id == "child"

    assert screen.focus_previous(Focusable).id == "baz"
    assert screen.focus_previous(Focusable).id == "Paul"
    assert screen.focus_previous(Focusable).id == "container1"
    assert screen.focus_previous(Focusable).id == "foo"


def test_focus_next_and_previous_with_str_selector(screen: Screen):
    """Move focus with a selector that matches the currently focused node."""
    screen.set_focus(screen.query_one("#foo"))
    assert screen.focused.id == "foo"

    assert screen.focus_next(".a").id == "foo"
    assert screen.focus_next(".c").id == "Paul"
    assert screen.focus_next(".c").id == "child"

    assert screen.focus_previous(".c").id == "Paul"
    assert screen.focus_previous(".a").id == "foo"


def test_focus_next_and_previous_with_type_selector_without_self():
    """Test moving the focus with a selector that does not match the currently focused node."""
    app = App()
    app._set_active()
    app.push_screen(Screen())

    screen = app.screen

    from textual.containers import Horizontal, VerticalScroll
    from textual.widgets import Button, Input, Switch

    screen._add_children(
        VerticalScroll(
            Horizontal(
                Input(id="w3"),
                Switch(id="w4"),
                Input(id="w5"),
                Button(id="w6"),
                Switch(id="w7"),
                id="w2",
            ),
            Horizontal(
                Button(id="w9"),
                Switch(id="w10"),
                Button(id="w11"),
                Input(id="w12"),
                Input(id="w13"),
                id="w8",
            ),
            id="w1",
        )
    )

    screen.set_focus(screen.query_one("#w3"))
    assert screen.focused.id == "w3"

    assert screen.focus_next(Button).id == "w6"
    assert screen.focus_next(Switch).id == "w7"
    assert screen.focus_next(Input).id == "w12"

    assert screen.focus_previous(Button).id == "w11"
    assert screen.focus_previous(Switch).id == "w10"
    assert screen.focus_previous(Button).id == "w9"
    assert screen.focus_previous(Input).id == "w5"


def test_focus_next_and_previous_with_str_selector_without_self(screen: Screen):
    """Test moving the focus with a selector that does not match the currently focused node."""
    screen.set_focus(screen.query_one("#foo"))
    assert screen.focused.id == "foo"

    assert screen.focus_next(".c").id == "Paul"
    assert screen.focus_next(".b").id == "baz"
    assert screen.focus_next(".c").id == "child"

    assert screen.focus_previous(".a").id == "foo"
    assert screen.focus_previous(".a").id == "foo"
    assert screen.focus_previous(".b").id == "baz"
