"""
Beads (bd) client: resolve binary path, check availability, run bd.

All callers must use is_available(project_root) before run_bd. run_bd raises
FileNotFoundError when bd cannot be resolved.
"""

from __future__ import annotations

import shutil
import subprocess
import sys
from pathlib import Path


def resolve_bd_path(project_root: Path) -> str | None:
    """
    Resolve the path to the bd binary.

    Prefer project-local tools/bd/bd.exe (Windows) or tools/bd/bd (Unix),
    then shutil.which("bd") on PATH.

    Args:
        project_root: Project root directory.

    Returns:
        Path to bd binary, or None if not found.
    """
    tools_bd = project_root / "tools" / "bd"
    if sys.platform == "win32":
        local = tools_bd / "bd.exe"
    else:
        local = tools_bd / "bd"
    if local.exists():
        return str(local)
    found = shutil.which("bd")
    return found


def is_available(project_root: Path) -> bool:
    """
    Return True if bd can be resolved for this project.

    Args:
        project_root: Project root directory.
    """
    return resolve_bd_path(project_root) is not None


def is_ready(project_root: Path) -> bool:
    """
    Return True if bd is available and the project has been initialized with bd.

    (project_root / ".beads").exists() indicates bd init has been run.

    Args:
        project_root: Project root directory.
    """
    return is_available(project_root) and (project_root / ".beads").exists()


BEADS_REQUIRED_REMEDIATION = (
    "Install bd to tools/bd or add bd to PATH, run 'bd init' or 'bd init --stealth', "
    "then 'bd doctor --fix'. Or set beads.required: false in .tapps-agents/config.yaml. "
    "See docs/BEADS_INTEGRATION.md."
)


class BeadsRequiredError(Exception):
    """Raised when beads.required is true but bd is unavailable or not initialized."""

    def __init__(self, message: str, remediation: str = BEADS_REQUIRED_REMEDIATION) -> None:
        super().__init__(f"{message}\n{remediation}")
        self.remediation = remediation


def require_beads(config: object, project_root: Path) -> None:
    """
    Ensure Beads is available when required. No-op when beads.enabled or beads.required is false.

    Args:
        config: ProjectConfig (or object with config.beads.enabled and config.beads.required).
        project_root: Project root directory.

    Raises:
        BeadsRequiredError: When beads.enabled and beads.required but bd unavailable or .beads missing.
    """
    beads = getattr(config, "beads", None)
    if beads is None:
        return
    if not getattr(beads, "enabled", False) or not getattr(beads, "required", False):
        return
    if not is_available(project_root):
        raise BeadsRequiredError(
            "Beads (bd) is required but bd was not found.",
            remediation=BEADS_REQUIRED_REMEDIATION,
        )
    if not is_ready(project_root):
        raise BeadsRequiredError(
            "Beads (bd) is required but project is not initialized. Run 'bd init' or 'bd init --stealth'.",
            remediation=BEADS_REQUIRED_REMEDIATION,
        )


def run_bd(
    project_root: Path,
    args: list[str],
    cwd: Path | None = None,
    *,
    capture_output: bool = True,
) -> subprocess.CompletedProcess:
    """
    Run bd with the given arguments.

    Args:
        project_root: Project root (used to resolve bd and as cwd when cwd is None).
        args: Arguments to pass to bd (e.g. ["ready"], ["create", "Title", "-p", "0"]).
        cwd: Working directory; defaults to project_root.
        capture_output: If True, capture stdout/stderr; if False, inherit. Default True.

    Returns:
        subprocess.CompletedProcess. Check .returncode.

    Raises:
        FileNotFoundError: If bd cannot be resolved (use is_available first).
    """
    path = resolve_bd_path(project_root)
    if path is None:
        raise FileNotFoundError(
            "bd not found. Install to tools/bd or add bd to PATH. See docs/BEADS_INTEGRATION.md."
        )
    workdir = cwd if cwd is not None else project_root
    return subprocess.run(
        [path, *args],
        cwd=workdir,
        capture_output=capture_output,
        text=True,
        encoding="utf-8",
        errors="replace",
    )
