"""Tests for the TaskEditorApp class.

This module provides comprehensive tests for the TaskEditorApp class, which is a text-based
user interface for editing tasks and their steps. The tests cover all methods, edge cases,
and user interactions.
"""

import asyncio
import contextlib
import sys
import types
from typing import Any, TypeVar
from unittest.mock import AsyncMock, Mock, patch

import pytest

# Import the generator module to check UI availability
import arklex.orchestrator.generator.generator as generator_mod


# Check if UI is available
def ui_available() -> bool:
    """Check if UI components are available."""
    return getattr(generator_mod, "_UI_AVAILABLE", True)


# Skip UI-dependent tests if UI is not available
def skip_if_ui_not_available() -> None:
    """Skip test if UI is not available."""
    if not ui_available():
        pytest.skip("UI not available, skipping UI-dependent test")


# --- FAKE TEXTUAL CLASSES AND MODULES (must be set before any code import) ---
class FakeTreeNode:
    def __init__(self, *args: object, **kwargs: object) -> None:
        self.children = []
        self.label = None
        self.parent = None

    def expand(self) -> None:
        pass

    def add(self, label: str) -> "FakeTreeNode":
        child = FakeTreeNode()
        child.label = label
        child.parent = self
        self.children.append(child)
        return child

    def add_leaf(self, label: str) -> "FakeTreeNode":
        return self.add(label)

    def remove(self) -> None:
        if self.parent:
            self.parent.children.remove(self)

    def set_label(self, label: str) -> None:
        self.label = label

    def focus(self) -> None:
        pass


class FakeButton:
    class Pressed:
        pass


class FakeInput:
    pass


class FakeStatic:
    pass


class FakeLabel:
    def __init__(self, *args: object, **kwargs: object) -> None:
        # Store the first argument as plain if provided
        self.plain = args[0] if args else ""


class FakeTree:
    class NodeSelected:
        pass

    def __init__(self, *args: object, **kwargs: object) -> None:
        # Ensure root is always a FakeTreeNode instance, not None
        self.root = FakeTreeNode()
        self.children = []
        self.cursor_node = None
        # Set the root's label if provided
        if args:
            self.root.label = args[0]

    def focus(self) -> None:
        pass


class FakeHorizontal:
    pass


class FakeVertical:
    pass


class FakeApp:
    def __init__(self, *args: object, **kwargs: object) -> None:
        pass

    def run(self) -> None:
        pass


class FakeComposeResult:
    pass


# Create a proper type variable for ReturnType
FakeReturnType = TypeVar("FakeReturnType")

# textual.app
fake_textual_app = types.ModuleType("textual.app")
fake_textual_app.App = FakeApp
fake_textual_app.ComposeResult = FakeComposeResult
fake_textual_app.ReturnType = FakeReturnType
sys.modules["textual.app"] = fake_textual_app
# textual.widgets
fake_textual_widgets = types.ModuleType("textual.widgets")
fake_textual_widgets.Button = FakeButton
fake_textual_widgets.Input = FakeInput
fake_textual_widgets.Static = FakeStatic
fake_textual_widgets.Label = FakeLabel
fake_textual_widgets.Tree = FakeTree
sys.modules["textual.widgets"] = fake_textual_widgets
# textual.widgets.tree
fake_textual_widgets_tree = types.ModuleType("textual.widgets.tree")
fake_textual_widgets_tree.TreeNode = FakeTreeNode
sys.modules["textual.widgets.tree"] = fake_textual_widgets_tree
# textual.containers
fake_textual_containers = types.ModuleType("textual.containers")
fake_textual_containers.Horizontal = FakeHorizontal
fake_textual_containers.Vertical = FakeVertical
sys.modules["textual.containers"] = fake_textual_containers
# textual.screen
fake_textual_screen = types.ModuleType("textual.screen")
fake_textual_screen.Screen = FakeApp
sys.modules["textual.screen"] = fake_textual_screen

# --- END FAKE TEXTUAL SETUP ---

# Import TaskEditorApp only if UI is available
if ui_available():
    from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp
else:
    # Create a dummy class for when UI is not available
    class TaskEditorApp:
        def __init__(self, *args: object, **kwargs: object) -> None:
            pytest.skip("UI not available")


# --- Mock fixtures for UI components ---


@pytest.fixture
def mock_tree(monkeypatch: pytest.MonkeyPatch) -> Mock:
    """Mock the Tree widget."""
    mock_tree = Mock()
    mock_tree_instance = Mock()
    mock_tree_instance.root = Mock()
    mock_tree_instance.root.expand = Mock()
    mock_tree_instance.root.add = Mock()
    mock_tree.return_value = mock_tree_instance
    return mock_tree


@pytest.fixture
def mock_label(monkeypatch: pytest.MonkeyPatch) -> Mock:
    """Mock the Label widget."""
    mock_label = Mock()
    mock_label_instance = Mock()
    mock_label.return_value = mock_label_instance
    return mock_label


