"""Scrollable commit log for the pgt TUI."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import UTC, datetime

from pygit2 import GIT_SORT_TIME, Commit, GitError, Repository
from unicurses import (  # type: ignore[import-untyped]
	A_REVERSE,
	KEY_DOWN,
	KEY_RESIZE,
	KEY_UP,
)

from pygittools.tui.draw import draw_line

_MAX_COMMITS = 500


@dataclass(frozen=True, slots=True)
class _LogEntry:
	short_id: str
	date: str
	subject: str


@dataclass(frozen=True, slots=True)
class LogNavKeyResult:
	layout_changed: bool = False


class CommitLogList:
	def __init__(self) -> None:
		self._entries: list[_LogEntry] = []
		self._cursor = 0
		self._scroll_offset = 0

	def is_at_top(self) -> bool:
		return self._cursor == 0

	def refresh(self, repo: Repository) -> None:
		self._entries = _load_entries(repo)
		max_index = max(0, len(self._entries) - 1)
		self._cursor = min(self._cursor, max_index)
		self._scroll_offset = min(self._scroll_offset, max(0, len(self._entries) - 1))

	def draw(self, top_row: int, height: int, width: int, *, highlight: bool) -> None:
		if height <= 0:
			return
		if not self._entries:
			draw_line(top_row, 0, "No commits", width)
			return
		self._ensure_cursor_visible(height)
		for view_row in range(height):
			index = self._scroll_offset + view_row
			if index >= len(self._entries):
				break
			entry = self._entries[index]
			label = _format_entry(entry, width)
			attr = A_REVERSE if highlight and index == self._cursor else 0
			draw_line(top_row + view_row, 0, label, width, attr)

	def handle_nav_key(self, key: int, repo: Repository) -> LogNavKeyResult:
		if key == KEY_RESIZE:
			return LogNavKeyResult()
		if key in (KEY_UP, ord("k"), ord("K")):
			self._cursor = max(0, self._cursor - 1)
			return LogNavKeyResult()
		if key in (KEY_DOWN, ord("j"), ord("J")):
			self._cursor = min(max(0, len(self._entries) - 1), self._cursor + 1)
			return LogNavKeyResult()
		self.refresh(repo)
		return LogNavKeyResult()

	def status_hint(self) -> str:
		if not self._entries:
			return "No commits — j/k move"
		entry = self._entries[self._cursor]
		return f"{entry.short_id} {entry.date} — j/k move"

	def _ensure_cursor_visible(self, height: int) -> None:
		if self._cursor < self._scroll_offset:
			self._scroll_offset = self._cursor
		elif self._cursor >= self._scroll_offset + height:
			self._scroll_offset = self._cursor - height + 1


def _load_entries(repo: Repository) -> list[_LogEntry]:
	try:
		head = repo.head.target
	except GitError:
		return []
	entries: list[_LogEntry] = []
	for commit in repo.walk(head, GIT_SORT_TIME):
		if not isinstance(commit, Commit):
			continue
		subject = commit.message.splitlines()[0] if commit.message else "(no message)"
		entries.append(
			_LogEntry(
				short_id=str(commit.id)[:7],
				date=_format_commit_date(commit),
				subject=subject,
			),
		)
		if len(entries) >= _MAX_COMMITS:
			break
	return entries


def _format_commit_date(commit: Commit) -> str:
	return datetime.fromtimestamp(commit.commit_time, tz=UTC).strftime("%Y-%m-%d")


def _format_entry(entry: _LogEntry, width: int) -> str:
	prefix = f"{entry.short_id} {entry.date} "
	remaining = max(0, width - len(prefix))
	subject = entry.subject
	if len(subject) > remaining:
		subject = subject[: max(0, remaining - 1)] + "…"
	return f"{prefix}{subject}"[:width]