from typing import Literal
from textual import on
from textual.message import Message
from textual.app import ComposeResult
from textual.widgets import TextArea, Button
from textual.containers import Container, Vertical, Horizontal, VerticalScroll
from textual.reactive import var
from textual.widgets.text_area import TextAreaTheme
from rich.style import Style

from analyst_klondike.features.app.actions import ChangeThemeAction
from analyst_klondike.features.code.actions import UpdateCodeAction
from analyst_klondike.features.code.macro_commands import run_code_command
from analyst_klondike.features.code_import.analysis.code_analysis import create_quiz_from_code
from analyst_klondike.features.code_import.ui_actions.actions import ImportCodeForTaskFromSource
from analyst_klondike.features.current.actions import MakeTaskCurrentAction
from analyst_klondike.features.err_handling import catch_error
from analyst_klondike.features.helpers import for_actions
from analyst_klondike.features.teacher.clear_solutions import ClearSolutionsAction
from analyst_klondike.features.teacher.edit_task import (
    ToggleTaskEditMode,
    UpdateTaskPythonSourceCode
)
from analyst_klondike.state.app_dispatch import app_dispatch
from analyst_klondike.state.app_state import INITIAL_STATE, AppState, select
from analyst_klondike.features.current.selectors import select_current_task


class CodeEditor(Container):

    DEFAULT_CSS = """
        CodeEditor {
            border: solid $primary;

            #code-container {
                height: 1fr
            }

            TextArea {
                border: none;
                background: $background;
                

                &:focus-within {
                    border: none;
                }               
                
            }
            
            #button-container {
                height: auto;
                align-horizontal: right;
                border-top: solid $primary;
                padding: 0;

                .separator {
                    width: 1;
                    height: 0;
                }

                .hidden {
                    display: none;
                }
            }
        }
    """

    RUN_CODE_BUTTON_ID = "run-code-button"
    CONVERT_BUTTON_ID = "convert-to-testcase-button"
    THEME_NAME = "ak_theme"

    class CodeUpdated(Message):
        def __init__(self, code: str) -> None:
            super().__init__()
            self.code = code

    state: var[AppState] = var(INITIAL_STATE, init=False)

    def on_mount(self):
        self.border_title = "Редактор"
        code_editor = self.query_one("TextArea", TextArea)
        code_editor.indent_type = "tabs"
        # dark theme is default
        my_theme = self._create_theme("dark")
        code_editor.register_theme(my_theme)
        code_editor.theme = CodeEditor.THEME_NAME
        yield code_editor

    def compose(self) -> ComposeResult:
        with Vertical():
            with VerticalScroll(id="code-container"):
                yield TextArea.code_editor("",
                                           theme="css",
                                           language="python",
                                           id="code_editor")
            with Horizontal(id="button-container"):
                yield Button(
                    id=CodeEditor.RUN_CODE_BUTTON_ID,
                    label="Запустить (F5)",
                    variant='primary',
                    compact=True,
                    disabled=True
                )
                yield Container(classes="separator")
                yield Button(
                    id=CodeEditor.CONVERT_BUTTON_ID,
                    label="Код в тест-кейс",
                    variant='primary',
                    compact=True
                )

    def watch_state(self, state: AppState) -> None:
        self._update_run_button(state)
        self._update_code(state)
        self._update_code(state)
        self._update_theme(state)
        self._update_code_if_edit_mode(state)

        # display convert button depending on teacher's mode
        self._update_convert_button(state)

    @for_actions(ChangeThemeAction)
    def _update_theme(self, state: AppState) -> None:
        editor = self.query_one("TextArea", TextArea)
        theme = self._create_theme(
            "dark") if state.is_dark else self._create_theme("light")
        editor.register_theme(theme)
        editor.theme = CodeEditor.THEME_NAME

    @for_actions(ToggleTaskEditMode)
    def _update_code_if_edit_mode(self, _: AppState) -> None:
        task = select(select_current_task)
        editor = self.query_one("TextArea", TextArea)
        if task is None:
            return
        if task.is_edited:
            with editor.prevent(TextArea.Changed):
                editor.text = task.python_source_code
        else:
            with editor.prevent(TextArea.Changed):
                editor.text = task.code

    @for_actions(MakeTaskCurrentAction)
    def _update_run_button(self, _: AppState) -> None:
        task = select(select_current_task)
        run_code_button = self.query_one(
            f"#{CodeEditor.RUN_CODE_BUTTON_ID}")
        # disable when task is none
        run_code_button.disabled = task is None

    @for_actions(MakeTaskCurrentAction, ClearSolutionsAction)
    def _update_code(self, _: AppState) -> None:
        task = select(select_current_task)
        if task is None:
            return
        editor = self.query_one("TextArea", TextArea)
        with editor.prevent(TextArea.Changed):
            if task.is_edited:
                editor.text = task.python_source_code
            else:
                editor.text = task.code

    def _update_convert_button(self, state: AppState) -> None:
        convert_button = self.query_one(f"#{CodeEditor.CONVERT_BUTTON_ID}")
        task = select(select_current_task)
        if task is None or not state.is_teacher_mode:
            convert_button.add_class("hidden")
        else:
            convert_button.remove_class("hidden")
        if task is not None:
            # enable convert button only is task is edited
            convert_button.disabled = not task.is_edited

    @on(TextArea.Changed)
    def on_textarea_changed(self, event: TextArea.Changed) -> None:
        new_code = event.text_area.text
        curr_task = select(select_current_task)
        assert curr_task is not None
        if curr_task.is_edited:
            app_dispatch(UpdateTaskPythonSourceCode(new_code))
        else:
            app_dispatch(UpdateCodeAction(new_code))

    @on(Button.Pressed, f"#{RUN_CODE_BUTTON_ID}")
    @catch_error
    def run_code_button_click(self, _event: Button.Pressed) -> None:
        run_code_command()

    @on(Button.Pressed, f"#{CONVERT_BUTTON_ID}")
    @catch_error
    def convert_button_click(self, _event: Button.Pressed) -> None:
        curr_task = select(select_current_task)
        assert curr_task is not None
        converted_tasks = create_quiz_from_code(
            curr_task.quiz_id, curr_task.python_source_code
        )
        conv_task = next(iter(converted_tasks))
        app_dispatch(ImportCodeForTaskFromSource(
            new_task=conv_task,
            dest_task_id=curr_task.id,
            python_source_code=curr_task.python_source_code
        ))
        self.app.notify("Код импортирован")  # type: ignore[arg-type]

    def _create_theme(self, theme_type: Literal["dark", "light"]) -> TextAreaTheme:
        vs_code_dark_theme = TextAreaTheme.get_builtin_theme("vscode_dark")
        github_light_theme = TextAreaTheme.get_builtin_theme("github_light")

        base_theme = vs_code_dark_theme if theme_type == "dark" else github_light_theme
        if base_theme is None:
            raise ValueError("No base theme created")
        base_theme.name = CodeEditor.THEME_NAME
        base_theme.base_style = Style(color="white", bgcolor=None)
        base_theme.gutter_style = Style(
            color="white") if theme_type == "dark" else Style(color="black")
        return base_theme