@pytest.fixture
def mock_input_modal(monkeypatch: pytest.MonkeyPatch) -> Mock:
    """Mock the InputModal class."""
    mock_input_modal = Mock()
    mock_input_modal_instance = Mock()
    mock_input_modal_instance.result = "modal result"
    mock_input_modal.return_value = mock_input_modal_instance
    return mock_input_modal


@pytest.fixture
def mock_log_context(monkeypatch: pytest.MonkeyPatch) -> Mock:
    """Mock the log_context."""
    mock_log = Mock()
    mock_log.debug = Mock()
    return mock_log


@pytest.fixture
def sample_tasks() -> list[dict[str, Any]]:
    """Sample tasks for testing."""
    return [
        {"name": "Task 1", "steps": ["Step 1", "Step 2"]},
        {"name": "Task 2", "steps": ["Step 3"]},
    ]


@pytest.fixture
def complex_tasks() -> list[dict[str, Any]]:
    """Complex tasks with mixed step formats for testing."""
    return [
        {
            "name": "Complex Task 1",
            "steps": [
                {"name": "Sub-step 1", "description": "Detailed step"},
                "Simple step 2",
                {"name": "Sub-step 3", "requirements": ["req1", "req2"]},
            ],
        },
        {
            "name": "Complex Task 2",
            "steps": [
                "Simple step 1",
                {"name": "Sub-step 2", "timeout": 30},
            ],
        },
    ]


@pytest.fixture
def task_editor_app(sample_tasks: list[dict[str, Any]]) -> TaskEditorApp:
    """Create a TaskEditorApp instance for testing."""
    return TaskEditorApp(sample_tasks)


class TestTaskEditorAppInitialization:
    """Test TaskEditorApp initialization and basic functionality."""

    def test_import_task_editor_module(self) -> None:
        """Test that the task_editor module can be imported."""
        from arklex.orchestrator.generator.ui import task_editor

        assert task_editor is not None

    def test_task_editor_instantiation_with_valid_tasks(self) -> None:
        """Test TaskEditorApp instantiation with valid tasks."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        tasks = [{"name": "Test Task", "steps": ["Step 1", "Step 2"]}]
        app = TaskEditorApp(tasks)
        assert app.tasks == tasks
        assert app.task_tree is None

    def test_task_editor_instantiation_with_empty_tasks(self) -> None:
        """Test TaskEditorApp instantiation with empty tasks."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])

        # Call compose and convert to list - should work with fake textual classes
        result = list(app.compose())

        # Verify that compose returns a result
        assert result is not None
        assert len(result) >= 1

    def test_task_editor_instantiation_with_none_tasks(self) -> None:
        """Test TaskEditorApp instantiation with None tasks."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp(None)
        assert app.tasks is None
        assert app.task_tree is None

    def test_task_editor_instantiation_with_complex_tasks(self) -> None:
        """Test TaskEditorApp instantiation with complex task structures."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        complex_tasks = [
            {
                "name": "Complex Task",
                "steps": [
                    {"description": "Step with dict"},
                    "Step as string",
                    {"other_field": "Not description"},
                ],
            }
        ]
        app = TaskEditorApp(complex_tasks)
        assert app.tasks == complex_tasks
        assert app.task_tree is None

    def test_task_editor_instantiation_with_invalid_task_structure(self) -> None:
        """Test TaskEditorApp instantiation with invalid task structure."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        invalid_tasks = [{"invalid_key": "value"}]  # Missing 'name' and 'steps'
        app = TaskEditorApp(invalid_tasks)
        assert app.tasks == invalid_tasks
        assert app.task_tree is None

    def test_task_editor_methods_existence(self) -> None:
        """Test that TaskEditorApp has required methods."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])
        required_methods = [
            "compose",
            "on_mount",
            "on_tree_node_selected",
            "on_key",
            "action_add_node",
            "show_input_modal",
            "update_tasks",
            "run",
        ]
        for method_name in required_methods:
            assert hasattr(app, method_name), f"Method {method_name} not found"

    def test_task_editor_attributes_existence(self) -> None:
        """Test that TaskEditorApp has required attributes."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])
        assert hasattr(app, "tasks")
        assert hasattr(app, "task_tree")

    def test_task_editor_init_none_tasks(self) -> None:
        """Test TaskEditorApp initialization with None tasks."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp(None)
        assert app.tasks is None
        assert app.task_tree is None


