"""Keyboard input modes for TUI pages (navigation, slash commands, text entry)."""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Literal, Protocol

from pygittools.tui.changes_list import ChangesList
from pygittools.tui.keys import (
    KeyLike,
    is_backspace,
    is_down,
    is_enter,
    is_esc,
    is_left,
    is_right,
    is_slash,
    is_up,
    move_hint,
    printable_char,
    set_vim_enabled,
    tab_hint,
)
from pygittools.tui.log_list import CommitLogList
from pygittools.tui.pages.actions import actions_page
from pygittools.tui.pages.branches import branch_picker_page
from pygittools.tui.pages.help import help_page
from pygittools.tui.pages.projects import projects_page
from pygittools.tui.pages.worktree_add import worktree_add_page
from pygittools.tui.types import PageAction, PageContext, PageResult

if TYPE_CHECKING:
    from pygittools.tui.tasks_list import TasksBoardList
    from pygittools.tui.worktrees_list import WorktreesList

_HeaderFocus = Literal["changes", "repo", "branch", "actions"]
_HomeTab = Literal["status", "log", "tasks", "worktrees"]
_ModeName = Literal["nav", "input", "command"]

_COMMAND_HELP = "help actions quit commit vim novim status log tasks worktrees projects branch"


@dataclass(frozen=True, slots=True)
class ContextDispatch:
    page_result: PageResult | None = None
    next_mode: _ModeName | None = None
    refresh_log: bool = False
    command_error: str | None = None


class PageInputHost(Protocol):
    @property
    def header_focus(self) -> _HeaderFocus: ...

    def set_header_focus(self, focus: _HeaderFocus) -> None: ...

    @property
    def page_context(self) -> PageContext | None: ...

    @property
    def changes(self) -> ChangesList: ...

    @property
    def log(self) -> CommitLogList: ...

    @property
    def active_tab(self) -> _HomeTab: ...

    def set_active_tab(self, tab: _HomeTab) -> None: ...

    @property
    def tasks(self) -> TasksBoardList: ...

    @property
    def worktrees(self) -> WorktreesList: ...

    @property
    def has_actions(self) -> bool: ...


class NavContext:
    def status_hint(self, host: PageInputHost) -> str:
        suffix = f"{move_hint()} — {tab_hint()} — / command"
        if host.header_focus == "repo":
            return f"Enter browse projects — {suffix}"
        if host.header_focus == "branch":
            if host.active_tab == "tasks":
                return f"Enter sync tasks — {suffix}"
            if host.active_tab == "worktrees":
                return f"Enter add worktree — {suffix}"
            return f"Enter switch branch — {suffix}"
        if host.header_focus == "actions":
            if host.has_actions:
                return f"Enter open actions — {suffix}"
            return f"No action groups — {suffix}"
        if host.active_tab == "log":
            return host.log.status_hint()
        if host.active_tab == "tasks":
            return host.tasks.status_hint()
        if host.active_tab == "worktrees":
            return host.worktrees.status_hint()
        return host.changes.status_hint()

    def dispatch_key(self, host: PageInputHost, key: KeyLike) -> ContextDispatch:
        if is_slash(key):
            return ContextDispatch(next_mode="command")

        if _try_switch_tab(host, key):
            return ContextDispatch()

        if host.header_focus == "repo":
            if is_down(key):
                host.set_header_focus("branch")
            elif is_enter(key):
                return ContextDispatch(page_result=PageResult(action=PageAction.PUSH, next_page=projects_page()))
            return ContextDispatch()

        if host.header_focus == "branch":
            if is_up(key):
                host.set_header_focus("repo")
            elif is_down(key):
                host.set_header_focus("actions" if _show_actions_row(host) else "changes")
            elif is_enter(key):
                if host.active_tab == "tasks" and host.page_context is not None:
                    host.tasks.sync_with_default_remote(host.page_context.repo)
                    return ContextDispatch()
                if host.active_tab == "worktrees":
                    return ContextDispatch(
                        page_result=PageResult(action=PageAction.PUSH, next_page=worktree_add_page()),
                    )
                return ContextDispatch(
                    page_result=PageResult(action=PageAction.PUSH, next_page=branch_picker_page()),
                )
            return ContextDispatch()

        if host.header_focus == "actions":
            if is_up(key):
                host.set_header_focus("branch")
            elif is_down(key):
                host.set_header_focus("changes")
            elif is_enter(key) and host.has_actions:
                return ContextDispatch(page_result=PageResult(action=PageAction.PUSH, next_page=actions_page()))
            return ContextDispatch()

        if is_up(key) and _content_list_is_at_top(host):
            host.set_header_focus("actions" if _show_actions_row(host) else "branch")
            return ContextDispatch()

        ctx = host.page_context
        if ctx is None:
            return ContextDispatch()
        if host.active_tab == "log":
            host.log.handle_nav_key(key, ctx.repo)
            return ContextDispatch()
        if host.active_tab == "tasks":
            page_result = host.tasks.handle_nav_key(key, ctx.repo)
            return ContextDispatch(page_result=page_result)
        if host.active_tab == "worktrees":
            page_result = host.worktrees.handle_nav_key(key, ctx.repo)
            return ContextDispatch(page_result=page_result)
        nav_result = host.changes.handle_nav_key(key, ctx.repo)
        if nav_result.enter_input:
            return ContextDispatch(next_mode="input")
        return ContextDispatch(refresh_log=nav_result.committed)


