"""Git worktree listing and mutation via the git CLI."""

from __future__ import annotations

import subprocess
from dataclasses import dataclass
from pathlib import Path

from pygit2 import Repository

from pygittools.hooks_push import git_executable


@dataclass(frozen=True, slots=True)
class WorktreeInfo:
	path: Path
	head: str | None
	branch: str | None
	bare: bool
	detached: bool
	locked: str | None
	prunable: str | None


def list_worktrees(repo: Repository) -> tuple[list[WorktreeInfo], str | None]:
	workdir = repo.workdir
	if workdir is None:
		return [], "Repository has no working directory"
	result = subprocess.run(
		[git_executable(), "worktree", "list", "--porcelain"],
		cwd=workdir,
		capture_output=True,
		text=True,
		check=False,
	)
	if result.returncode != 0:
		output = (result.stderr or result.stdout or "git worktree list failed").strip()
		return [], output.splitlines()[-1]
	return parse_worktree_porcelain(result.stdout), None


def parse_worktree_porcelain(text: str) -> list[WorktreeInfo]:
	entries: list[WorktreeInfo] = []
	block: list[str] = []
	for raw_line in text.splitlines():
		line = raw_line.strip()
		if not line:
			if block:
				entries.append(_block_to_info(block))
				block = []
			continue
		if line.startswith("worktree "):
			if block:
				entries.append(_block_to_info(block))
			block = [line]
		else:
			block.append(line)
	if block:
		entries.append(_block_to_info(block))
	return entries


def _block_to_info(lines: list[str]) -> WorktreeInfo:
	path = Path(lines[0].removeprefix("worktree ").strip())
	head: str | None = None
	branch: str | None = None
	bare = False
	detached = False
	locked: str | None = None
	prunable: str | None = None
	for line in lines[1:]:
		if line == "bare":
			bare = True
		elif line == "detached":
			detached = True
		elif line == "locked":
			locked = ""
		elif line.startswith("locked "):
			locked = line.removeprefix("locked ").strip()
		elif line == "prunable":
			prunable = ""
		elif line.startswith("prunable "):
			prunable = line.removeprefix("prunable ").strip()
		elif line.startswith("HEAD "):
			head = line.removeprefix("HEAD ").strip()
		elif line.startswith("branch "):
			ref = line.removeprefix("branch ").strip()
			branch = ref.removeprefix("refs/heads/") if ref.startswith("refs/heads/") else ref
	return WorktreeInfo(
		path=path,
		head=head,
		branch=branch,
		bare=bare,
		detached=detached,
		locked=locked,
		prunable=prunable,
	)


def add_worktree(repo: Repository, path: Path, branch: str | None) -> tuple[bool, str]:
	workdir = repo.workdir
	if workdir is None:
		return False, "Repository has no working directory"
	trimmed_branch = (branch or "").strip()
	target = _resolve_worktree_path(workdir, path)
	cmd = [git_executable(), "worktree", "add"]
	if trimmed_branch:
		local = {name for name in repo.branches.local}
		if trimmed_branch in local:
			cmd.extend([str(target), trimmed_branch])
		else:
			cmd.extend(["-b", trimmed_branch, str(target)])
	else:
		cmd.append(str(target))
	result = subprocess.run(
		cmd,
		cwd=workdir,
		capture_output=True,
		text=True,
		check=False,
	)
	if result.returncode == 0:
		return True, ""
	output = (result.stderr or result.stdout or "git worktree add failed").strip()
	if not output:
		return False, "git worktree add failed"
	return False, output.splitlines()[-1]


def remove_worktree(repo: Repository, path: Path, *, force: bool) -> tuple[bool, str]:
	workdir = repo.workdir
	if workdir is None:
		return False, "Repository has no working directory"
	cmd = [git_executable(), "worktree", "remove"]
	if force:
		cmd.append("--force")
	cmd.append(str(path.resolve()))
	result = subprocess.run(
		cmd,
		cwd=workdir,
		capture_output=True,
		text=True,
		check=False,
	)
	if result.returncode == 0:
		return True, ""
	output = (result.stderr or result.stdout or "git worktree remove failed").strip()
	if not output:
		return False, "git worktree remove failed"
	return False, output.splitlines()[-1]


def _resolve_worktree_path(workdir: str | Path, path: Path) -> Path:
	if path.is_absolute():
		return path
	return (Path(workdir) / path).resolve()