"""Tests for pyos/ContextUtils.py — pure dict-manipulation functions."""

import pytest

from pyos.ContextUtils import (
    get_text,
    get_cursor_index,
    get_x,
    get_text_length,
    scroll_up,
    scroll_down,
    scroll_to_top,
    scroll_to_bottom,
    scroll_left,
    scroll_right,
    move_menu_left,
    move_menu_right,
    get_num_lines,
    get_selected_item_from_index_map,
    get_items_len,
    is_hidden,
)


# ===========================================================================
# Getter defaults
# ===========================================================================

class TestGetters:
    def test_get_text_default(self):
        assert get_text({}) == ""

    def test_get_text_present(self):
        assert get_text({"text": "hello"}) == "hello"

    def test_get_cursor_index_default(self):
        assert get_cursor_index({}) == 0

    def test_get_cursor_index_present(self):
        assert get_cursor_index({"cursor_index": 5}) == 5

    def test_get_x_default(self):
        assert get_x({}) == 0

    def test_get_x_present(self):
        assert get_x({"x": 10}) == 10


# ===========================================================================
# get_text_length
# ===========================================================================

class TestGetTextLength:
    def test_returns_neg1_for_missing_text(self):
        assert get_text_length({}) == -1

    def test_returns_0_for_empty_string(self):
        assert get_text_length({"text": ""}) == 0

    def test_returns_length(self):
        assert get_text_length({"text": "abc"}) == 3


# ===========================================================================
# scroll_up / scroll_down
# ===========================================================================

class TestScrollUpDown:
    def test_scroll_up_normal(self):
        ctx = {"selected_index": 3}
        scroll_up(ctx)
        assert ctx["selected_index"] == 2

    def test_scroll_up_clamps_at_zero(self):
        ctx = {"selected_index": 0}
        scroll_up(ctx)
        assert ctx["selected_index"] == 0

    def test_scroll_up_from_default(self):
        ctx = {}
        scroll_up(ctx)
        assert ctx["selected_index"] == 0

    def test_scroll_down_normal(self):
        ctx = {"items": ["a", "b", "c"], "selected_index": 0}
        scroll_down(ctx)
        assert ctx["selected_index"] == 1

    def test_scroll_down_clamps_at_last(self):
        ctx = {"items": ["a", "b", "c"], "selected_index": 2}
        scroll_down(ctx)
        assert ctx["selected_index"] == 2

    def test_scroll_down_empty_items(self):
        """Empty items → get_num_lines returns 0 → min(0-1, 0+1) = -1."""
        ctx = {"items": []}
        scroll_down(ctx)
        assert ctx["selected_index"] == -1


# ===========================================================================
# scroll_to_top / scroll_to_bottom
# ===========================================================================

class TestScrollToTopBottom:
    def test_scroll_to_top(self):
        ctx = {"items": ["a", "b", "c"], "selected_index": 2}
        scroll_to_top(ctx)
        assert ctx["selected_index"] == 0

    def test_scroll_to_bottom(self):
        ctx = {"items": ["a", "b", "c"], "selected_index": 0}
        scroll_to_bottom(ctx)
        assert ctx["selected_index"] == 2

    def test_scroll_to_bottom_empty_items(self):
        """Empty items → index set to -1."""
        ctx = {"items": []}
        scroll_to_bottom(ctx)
        assert ctx["selected_index"] == -1


# ===========================================================================
# scroll_left / scroll_right (wrapping)
# ===========================================================================

class TestScrollLeftRight:
    def test_scroll_right_wraps(self):
        ctx = {"items": ["a", "b", "c"], "selected_index": 2}
        scroll_right(ctx)
        assert ctx["selected_index"] == 0

    def test_scroll_left_wraps(self):
        ctx = {"items": ["a", "b", "c"], "selected_index": 0}
        scroll_left(ctx)
        assert ctx["selected_index"] == 2

    def test_scroll_right_normal(self):
        ctx = {"items": ["a", "b", "c"], "selected_index": 0}
        scroll_right(ctx)
        assert ctx["selected_index"] == 1

    def test_scroll_left_empty_items_crash(self):
        """Empty items → ZeroDivisionError from modulo."""
        ctx = {"items": []}
        with pytest.raises(ZeroDivisionError):
            scroll_left(ctx)

    def test_scroll_right_empty_items_crash(self):
        ctx = {"items": []}
        with pytest.raises(ZeroDivisionError):
            scroll_right(ctx)

    def test_scroll_left_missing_items_crash(self):
        """Missing 'items' key → KeyError."""
        with pytest.raises(KeyError):
            scroll_left({})

    def test_scroll_right_missing_items_crash(self):
        with pytest.raises(KeyError):
            scroll_right({})


# ===========================================================================
# move_menu_left / move_menu_right
# ===========================================================================

class TestMoveMenu:
    def test_move_menu_right_wraps(self):
        ctx = {"items": ["a", "b", "c"], "selected_menu_index": 2}
        move_menu_right(ctx)
        assert ctx["selected_menu_index"] == 0

    def test_move_menu_left_wraps(self):
        ctx = {"items": ["a", "b", "c"], "selected_menu_index": 0}
        move_menu_left(ctx)
        assert ctx["selected_menu_index"] == 2

    def test_move_menu_right_normal(self):
        ctx = {"items": ["a", "b", "c"], "selected_menu_index": 0}
        move_menu_right(ctx)
        assert ctx["selected_menu_index"] == 1

    def test_move_menu_left_empty_crash(self):
        ctx = {"items": []}
        with pytest.raises(ZeroDivisionError):
            move_menu_left(ctx)

    def test_move_menu_right_missing_items_crash(self):
        with pytest.raises(KeyError):
            move_menu_right({})


# ===========================================================================
# get_num_lines
# ===========================================================================

class TestGetNumLines:
    def test_with_lines_key(self):
        assert get_num_lines({"lines": ["a", "b"]}) == 2

    def test_with_items_key(self):
        assert get_num_lines({"items": [1, 2, 3]}) == 3

    def test_with_neither(self):
        assert get_num_lines({}) == 0


# ===========================================================================
# get_selected_item_from_index_map
# ===========================================================================

class TestGetSelectedItemFromIndexMap:
    def test_normal(self):
        ctx = {"line_ids": ["x", "y", "z"], "selected_index": 1}
        assert get_selected_item_from_index_map(ctx) == "y"

    def test_missing_line_ids(self):
        assert get_selected_item_from_index_map({}) is None

    def test_out_of_bounds_index(self):
        ctx = {"line_ids": ["x"], "selected_index": 99}
        assert get_selected_item_from_index_map(ctx) is None

    def test_empty_line_ids(self):
        ctx = {"line_ids": [], "selected_index": 0}
        assert get_selected_item_from_index_map(ctx) is None

    def test_default_index_zero(self):
        ctx = {"line_ids": ["a", "b"]}
        assert get_selected_item_from_index_map(ctx) == "a"


# ===========================================================================
# get_items_len
# ===========================================================================

class TestGetItemsLen:
    def test_returns_length(self):
        assert get_items_len({"items": [1, 2, 3]}) == 3

    def test_returns_none_when_absent(self):
        """Type surprise: returns None instead of 0 or raising."""
        assert get_items_len({}) is None


# ===========================================================================
# is_hidden
# ===========================================================================

class TestIsHidden:
    def test_default_false(self):
        assert is_hidden({}) is False

    def test_explicit_true(self):
        assert is_hidden({"hidden": True}) is True

    def test_explicit_false(self):
        assert is_hidden({"hidden": False}) is False