class CommandContext:
    def __init__(self) -> None:
        self._buffer = ""
        self._error: str | None = None

    def status_hint(self, host: PageInputHost) -> str:
        del host
        if self._error is not None:
            return self._error
        if not self._buffer:
            return f"/ — {_COMMAND_HELP} — Esc cancel"
        return f"/{self._buffer}"

    def dispatch_key(self, host: PageInputHost, key: KeyLike) -> ContextDispatch:
        if is_esc(key):
            return ContextDispatch(next_mode="nav")
        if is_backspace(key):
            self._error = None
            self._buffer = self._buffer[:-1]
            return ContextDispatch()
        if is_enter(key):
            return self._run(host)
        char = printable_char(key)
        if char is not None and not char.isspace():
            self._error = None
            self._buffer += char
            return ContextDispatch()
        return ContextDispatch()

    def _run(self, host: PageInputHost) -> ContextDispatch:
        raw = self._buffer.strip().casefold()
        name = raw.lstrip("/")
        self._buffer = ""
        if not name:
            return ContextDispatch(next_mode="nav")

        if name in {"help", "?"}:
            return ContextDispatch(
                next_mode="nav",
                page_result=PageResult(action=PageAction.PUSH, next_page=help_page()),
            )
        if name in {"actions", "!"}:
            return ContextDispatch(
                next_mode="nav",
                page_result=PageResult(action=PageAction.PUSH, next_page=actions_page()),
            )
        if name in {"quit", "q"}:
            return ContextDispatch(next_mode="nav", page_result=PageResult(action=PageAction.QUIT))
        if name == "commit":
            host.set_active_tab("status")
            host.set_header_focus("changes")
            host.changes.begin_input()
            return ContextDispatch(next_mode="input")
        if name == "vim":
            set_vim_enabled(True)
            return ContextDispatch(next_mode="nav")
        if name in {"novim", "arrows"}:
            set_vim_enabled(False)
            return ContextDispatch(next_mode="nav")
        tab = _tab_command(name)
        if tab is not None:
            host.set_active_tab(tab)
            host.set_header_focus("changes")
            return ContextDispatch(next_mode="nav")
        if name == "projects":
            return ContextDispatch(
                next_mode="nav",
                page_result=PageResult(action=PageAction.PUSH, next_page=projects_page()),
            )
        if name == "branch":
            return ContextDispatch(
                next_mode="nav",
                page_result=PageResult(action=PageAction.PUSH, next_page=branch_picker_page()),
            )

        self._error = f"Unknown command: /{name} — {_COMMAND_HELP}"
        return ContextDispatch()


_TAB_COMMANDS: dict[str, _HomeTab] = {
    "status": "status",
    "log": "log",
    "tasks": "tasks",
    "worktrees": "worktrees",
}


def _tab_command(name: str) -> _HomeTab | None:
    return _TAB_COMMANDS.get(name)


class InputContext:
    def status_hint(self, host: PageInputHost) -> str:
        return host.changes.input_status_hint()

    def dispatch_key(self, host: PageInputHost, key: KeyLike) -> ContextDispatch:
        if is_esc(key):
            return ContextDispatch(next_mode="nav")
        ctx = host.page_context
        if ctx is not None:
            result = host.changes.handle_input_key(key, ctx.repo)
            if result.relinquish:
                return ContextDispatch(next_mode="nav", refresh_log=result.committed)
        return ContextDispatch()


InputMode = NavContext | InputContext | CommandContext


def nav_context() -> NavContext:
    return NavContext()


def input_context() -> InputContext:
    return InputContext()


def command_context() -> CommandContext:
    return CommandContext()


def _show_actions_row(host: PageInputHost) -> bool:
    return host.active_tab == "status"


def _content_list_is_at_top(host: PageInputHost) -> bool:
    if host.active_tab == "log":
        return host.log.is_at_top()
    if host.active_tab == "tasks":
        return host.tasks.is_at_top()
    if host.active_tab == "worktrees":
        return host.worktrees.is_at_top()
    return host.changes.is_at_top()


def _try_switch_tab(host: PageInputHost, key: KeyLike) -> bool:
    tabs: tuple[_HomeTab, ...] = ("status", "log", "tasks", "worktrees")
    current = host.active_tab
    index = tabs.index(current)
    if is_left(key):
        if index > 0:
            host.set_active_tab(tabs[index - 1])
        return True
    if is_right(key):
        if index < len(tabs) - 1:
            host.set_active_tab(tabs[index + 1])
        return True
    return False