"""Linked worktree list for the pgt TUI."""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Literal

from pygit2 import Repository
from pyratatui import Frame, List, ListItem, ListState, Rect

from pygittools.tui.git_colors import current_theme, focus_style
from pygittools.tui.git_worktrees import WorktreeInfo, list_worktrees, remove_worktree
from pygittools.tui.keys import KeyLike, is_down, is_enter, is_up, move_hint
from pygittools.tui.pages.worktree_add import worktree_add_page
from pygittools.tui.types import PageAction, PageResult


@dataclass(frozen=True, slots=True)
class _Row:
    kind: Literal["add", "worktree"]
    info: WorktreeInfo | None


class WorktreesList:
    def __init__(self) -> None:
        self._rows: list[_Row] = []
        self._list_state = ListState()
        self._error: str | None = None
        self._current_path: Path | None = None

    def is_at_top(self) -> bool:
        return (self._list_state.selected or 0) == 0

    def refresh(self, repo: Repository) -> None:
        workdir = repo.workdir
        self._current_path = Path(workdir).resolve() if workdir is not None else None
        entries, error = list_worktrees(repo)
        self._error = error
        rows: list[_Row] = [_Row(kind="add", info=None)]
        for info in entries:
            rows.append(_Row(kind="worktree", info=info))
        self._rows = rows
        selected = self._list_state.selected or 0
        self._list_state.select(min(selected, max(0, len(self._rows) - 1)))

    def draw(self, frame: Frame, area: Rect, *, highlight: bool) -> None:
        if area.height <= 0 or area.width <= 0:
            return
        theme = current_theme()
        items: list[ListItem] = []
        error_offset = 0
        if self._error:
            items.append(ListItem(self._error, theme.log_date))
            error_offset = 1
        selected = self._list_state.selected or 0
        for index, row in enumerate(self._rows):
            label = _row_label(row, current=self._current_path, width=max(10, area.width - 4))
            focused = highlight and index == selected
            style = focus_style(theme.normal) if focused and highlight else theme.normal
            if row.kind == "add":
                style = focus_style(theme.header) if focused and highlight else theme.header
            items.append(ListItem(label, style))
        if not self._rows and not self._error:
            items.append(ListItem("No worktrees", theme.log_date))
        list_state = self._list_state
        if error_offset:
            list_state = ListState()
            row_selected = self._list_state.selected
            if row_selected is not None:
                list_state.select(row_selected + error_offset)
        frame.render_stateful_list(
            List(items).highlight_style(theme.focus).highlight_symbol(""),
            area,
            list_state,
        )

    def handle_nav_key(self, key: KeyLike, repo: Repository) -> PageResult | None:
        if self._error is not None and not (is_up(key) or is_down(key)):
            self._error = None
        if is_up(key):
            selected = self._list_state.selected or 0
            self._list_state.select(max(0, selected - 1))
            return None
        if is_down(key):
            selected = self._list_state.selected or 0
            self._list_state.select(min(max(0, len(self._rows) - 1), selected + 1))
            return None
        if not self._rows:
            return None
        row = self._rows[self._list_state.selected or 0]
        if is_enter(key):
            if row.kind == "add":
                return PageResult(action=PageAction.PUSH, next_page=worktree_add_page())
            if row.info is not None and self._current_path is not None:
                target = row.info.path.resolve()
                if target != self._current_path.resolve():
                    return PageResult(action=PageAction.PUSH, switch_repo=target)
            return None
        if key.code in {"d", "D"} and row.kind == "worktree" and row.info is not None:
            if self._current_path is not None and row.info.path.resolve() == self._current_path.resolve():
                self._error = "Cannot remove the active worktree"
                return None
            ok, error = remove_worktree(repo, row.info.path, force=True)
            if ok:
                self.refresh(repo)
                return None
            self._error = error
        return None

    def status_hint(self) -> str:
        if self._error:
            return self._error
        if not self._rows:
            return "No worktrees"
        move = move_hint()
        row = self._rows[self._list_state.selected or 0]
        if row.kind == "add":
            return f"Enter add worktree — {move}"
        if row.info is not None and self._current_path is not None:
            if row.info.path.resolve() == self._current_path.resolve():
                return f"Active worktree — d remove other worktrees — {move}"
            return f"Enter open worktree — d remove — {move}"
        return move


def _row_label(row: _Row, *, current: Path | None, width: int) -> str:
    if row.kind == "add":
        return "(+ Add worktree)"
    if row.info is None:
        return ""
    info = row.info
    marker = "* " if current is not None and info.path.resolve() == current.resolve() else "  "
    if info.bare:
        branch = "(bare)"
    elif info.branch:
        branch = info.branch
    elif info.detached:
        branch = "(detached)"
    else:
        branch = "?"
    extras: list[str] = []
    if info.locked is not None:
        extras.append("locked")
    if info.prunable is not None:
        extras.append("prunable")
    suffix = f" [{', '.join(extras)}]" if extras else ""
    path_str = _display_path(info.path, anchor=current)
    label = f"{marker}{path_str}  {branch}{suffix}"
    if len(label) > width:
        return label[: max(0, width - 1)] + "…"
    return label


def _display_path(path: Path, *, anchor: Path | None) -> str:
    if anchor is not None:
        try:
            return str(path.resolve().relative_to(anchor.parent.resolve()))
        except ValueError:
            pass
    return str(path)