class TestTaskEditorAppCompose:
    """Test TaskEditorApp compose method."""

    def test_compose_method(
        self, sample_tasks: list[dict[str, Any]], mock_tree: Mock, mock_label: Mock
    ) -> None:
        """Test compose method with valid tasks."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp(sample_tasks)

        # Call compose and convert to list - should work with fake textual classes
        result = list(app.compose())

        # Verify that compose returns a result
        assert result is not None
        assert len(result) >= 1

    def test_compose_with_empty_tasks(self, mock_tree: Mock, mock_label: Mock) -> None:
        """Test compose method with empty tasks."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])

        # Call compose and convert to list - should work with fake textual classes
        result = list(app.compose())

        # Verify that compose returns a result
        assert result is not None
        assert len(result) >= 1

    def test_compose_with_none_tasks(self, mock_tree: Mock, mock_label: Mock) -> None:
        """Test compose method with None tasks."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp(None)

        # Call compose and convert to list - should work with fake textual classes
        result = list(app.compose())

        # Verify that compose returns a result
        assert result is not None
        assert len(result) >= 1

    def test_compose_with_complex_tasks(
        self, complex_tasks: list[dict[str, Any]], mock_tree: Mock, mock_label: Mock
    ) -> None:
        """Test compose method with complex tasks."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp(complex_tasks)

        # Call compose and convert to list - should work with fake textual classes
        result = list(app.compose())

        # Verify that compose returns a result
        assert result is not None
        assert len(result) >= 1

    def test_task_editor_compose_with_none_tasks(self) -> None:
        """Test compose method with None tasks."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp(None)

        # Call compose and convert to list - should work with fake textual classes
        result = list(app.compose())

        # Verify that compose returns a result
        assert result is not None
        assert len(result) >= 1

    def test_compose_with_none_root(self) -> None:
        """Test compose method when no tasks are added (simulating root=None scenario)."""
        app = TaskEditorApp([])  # Empty tasks list

        # Call compose and check yields
        compose_result = list(app.compose())
        assert len(compose_result) == 2
        # The first yield should be a Tree instance
        assert type(compose_result[0]).__name__ in ("Tree", "FakeTree")
        # The second yield is a Label instance (by class name)
        assert type(compose_result[1]).__name__ in ("Label", "FakeLabel")

    def test_compose_with_none_root_and_tasks(self) -> None:
        """Test compose method with tasks but no steps (simulating root=None scenario)."""
        tasks = [{"name": "Task 1"}]  # Task without steps
        app = TaskEditorApp(tasks)

        compose_result = list(app.compose())
        assert len(compose_result) == 2
        # The first yield should be a Tree instance
        assert type(compose_result[0]).__name__ in ("Tree", "FakeTree")
        # The second yield is a Label instance (by class name)
        assert type(compose_result[1]).__name__ in ("Label", "FakeLabel")


class TestTaskEditorAppEventHandling:
    """Test TaskEditorApp event handling methods."""

    def test_on_mount(self, task_editor_app: TaskEditorApp) -> None:
        """Test on_mount method."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.focus = Mock()

        task_editor_app.on_mount()

        # Verify focus was called
        task_editor_app.task_tree.focus.assert_called_once()

    async def test_on_tree_node_selected(
        self, task_editor_app: TaskEditorApp, mock_input_modal: Mock
    ) -> None:
        """Test on_tree_node_selected method."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.cursor_node = Mock()

        # Create a mock event
        mock_event = Mock()
        mock_event.node = Mock()
        mock_event.node.label = "Test Node"

        # Mock the push_screen method
        task_editor_app.push_screen = Mock()

        # Ensure InputModal exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "InputModal"):
            ui_module.InputModal = mock_input_modal

        # Mock the InputModal class
        with patch("arklex.orchestrator.generator.ui.InputModal", mock_input_modal):
            await task_editor_app.on_tree_node_selected(mock_event)

            # Verify push_screen was called
            task_editor_app.push_screen.assert_called_once()

    async def test_on_tree_node_selected_with_none_label(
        self, task_editor_app: TaskEditorApp, mock_input_modal: Mock
    ) -> None:
        """Test on_tree_node_selected method with None label."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.cursor_node = Mock()

        # Create a mock event
        mock_event = Mock()
        mock_event.node = Mock()
        mock_event.node.label = None

        # Mock the push_screen method
        task_editor_app.push_screen = Mock()

        # Ensure InputModal exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "InputModal"):
            ui_module.InputModal = mock_input_modal

        # Mock the InputModal class
        with patch("arklex.orchestrator.generator.ui.InputModal", mock_input_modal):
            await task_editor_app.on_tree_node_selected(mock_event)

            # Verify push_screen was called
            task_editor_app.push_screen.assert_called_once()


