"""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]
	KEY_DOWN,
	KEY_RESIZE,
	KEY_UP,
)

from pygittools.tui.draw import clear_row, draw_text
from pygittools.tui.git_colors import current_theme, log_entry_attrs

_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_text(top_row, 0, "No commits", width, current_theme().log_date)
			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]
			focused = highlight and index == self._cursor
			_draw_entry(top_row + view_row, width, entry, focused=focused, highlight=highlight)

	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 _draw_entry(row: int, width: int, entry: _LogEntry, *, focused: bool, highlight: bool) -> None:
	clear_row(row)
	hash_attr, date_attr, subject_attr = log_entry_attrs(focused=focused, highlight=highlight)
	col = draw_text(row, 0, f"{entry.short_id} ", width, hash_attr)
	col = draw_text(row, col, f"{entry.date} ", width, date_attr)
	remaining = max(0, width - col)
	subject = entry.subject
	if len(subject) > remaining:
		subject = subject[: max(0, remaining - 1)] + "…"
	draw_text(row, col, subject, width, subject_attr)


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]