"""Permission Question Widget for inline permission decisions.

Renders inline in the conversation stream for single-file permission
requests.  For multi-file operations the full PermissionsScreen is
pushed instead.

Based on issue #997 — feat(tui): implement Permission Question Widget.
"""

from __future__ import annotations

import importlib
from typing import Any

from cleveragents.domain.models.core.inline_permission_question import (
    InlinePermissionQuestion,
    PermissionDecision,
)

__all__ = [
    "PermissionDecisionEvent",
    "PermissionQuestionWidget",
    "render_permission_question",
]


def _load_static_base() -> type[Any]:
    try:
        return importlib.import_module("textual.widgets").Static
    except Exception:  # pragma: no cover

        class _FallbackStatic:
            def __init__(self, *args: object, **kwargs: object) -> None:
                self._text = ""

            def update(self, text: str) -> None:
                self._text = text

        return _FallbackStatic


_StaticBase = _load_static_base()

# ── Option labels ─────────────────────────────────────────────────

_OPTIONS: list[tuple[str, str, PermissionDecision]] = [
    ("a", "Allow once", PermissionDecision.ALLOW_ONCE),
    ("A", "Allow always (this session)", PermissionDecision.ALLOW_ALWAYS),
    ("r", "Reject once", PermissionDecision.REJECT_ONCE),
    ("R", "Reject always (this session)", PermissionDecision.REJECT_ALWAYS),
]

_KEY_TO_DECISION: dict[str, PermissionDecision] = {
    key: decision for key, _, decision in _OPTIONS
}

_DECISION_INDEX: dict[PermissionDecision, int] = {
    decision: idx for idx, (_, _, decision) in enumerate(_OPTIONS)
}


# ── Pure rendering helper ─────────────────────────────────────────


def render_permission_question(
    question: InlinePermissionQuestion,
    selected_index: int = 0,
    *,
    show_diff: bool = False,
) -> str:
    """Render the permission question as a plain-text string.

    Args:
        question: The permission question to render.
        selected_index: Index of the currently highlighted option (0-3).
        show_diff: When True, append the diff content below the options.

    Returns:
        A multi-line string suitable for display in a Static widget.
    """
    lines: list[str] = [
        "Permission Required",
        "",
        question.description_line(),
        f"  {question.file_path}",
        "",
    ]

    for idx, (key, label, _) in enumerate(_OPTIONS):
        caret = "❯" if idx == selected_index else " "  # noqa: RUF001
        lines.append(f"  {caret} {key}  {label}")

    lines.append("")

    if question.has_diff:
        lines.append("For multi-file diffs, press v to open full PermissionsScreen")
    else:
        lines.append("Press v to open full PermissionsScreen with diff view")

    if show_diff and question.has_diff:
        lines.append("")
        lines.append("── diff ──")
        lines.extend(question.diff_content.splitlines())

    return "\n".join(lines)


# ── Event dataclass ───────────────────────────────────────────────


class PermissionDecisionEvent:
    """Emitted when the user makes a permission decision.

    Attributes:
        question: The original permission question.
        decision: The decision made by the user.
    """

    __slots__ = ("decision", "question")

    def __init__(
        self,
        question: InlinePermissionQuestion,
        decision: PermissionDecision,
    ) -> None:
        self.question = question
        self.decision = decision

    def __repr__(self) -> str:
        return (
            f"PermissionDecisionEvent("
            f"question={self.question!r}, "
            f"decision={self.decision!r})"
        )


# ── Widget ────────────────────────────────────────────────────────


class PermissionQuestionWidget(_StaticBase):
    """Inline permission question widget.

    Renders a compact permission request inside the conversation stream.
    The user can navigate options with ``up``/``down`` and confirm with
    ``enter``, or use the single-key shortcuts ``a``/``A``/``r``/``R``.

    Pressing ``v`` opens the full PermissionsScreen (not implemented here;
    the host application should listen for the ``open_full_screen`` flag).

    Usage::

        widget = PermissionQuestionWidget(question)
        # Simulate key press:
        event = widget.handle_key("a")   # returns PermissionDecisionEvent or None
    """

    def __init__(
        self,
        question: InlinePermissionQuestion,
        *args: object,
        **kwargs: object,
    ) -> None:
        super().__init__(*args, **kwargs)
        self._question = question
        self._selected_index: int = 0
        self._show_diff: bool = False
        self._decision: PermissionDecision | None = None
        self._open_full_screen: bool = False
        self._text = render_permission_question(question, self._selected_index)
        self.update(self._text)

    # ── Public properties ─────────────────────────────────────────

    @property
    def question(self) -> InlinePermissionQuestion:
        """Return the associated permission question."""
        return self._question

    @property
    def selected_index(self) -> int:
        """Return the index of the currently highlighted option."""
        return self._selected_index

    @property
    def decision(self) -> PermissionDecision | None:
        """Return the decision if one has been made, else None."""
        return self._decision

    @property
    def open_full_screen(self) -> bool:
        """Return True if the user requested the full PermissionsScreen."""
        return self._open_full_screen

    # ── Navigation ────────────────────────────────────────────────

    def move_up(self) -> None:
        """Move the selection cursor up by one option."""
        self._selected_index = (self._selected_index - 1) % len(_OPTIONS)
        self._refresh()

    def move_down(self) -> None:
        """Move the selection cursor down by one option."""
        self._selected_index = (self._selected_index + 1) % len(_OPTIONS)
        self._refresh()

    # ── Key handling ──────────────────────────────────────────────

    def handle_key(self, key: str) -> PermissionDecisionEvent | None:
        """Process a key press and return a decision event if resolved.

        Args:
            key: Single character key string (e.g. ``"a"``, ``"up"``,
                 ``"down"``, ``"enter"``, ``"v"``).

        Returns:
            A :class:`PermissionDecisionEvent` when a decision is made,
            ``None`` otherwise.
        """
        if key == "up":
            self.move_up()
            return None
        if key == "down":
            self.move_down()
            return None
        if key == "v":
            self._open_full_screen = True
            return None
        if key == "enter":
            _, _, decision = _OPTIONS[self._selected_index]
            return self._resolve(decision)
        if key in _KEY_TO_DECISION:
            return self._resolve(_KEY_TO_DECISION[key])
        return None

    # ── Internal helpers ──────────────────────────────────────────

    def _resolve(self, decision: PermissionDecision) -> PermissionDecisionEvent:
        """Record the decision and return the event."""
        self._decision = decision
        self._refresh()
        return PermissionDecisionEvent(question=self._question, decision=decision)

    def _refresh(self) -> None:
        """Re-render the widget text."""
        self._text = render_permission_question(
            self._question,
            self._selected_index,
            show_diff=self._show_diff,
        )
        self.update(self._text)