class TestTaskEditorAppKeyboardHandling:
    """Test TaskEditorApp keyboard handling methods."""

    async def test_on_key_add_node(self, task_editor_app: TaskEditorApp) -> None:
        """Test on_key method for add node action."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.cursor_node = Mock()
        task_editor_app.task_tree.cursor_node.parent = Mock()

        # Mock the action_add_node method
        task_editor_app.action_add_node = AsyncMock()

        # Create a mock event
        mock_event = Mock()
        mock_event.key = "a"

        await task_editor_app.on_key(mock_event)

        # Verify action_add_node was called
        task_editor_app.action_add_node.assert_called_once()

    async def test_on_key_delete_node(self, task_editor_app: TaskEditorApp) -> None:
        """Test on_key method for delete node action."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.cursor_node = Mock()
        task_editor_app.task_tree.cursor_node.remove = Mock()
        task_editor_app.task_tree.cursor_node.parent = Mock()

        # Mock the root with proper children list
        task_editor_app.task_tree.root = Mock()
        task_editor_app.task_tree.root.children = []

        # Create a mock event
        mock_event = Mock()
        mock_event.key = "d"

        await task_editor_app.on_key(mock_event)

    async def test_on_key_save_exit(self, task_editor_app: TaskEditorApp) -> None:
        """Test on_key method for save and exit action."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.cursor_node = Mock()

        # Mock the exit method
        task_editor_app.exit = Mock()

        # Mock the update_tasks method to prevent issues
        task_editor_app.update_tasks = AsyncMock()

        # Create a mock event
        mock_event = Mock()
        mock_event.key = "s"

        await task_editor_app.on_key(mock_event)

        # Verify update_tasks was called
        task_editor_app.update_tasks.assert_called_once()
        # Verify exit was called
        task_editor_app.exit.assert_called_once_with(task_editor_app.tasks)

    async def test_on_key_other_key(self, task_editor_app: TaskEditorApp) -> None:
        """Test on_key method for other keys."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.cursor_node = Mock()

        # Create a mock event
        mock_event = Mock()
        mock_event.key = "x"

        # Should not raise any exception
        await task_editor_app.on_key(mock_event)

    async def test_on_key_no_cursor_node(self, task_editor_app: TaskEditorApp) -> None:
        """Test on_key method when no cursor node is selected."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.cursor_node = None

        # Create a mock event
        mock_event = Mock()
        mock_event.key = "a"

        # Should not raise any exception
        await task_editor_app.on_key(mock_event)

    async def test_on_key_no_parent(self, task_editor_app: TaskEditorApp) -> None:
        """Test on_key method when cursor node has no parent."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.cursor_node = Mock()
        task_editor_app.task_tree.cursor_node.parent = None

        # Mock the push_screen method to avoid Textual issues
        task_editor_app.push_screen = Mock()

        # Create a mock InputModal
        mock_input_modal = Mock()

        # Ensure InputModal exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "InputModal"):
            ui_module.InputModal = mock_input_modal

        # Mock the InputModal class to prevent stylesheet issues
        with patch("arklex.orchestrator.generator.ui.InputModal") as mock_input_modal:
            mock_modal_instance = Mock()
            mock_modal_instance.result = ""
            mock_input_modal.return_value = mock_modal_instance

            # Create a mock event
            mock_event = Mock()
            mock_event.key = "a"

            # Should not raise any exception
            await task_editor_app.on_key(mock_event)

            # Verify push_screen was called
            task_editor_app.push_screen.assert_called_once()


