"""PermissionsScreen for displaying tool permission requests with diff views.

Shows a split-pane layout: a file list on the left and a diff view on the right.
Supports three diff display modes (unified, side-by-side, context) toggled with ``d``.
Allow/reject keyboard bindings: ``a`` allow-once, ``A`` allow-always,
``r`` reject-once, ``R`` reject-always.
"""

from __future__ import annotations

import contextlib
import importlib
from collections.abc import Iterator
from typing import Any, ClassVar

from cleveragents.tui.permissions.models import (
    DiffDisplayMode,
    PermissionDecision,
    PermissionRequest,
    ToolPermissionRequest,
)

__all__ = ["PermissionsScreen"]

# ── Optional Textual import gate ─────────────────────────────────


def _load_screen_base() -> type[Any]:
    try:
        return importlib.import_module("textual.app").Screen
    except Exception:  # pragma: no cover

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

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

        return _FallbackScreen


_ScreenBase = _load_screen_base()

# ── Diff display mode cycle ───────────────────────────────────────

_DIFF_MODE_CYCLE: list[DiffDisplayMode] = [
    DiffDisplayMode.UNIFIED,
    DiffDisplayMode.SPLIT,
    DiffDisplayMode.AUTO,
]


def _next_diff_mode(current: DiffDisplayMode) -> DiffDisplayMode:
    """Return the next diff display mode in the cycle."""
    idx = _DIFF_MODE_CYCLE.index(current)
    return _DIFF_MODE_CYCLE[(idx + 1) % len(_DIFF_MODE_CYCLE)]


# ── Rendering helpers ─────────────────────────────────────────────


def _render_file_list(
    changes: list[PermissionRequest],
    selected_index: int,
) -> str:
    """Render the file list panel."""
    lines: list[str] = [f"Files ({len(changes)} changes):"]
    for i, change in enumerate(changes):
        prefix = "❯ " if i == selected_index else "  "  # noqa: RUF001
        lines.append(f"{prefix}{change.path} [{change.change_type}]")
    return "\n".join(lines)


def _render_diff_panel(
    change: PermissionRequest | None,
    mode: DiffDisplayMode,
) -> str:
    """Render the diff panel for the selected file."""
    if change is None:
        return "(no file selected)"
    header = f"{change.path}\n{'─' * 50}"
    diff_text = change.render_diff(mode)
    if not diff_text:
        diff_text = "(no changes)"
    return f"{header}\n{diff_text}"


def _render_status_bar(
    tool_name: str,
    resource_name: str,
    mode: DiffDisplayMode,
) -> str:
    """Render the status bar at the bottom of the screen."""
    return (
        f"{tool_name} wants to modify files in {resource_name}\n"
        f"[M] Modified  [A] Added  [D] Deleted  |  Diff: {mode}\n"
        "a Allow │ A Allow Always │ r Reject │ R Reject Always │ j/k Nav │ d Diff │ esc"
    )


def _render_screen(
    request: ToolPermissionRequest,
    selected_index: int,
    diff_mode: DiffDisplayMode,
) -> str:
    """Render the full PermissionsScreen content as text."""
    file_list = _render_file_list(request.changes, selected_index)
    selected_change = request.changes[selected_index] if request.changes else None
    diff_panel = _render_diff_panel(selected_change, diff_mode)
    status_bar = _render_status_bar(request.tool_name, request.resource_name, diff_mode)
    return f"Permission Request\n\n{file_list}\n\n{diff_panel}\n\n{status_bar}"


# ── PermissionsScreen ─────────────────────────────────────────────


