"""Working tree status and index helpers for the pgt TUI."""

from __future__ import annotations

import subprocess

from pygit2 import Commit, GitError, Index, IndexEntry, Repository, Tree
from pygit2.enums import FileStatus

from pygittools.hooks_push import git_executable

_INDEX_FLAGS = (
    FileStatus.INDEX_NEW
    | FileStatus.INDEX_MODIFIED
    | FileStatus.INDEX_DELETED
    | FileStatus.INDEX_RENAMED
    | FileStatus.INDEX_TYPECHANGE
)
_WT_FLAGS = (
    FileStatus.WT_MODIFIED
    | FileStatus.WT_DELETED
    | FileStatus.WT_TYPECHANGE
    | FileStatus.WT_RENAMED
    | FileStatus.WT_UNREADABLE
)


def categorize_status(repo: Repository) -> tuple[list[str], list[str], list[str]]:
    """Return staged, unstaged, and untracked paths from ``repo.status()``."""
    status = repo.status()
    staged = sorted(path for path, flags in status.items() if flags & _INDEX_FLAGS)
    unstaged = sorted(path for path, flags in status.items() if flags & _WT_FLAGS)
    untracked = sorted(path for path, flags in status.items() if flags & FileStatus.WT_NEW)
    return staged, unstaged, untracked


def _index_path(path: str) -> str:
    """Normalize a path for libgit2 index operations.

    pygit2 reports untracked directories with a trailing slash, but
    ``index.add`` rejects those paths.
    """
    return path.removesuffix("/")


def stage_path(repo: Repository, path: str) -> None:
    stage_paths(repo, [path])


def stage_paths(repo: Repository, paths: list[str]) -> None:
    if not paths:
        return
    index = repo.index
    for path in paths:
        status_flags = repo.status().get(path, 0)
        if status_flags & FileStatus.WT_DELETED:
            index.remove(path)
        else:
            index.add(_index_path(path))
    index.write()


def unstage_path(repo: Repository, path: str) -> None:
    index = repo.index
    _unstage_index_path(index, _head_tree(repo), path)
    index.write()


def unstage_paths(repo: Repository, paths: list[str]) -> None:
    if not paths:
        return
    index = repo.index
    head_tree = _head_tree(repo)
    for path in paths:
        _unstage_index_path(index, head_tree, path)
    index.write()


def _head_tree(repo: Repository) -> Tree | None:
    if repo.head_is_unborn:
        return None
    try:
        return repo.head.peel(Commit).tree
    except GitError:
        return None


def _unstage_index_path(index: Index, head_tree: Tree | None, path: str) -> None:
    if head_tree is not None and path in head_tree:
        entry = head_tree[path]
        index.add(IndexEntry(path, entry.id, entry.filemode))
    else:
        index.remove(path)


def commit_staged(repo: Repository, message: str, *, amend: bool = False) -> tuple[bool, str]:
    """Create or amend a commit via the git CLI so client hooks run. Returns (ok, error)."""
    trimmed = message.strip()
    if not trimmed:
        return False, "Commit message is empty"
    workdir = repo.workdir
    if workdir is None:
        return False, "Repository has no working directory"
    if amend:
        if repo.head_is_unborn:
            return False, "Nothing to amend"
    else:
        staged, _, _ = categorize_status(repo)
        if not staged:
            return False, "Nothing staged to commit"

    command = [git_executable(), "commit"]
    if amend:
        command.append("--amend")
    command.extend(["-F", "-"])
    result = subprocess.run(
        command,
        cwd=workdir,
        input=f"{trimmed}\n",
        capture_output=True,
        text=True,
        check=False,
    )
    if result.returncode == 0:
        return True, ""

    output = (result.stderr or result.stdout or "Commit failed").strip()
    if not output:
        return False, "Commit failed"
    return False, output.splitlines()[-1]