class TestTaskEditorAppNodeManagement:
    """Test TaskEditorApp node management methods."""

    async def test_action_add_node_step_node(
        self, task_editor_app: TaskEditorApp, mock_input_modal: Mock
    ) -> None:
        """Test action_add_node method with step node."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.cursor_node = Mock()
        task_editor_app.task_tree.cursor_node.parent = Mock()
        task_editor_app.task_tree.cursor_node.parent.parent = Mock()

        # Mock the push_screen method
        task_editor_app.push_screen = Mock()

        # Ensure InputModal exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "InputModal"):
            ui_module.InputModal = mock_input_modal

        # Mock the InputModal class
        with patch("arklex.orchestrator.generator.ui.InputModal", mock_input_modal):
            await task_editor_app.action_add_node(task_editor_app.task_tree.cursor_node)

            # Verify push_screen was called
            task_editor_app.push_screen.assert_called_once()

    async def test_action_add_node_task_node_expanded(
        self, task_editor_app: TaskEditorApp, mock_input_modal: Mock
    ) -> None:
        """Test action_add_node method with expanded task node."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.cursor_node = Mock()
        task_editor_app.task_tree.cursor_node.parent = Mock()  # Add parent
        task_editor_app.task_tree.cursor_node.parent.parent = (
            None  # Set parent.parent to None
        )
        task_editor_app.task_tree.cursor_node.is_expanded = True

        # Mock the push_screen method
        task_editor_app.push_screen = Mock()

        # Ensure InputModal exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "InputModal"):
            ui_module.InputModal = mock_input_modal

        # Mock the InputModal class
        with patch("arklex.orchestrator.generator.ui.InputModal", mock_input_modal):
            await task_editor_app.action_add_node(task_editor_app.task_tree.cursor_node)

            # Verify push_screen was called
            task_editor_app.push_screen.assert_called_once()

    async def test_action_add_node_task_node_not_expanded(
        self, task_editor_app: TaskEditorApp, mock_input_modal: Mock
    ) -> None:
        """Test action_add_node method with not expanded task node."""
        skip_if_ui_not_available()

        # Mock the task_tree
        task_editor_app.task_tree = Mock()
        task_editor_app.task_tree.cursor_node = Mock()
        task_editor_app.task_tree.cursor_node.parent = Mock()  # Add parent
        task_editor_app.task_tree.cursor_node.parent.parent = (
            None  # Set parent.parent to None
        )
        task_editor_app.task_tree.cursor_node.is_expanded = False

        # Mock the push_screen method
        task_editor_app.push_screen = Mock()

        # Ensure InputModal exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "InputModal"):
            ui_module.InputModal = mock_input_modal

        # Mock the InputModal class
        with patch("arklex.orchestrator.generator.ui.InputModal", mock_input_modal):
            await task_editor_app.action_add_node(task_editor_app.task_tree.cursor_node)

            # Verify push_screen was called
            task_editor_app.push_screen.assert_called_once()

    def test_show_input_modal(
        self, task_editor_app: TaskEditorApp, mock_input_modal: Mock
    ) -> None:
        """Test show_input_modal method."""
        skip_if_ui_not_available()

        # Mock the push_screen method
        task_editor_app.push_screen = Mock()

        # Create a fresh mock for this test with correct result
        fresh_mock_modal = Mock()
        fresh_mock_modal_instance = Mock()
        fresh_mock_modal_instance.result = "default value"
        fresh_mock_modal.return_value = fresh_mock_modal_instance

        # Ensure InputModal exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "InputModal"):
            ui_module.InputModal = fresh_mock_modal

        # Mock the InputModal class
        with patch("arklex.orchestrator.generator.ui.InputModal", fresh_mock_modal):
            result = task_editor_app.show_input_modal("Test Title", "default value")

            # Verify push_screen was called
            task_editor_app.push_screen.assert_called_once()
            # Verify the result is returned correctly
            assert result == "default value"

    def test_show_input_modal_with_empty_default(
        self, task_editor_app: TaskEditorApp, mock_input_modal: Mock
    ) -> None:
        """Test show_input_modal method with empty default value."""
        skip_if_ui_not_available()

        # Mock the push_screen method
        task_editor_app.push_screen = Mock()

        # Create a fresh mock for this test
        fresh_mock_modal = Mock()
        fresh_mock_modal_instance = Mock()
        fresh_mock_modal_instance.result = ""
        fresh_mock_modal.return_value = fresh_mock_modal_instance

        # Ensure InputModal exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "InputModal"):
            ui_module.InputModal = fresh_mock_modal

        # Mock the InputModal class
        with patch("arklex.orchestrator.generator.ui.InputModal", fresh_mock_modal):
            result = task_editor_app.show_input_modal("Test Title")

            # Verify push_screen was called
            task_editor_app.push_screen.assert_called_once()
            # Verify the result is returned correctly
            assert result == ""

    def test_show_input_modal_various(self, task_editor_app: TaskEditorApp) -> None:
        """Test show_input_modal method with various inputs."""
        skip_if_ui_not_available()

        # Mock the push_screen method
        task_editor_app.push_screen = Mock()

        # Create a fresh mock for this test
        fresh_mock_modal = Mock()
        fresh_mock_modal_instance = Mock()
        fresh_mock_modal_instance.result = "default value"
        fresh_mock_modal.return_value = fresh_mock_modal_instance

        # Ensure InputModal exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "InputModal"):
            ui_module.InputModal = fresh_mock_modal

        # Mock the InputModal class
        with patch("arklex.orchestrator.generator.ui.InputModal", fresh_mock_modal):
            result = task_editor_app.show_input_modal("Test Title", "default value")
            assert result == "default value"

    def test_show_input_modal_returns_result(
        self, task_editor_app: TaskEditorApp
    ) -> None:
        """Test show_input_modal method returns correct result."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        # Create a dummy modal class for testing
        class DummyModal:
            def __init__(self, *a: object, **k: object) -> None:
                pass

        app = TaskEditorApp([])
        app.push_screen = Mock()

        # Ensure InputModal exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "InputModal"):
            ui_module.InputModal = DummyModal

        # Mock the InputModal class and also patch the module attribute
        with (
            patch("arklex.orchestrator.generator.ui.InputModal", DummyModal),
            patch("sys.modules"),
        ):
            result = app.show_input_modal("Test Title", "default value")
            assert result == "default value"


class TestTaskEditorAppDataManagement:
    """Test TaskEditorApp data management methods."""

    async def test_update_tasks(self, mock_log_context: Mock) -> None:
        """Test update_tasks method."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])

        # Mock the task_tree
        app.task_tree = Mock()
        app.task_tree.root = Mock()
        app.task_tree.root.children = []

        # Ensure task_editor exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "task_editor"):
            ui_module.task_editor = Mock()

        # Mock the log_context at the module level
        with (
            patch(
                "arklex.orchestrator.generator.ui.task_editor.log_context",
                mock_log_context,
            ),
            contextlib.suppress(AttributeError),
        ):
            await app.update_tasks()

    async def test_update_tasks_empty_tree(self, mock_log_context: Mock) -> None:
        """Test update_tasks method with empty tree."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])

        # Mock the task_tree with empty root
        app.task_tree = Mock()
        app.task_tree.root = Mock()
        app.task_tree.root.children = []

        # Ensure task_editor exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "task_editor"):
            ui_module.task_editor = Mock()

        # Mock the log_context at the module level
        with (
            patch(
                "arklex.orchestrator.generator.ui.task_editor.log_context",
                mock_log_context,
            ),
            contextlib.suppress(AttributeError),
        ):
            await app.update_tasks()

            # The method should complete without raising an exception
            assert app.tasks == []

    async def test_update_tasks_with_none_tree(self, mock_log_context: Mock) -> None:
        """Test update_tasks method with None tree."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])
        app.task_tree = None

        # Should not raise any exception
        await app.update_tasks()

    async def test_update_tasks_with_invalid_node_structure(
        self, mock_log_context: Mock
    ) -> None:
        """Test update_tasks method with invalid node structure."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])

        # Mock the task_tree with invalid structure
        app.task_tree = Mock()
        app.task_tree.root = Mock()
        app.task_tree.root.children = [None]  # Invalid child

        # Ensure task_editor exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "task_editor"):
            ui_module.task_editor = Mock()

        # Mock the log_context at the module level
        with (
            patch(
                "arklex.orchestrator.generator.ui.task_editor.log_context",
                mock_log_context,
            ),
            contextlib.suppress(AttributeError),
        ):
            await app.update_tasks()

    async def test_update_tasks_with_missing_step_data(
        self, mock_log_context: Mock
    ) -> None:
        """Test update_tasks method with missing step data."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])

        # Mock the task_tree
        app.task_tree = Mock()
        app.task_tree.root = Mock()

        # Create a mock node with missing step data
        mock_node = Mock()
        mock_node.label = Mock()
        mock_node.label.plain = "Test Node"
        mock_node.children = []

        app.task_tree.root.children = [mock_node]

        # Ensure task_editor exists in the UI module
        import arklex.orchestrator.generator.ui as ui_module

        if not hasattr(ui_module, "task_editor"):
            ui_module.task_editor = Mock()

        # Mock the log_context at the module level
        with (
            patch(
                "arklex.orchestrator.generator.ui.task_editor.log_context",
                mock_log_context,
            ),
            contextlib.suppress(AttributeError),
        ):
            await app.update_tasks()

            # The method should complete without raising an exception
            assert len(app.tasks) == 1
            assert app.tasks[0]["name"] == "Test Node"
            # The steps key is only added if there are steps, so it shouldn't be present here

    def test_run_returns_tasks(self, task_editor_app: TaskEditorApp) -> None:
        """Test run method returns tasks."""
        skip_if_ui_not_available()

        # Just test that the method returns the tasks attribute
        # We'll skip the actual super().run() call to avoid Textual issues
        assert task_editor_app.run() == task_editor_app.tasks

    def test_task_editor_app_init_with_none(self) -> None:
        """Test TaskEditorApp initialization with None."""
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp(None)
        assert app.tasks is None
        assert app.task_tree is None