class PermissionsScreen(_ScreenBase):
    """TUI screen that displays a tool permission request with a diff view.

    Inherits from ``textual.app.Screen`` so it can be pushed onto the
    Textual screen stack via ``app.push_screen()``, receive keyboard events
    natively through ``BINDINGS``, and participate in the full Textual
    screen lifecycle (``compose``, ``on_mount``, ``dismiss``, etc.).

    Layout:
    - Title: "Permission Request"
    - Left panel: file list with change type indicators
    - Right panel: diff view for the selected file
    - Status bar: keyboard bindings and diff mode indicator

    Keyboard bindings:
    - ``a``: allow once
    - ``A``: allow always (session)
    - ``r``: reject once
    - ``R``: reject always (session)
    - ``j`` / ``k``: navigate file list (next / previous)
    - ``d``: cycle diff display mode (unified → side-by-side → context)
    - ``escape``: dismiss screen
    """

    BINDINGS: ClassVar[list[tuple[str, str, str]]] = [
        ("a", "allow_once", "Allow Once"),
        ("A", "allow_always", "Allow Always"),
        ("r", "reject_once", "Reject Once"),
        ("R", "reject_always", "Reject Always"),
        ("j", "nav_next", "Next File"),
        ("k", "nav_prev", "Previous File"),
        ("d", "cycle_diff", "Cycle Diff Mode"),
        ("escape", "dismiss_screen", "Dismiss"),
    ]

    def __init__(self, *args: object, **kwargs: object) -> None:
        super().__init__(*args, **kwargs)
        self._request: ToolPermissionRequest | None = None
        self._selected_index: int = 0
        self._diff_mode: DiffDisplayMode = DiffDisplayMode.UNIFIED
        self._decision: PermissionDecision | None = None
        self._text: str = "(no permission request)"

    def update(self, text: str) -> None:
        """Update the internal text representation.

        Stores the rendered text for programmatic access and testing.
        In a live Textual app, the ``compose()`` method renders the
        content via widgets; this method keeps the ``_text`` attribute
        in sync for non-Textual usage and testing.
        """
        self._text = text

    def compose(self) -> Iterator[Any]:
        """Compose the screen layout with a Static widget showing the content.

        Returns a generator over Textual ``Widget`` instances (the
        ``ComposeResult`` type), but typed as ``Iterator[Any]`` here so
        the module stays importable in environments where ``textual`` is
        not installed (typecheck gate, pure-domain tests).
        """
        with contextlib.suppress(Exception):  # pragma: no cover
            Static = importlib.import_module("textual.widgets").Static
            yield Static(self._text, id="permissions-content")

    # ── Public API ────────────────────────────────────────────────

    @property
    def current_request(self) -> ToolPermissionRequest | None:
        """Return the currently displayed permission request."""
        return self._request

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

    @property
    def diff_mode(self) -> DiffDisplayMode:
        """Return the current diff display mode."""
        return self._diff_mode

    @property
    def decision(self) -> PermissionDecision | None:
        """Return the decision made by the user, or None if pending."""
        return self._decision

    def load_request(self, request: ToolPermissionRequest) -> None:
        """Load a new permission request and reset state."""
        self._request = request
        self._selected_index = 0
        self._diff_mode = DiffDisplayMode.UNIFIED
        self._decision = None
        self._refresh()

    def clear(self) -> None:
        """Clear the current request and reset state."""
        self._request = None
        self._selected_index = 0
        self._diff_mode = DiffDisplayMode.UNIFIED
        self._decision = None
        self._text = ""
        self.update("")

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

    def navigate_next(self) -> None:
        """Move selection to the next file (``j`` key)."""
        if self._request and self._request.changes:
            self._selected_index = (self._selected_index + 1) % len(
                self._request.changes
            )
            self._refresh()

    def navigate_prev(self) -> None:
        """Move selection to the previous file (``k`` key)."""
        if self._request and self._request.changes:
            self._selected_index = (self._selected_index - 1) % len(
                self._request.changes
            )
            self._refresh()

    def cycle_diff_mode(self) -> DiffDisplayMode:
        """Cycle to the next diff display mode (``d`` key).

        Returns the new mode.
        """
        self._diff_mode = _next_diff_mode(self._diff_mode)
        self._refresh()
        return self._diff_mode

    def set_diff_mode(self, mode: DiffDisplayMode) -> None:
        """Set the diff display mode directly."""
        self._diff_mode = mode
        self._refresh()

    # ── Decision actions ──────────────────────────────────────────

    def allow_once(self) -> PermissionDecision:
        """Record an allow-once decision (``a`` key)."""
        return self._record_decision(PermissionDecision.ALLOW_ONCE)

    def allow_always(self) -> PermissionDecision:
        """Record an allow-always decision (``A`` key)."""
        return self._record_decision(PermissionDecision.ALLOW_ALWAYS)

    def reject_once(self) -> PermissionDecision:
        """Record a reject-once decision (``r`` key)."""
        return self._record_decision(PermissionDecision.REJECT_ONCE)

    def reject_always(self) -> PermissionDecision:
        """Record a reject-always decision (``R`` key)."""
        return self._record_decision(PermissionDecision.REJECT_ALWAYS)

    def _record_decision(self, decision: PermissionDecision) -> PermissionDecision:
        """Store the decision and update the display."""
        self._decision = decision
        if self._request is not None:
            self._request = self._request.apply_decision(decision)
        self._refresh()
        return decision

    # ── Textual action methods ────────────────────────────────────

    def action_allow_once(self) -> None:
        """Textual action: allow once (``a`` key binding)."""
        self.allow_once()

    def action_allow_always(self) -> None:
        """Textual action: allow always (``A`` key binding)."""
        self.allow_always()

    def action_reject_once(self) -> None:
        """Textual action: reject once (``r`` key binding)."""
        self.reject_once()

    def action_reject_always(self) -> None:
        """Textual action: reject always (``R`` key binding)."""
        self.reject_always()

    def action_nav_next(self) -> None:
        """Textual action: navigate to next file (``j`` key binding)."""
        self.navigate_next()

    def action_nav_prev(self) -> None:
        """Textual action: navigate to previous file (``k`` key binding)."""
        self.navigate_prev()

    def action_cycle_diff(self) -> None:
        """Textual action: cycle diff display mode (``d`` key binding)."""
        self.cycle_diff_mode()

    def action_dismiss_screen(self) -> None:
        """Textual action: dismiss the screen (``escape`` key binding)."""
        with contextlib.suppress(Exception):  # pragma: no cover
            self.dismiss()

    # ── Rendering ─────────────────────────────────────────────────

    def _refresh(self) -> None:
        """Re-render the screen content."""
        if self._request is None:
            self._text = "(no permission request)"
            self.update(self._text)
            return
        self._text = _render_screen(
            self._request,
            self._selected_index,
            self._diff_mode,
        )
        self.update(self._text)