class TestTaskEditorFinalCoverage:
    """Test TaskEditorApp for final coverage."""

    def test_task_editor_init_with_none_tasks(self) -> None:
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp(None)
        assert app.tasks is None

    def test_task_editor_compose_with_none_tasks(self) -> None:
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp(None)

        # Call compose and convert to list - should work with fake textual classes
        result = list(app.compose())

        # Verify that compose returns a result
        assert result is not None
        assert len(result) >= 1

    def test_task_editor_update_tasks_with_none_tree(self) -> None:
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])
        app.task_tree = None

        # Should not raise any exception
        asyncio.run(app.update_tasks())

    def test_task_editor_update_tasks_with_none_root(self) -> None:
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])
        app.task_tree = Mock()
        app.task_tree.root = None

        # Should not raise any exception
        asyncio.run(app.update_tasks())

    def test_task_editor_update_tasks_with_getattr_none_root(self) -> None:
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])
        app.task_tree = Mock()
        app.task_tree.root = None

        # Should not raise any exception
        asyncio.run(app.update_tasks())

    async def test_task_editor_update_tasks_with_complex_tree_structure(self) -> None:
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])

        # Create fake node for testing
        class FakeNode:
            def __init__(self, label: object, children: list | None = None) -> None:
                self.label = label
                self.children = children or []

        # Mock the task_tree
        app.task_tree = Mock()
        app.task_tree.root = Mock()
        app.task_tree.root.children = [
            FakeNode("Task 1", [FakeNode("Step 1"), FakeNode("Step 2")]),
            FakeNode("Task 2", [FakeNode("Step 3")]),
        ]

        await app.update_tasks()

        # Verify tasks were updated correctly
        assert len(app.tasks) == 2
        assert app.tasks[0]["name"] == "Task 1"
        assert app.tasks[0]["steps"] == ["Step 1", "Step 2"]
        assert app.tasks[1]["name"] == "Task 2"
        assert app.tasks[1]["steps"] == ["Step 3"]

    def test_task_editor_run_method(self) -> None:
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])
        # Just test that the method returns the tasks attribute
        assert app.run() == []

    def test_task_editor_show_input_modal_with_callback(self) -> None:
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])

        # Mock the push_screen method
        app.push_screen = Mock()

        # Create a fresh mock for this test
        fresh_mock_modal = Mock()
        fresh_mock_modal_instance = Mock()
        fresh_mock_modal_instance.result = "default value"
        fresh_mock_modal.return_value = fresh_mock_modal_instance

        # Mock the InputModal class
        with patch("arklex.orchestrator.generator.ui.InputModal", fresh_mock_modal):
            result = app.show_input_modal("Test Title", "default value")

            # Verify push_screen was called
            app.push_screen.assert_called_once()
            # Verify the result is returned correctly
            assert result == "default value"

    def test_task_editor_show_input_modal_without_default(self) -> None:
        skip_if_ui_not_available()
        from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

        app = TaskEditorApp([])

        # Mock the push_screen method
        app.push_screen = Mock()

        # Create a fresh mock for this test
        fresh_mock_modal = Mock()
        fresh_mock_modal_instance = Mock()
        fresh_mock_modal_instance.result = ""
        fresh_mock_modal.return_value = fresh_mock_modal_instance

        # Mock the InputModal class
        with patch("arklex.orchestrator.generator.ui.InputModal", fresh_mock_modal):
            result = app.show_input_modal("Test Title")

            # Verify push_screen was called
            app.push_screen.assert_called_once()
            # Verify the result is returned correctly
            assert result == ""


def test_update_tasks_handles_no_task_tree_or_root() -> None:
    skip_if_ui_not_available()
    from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

    app = TaskEditorApp([])
    app.task_tree = None
    # Should return early (line 139)
    assert asyncio.run(app.update_tasks()) is None
    app.task_tree = Mock()
    app.task_tree.root = None
    # Should return early (line 140)
    assert asyncio.run(app.update_tasks()) is None


def test_update_tasks_handles_various_label_types() -> None:
    skip_if_ui_not_available()
    from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

    app = TaskEditorApp([])

    class FakeNode:
        def __init__(self, label: object, children: list | None = None) -> None:
            self.label = label
            self.children = children or []

    # Step label with and without 'plain'
    app.task_tree = Mock()
    app.task_tree.root = Mock()
    app.task_tree.root.children = [
        FakeNode("Task1", [FakeNode("Step1"), FakeNode("Step2")]),
        FakeNode("Task2", [FakeNode("Step3")]),
    ]
    asyncio.run(app.update_tasks())  # lines 145, 200-201
    assert app.tasks[0]["name"] == "Task1"
    assert app.tasks[0]["steps"] == ["Step1", "Step2"]
    assert app.tasks[1]["name"] == "Task2"
    assert app.tasks[1]["steps"] == ["Step3"]


def test_push_screen_calls_super() -> None:
    skip_if_ui_not_available()
    from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

    called = {}

    class MyTaskEditorApp(TaskEditorApp):
        def __init__(self) -> None:
            super().__init__([])

        def push_screen(self, screen: object) -> object:
            called["super"] = screen
            return super().push_screen(screen)

    app = MyTaskEditorApp()

    # Check if the base class has push_screen method before patching
    base_class = TaskEditorApp.__bases__[0]
    if hasattr(base_class, "push_screen"):
        # Patch the superclass push_screen to avoid event loop error
        import unittest.mock

        with unittest.mock.patch.object(
            base_class, "push_screen", return_value=[1, 2, 3]
        ):
            result = app.push_screen(True)
            assert called["super"] is True
            assert result == [1, 2, 3]
    else:
        # If base class doesn't have push_screen, just test that our method works
        result = app.push_screen(True)
        assert called["super"] is True
        assert result == [1, 2, 3]


def test_show_input_modal_returns_default(monkeypatch: pytest.MonkeyPatch) -> None:
    skip_if_ui_not_available()
    from arklex.orchestrator.generator.ui.task_editor import TaskEditorApp

    app = TaskEditorApp([])

    class DummyModal:
        def __init__(self, *a: object, **k: object) -> None:
            pass

    monkeypatch.setattr("arklex.orchestrator.generator.ui.InputModal", DummyModal)
    app.push_screen = Mock()
    result = app.show_input_modal("title", "default")  # line 264
    assert result == "default"


def test_run_calls_super_and_returns_tasks() -> None:
    """Test that run method returns self.tasks."""
    app = TaskEditorApp([{"name": "T"}])
    result = app.run()
    assert result == [{"name": "T"}]


# Additional test cases to cover missing lines in task_editor.py.
class TestTaskEditorMissingCoverage:
    """Test cases to cover missing lines in task_editor.py."""

    def test_compose_with_none_root(self) -> None:
        """Test compose method when no tasks are added (simulating root=None scenario)."""
        app = TaskEditorApp([])  # Empty tasks list

        # Call compose and check yields
        compose_result = list(app.compose())
        assert len(compose_result) == 2
        # The first yield should be a Tree instance
        assert type(compose_result[0]).__name__ in ("Tree", "FakeTree")
        # The second yield is a Label instance (by class name)
        assert type(compose_result[1]).__name__ in ("Label", "FakeLabel")

    def test_compose_with_none_root_and_tasks(self) -> None:
        """Test compose method with tasks but no steps (simulating root=None scenario)."""
        tasks = [{"name": "Task 1"}]  # Task without steps
        app = TaskEditorApp(tasks)

        compose_result = list(app.compose())
        assert len(compose_result) == 2
        # The first yield should be a Tree instance
        assert type(compose_result[0]).__name__ in ("Tree", "FakeTree")
        # The second yield is a Label instance (by class name)
        assert type(compose_result[1]).__name__ in ("Label", "FakeLabel")

    def test_on_tree_node_selected_without_plain_attribute(self) -> None:
        """Test on_tree_node_selected when label doesn't have plain attribute."""
        app = TaskEditorApp([])

        # Create a mock node with a label that doesn't have 'plain' attribute
        mock_node = Mock()
        mock_node.label = "Test Label"  # String label without 'plain' attribute

        # Create a mock event
        mock_event = Mock()
        mock_event.node = mock_node

        with patch.object(app, "show_input_modal") as mock_show_modal:
            asyncio.run(app.on_tree_node_selected(mock_event))

            # Should call show_input_modal with the string representation
            mock_show_modal.assert_called_once()
            args, kwargs = mock_show_modal.call_args
            assert args[0] == "Edit node"
            assert args[1] == "Test Label"
            assert args[2] == mock_node
            assert callable(args[3])  # The callback function

    def test_push_screen_without_super_method(self) -> None:
        """Test push_screen when super() doesn't have push_screen method."""

        class DummyTaskEditorApp(TaskEditorApp):
            def __init__(self) -> None:
                super().__init__([])

        app = DummyTaskEditorApp()
        # Mock hasattr to return False for push_screen
        with patch("builtins.hasattr", return_value=False):
            mock_screen = Mock()
            result = app.push_screen(mock_screen)
            # Only assert the unique return value for the else branch
            assert result == [1, 2, 3]

    def test_push_screen_else_branch(self) -> None:
        """Test push_screen else branch when super() doesn't have push_screen."""

        class DummyTaskEditorApp(TaskEditorApp):
            def __init__(self) -> None:
                super().__init__([])

        app = DummyTaskEditorApp()
        # Mock hasattr to return False for push_screen
        with patch("builtins.hasattr", return_value=False):
            mock_screen = Mock()
            result = app.push_screen(mock_screen)
            # Only assert the unique return value for the else branch
            assert result == [1, 2, 3]

    def test_show_input_modal_return_statement(self) -> None:
        """Test that show_input_modal returns the default value and calls push_screen."""
        app = TaskEditorApp([])
        with patch.object(app, "push_screen") as mock_push_screen:
            result = app.show_input_modal("Test Title", "Default Value")
            assert result == "Default Value"
            mock_push_screen.assert_called_once()

    def test_on_tree_node_selected_calls_show_input_modal(self) -> None:
        """Test that on_tree_node_selected calls show_input_modal."""
        app = TaskEditorApp([])

        # Create a mock node with a label that has 'plain' attribute
        mock_node = Mock()
        mock_label = Mock()
        mock_label.plain = "Test Label"
        mock_node.label = mock_label

        # Create a mock event
        mock_event = Mock()
        mock_event.node = mock_node

        with patch.object(app, "show_input_modal") as mock_show_modal:
            asyncio.run(app.on_tree_node_selected(mock_event))

            # Should call show_input_modal with the plain text
            mock_show_modal.assert_called_once()
            args, kwargs = mock_show_modal.call_args
            assert args[0] == "Edit node"
            assert args[1] == "Test Label"
            assert args[2] == mock_node
            assert callable(args[3])  # The callback function

    def test_show_input_modal_with_all_parameters(self) -> None:
        """Test show_input_modal with all parameters provided and calls push_screen."""
        app = TaskEditorApp([])
        mock_node = Mock()
        mock_callback = Mock()
        with patch.object(app, "push_screen") as mock_push_screen:
            result = app.show_input_modal(
                "Test Title", "Default Value", mock_node, mock_callback
            )
            assert result == "Default Value"
            mock_push_screen.assert_called_once()
