"""Compilar y ejecutar un juego vía subprocess."""

from __future__ import annotations

import os
import subprocess
from collections.abc import Callable
from pathlib import Path

from .config import Game
from .paths import repo_dir

LogFn = Callable[[str], None]

# Directorios habituales de herramientas de línea de comandos (Homebrew, MacPorts…).
# Una .app de macOS lanzada desde Finder/Dock NO hereda el PATH del shell de login,
# así que herramientas como cmake, instaladas con Homebrew en /opt/homebrew/bin
# (Apple Silicon) o /usr/local/bin (Intel), quedan fuera del PATH y `make` no las
# encuentra. Las añadimos explícitamente. En Linux estos paths no existen y se
# filtran solos.
_EXTRA_PATH_DIRS = (
    "/opt/homebrew/bin",
    "/opt/homebrew/sbin",
    "/usr/local/bin",
    "/usr/local/sbin",
    "/opt/local/bin",
)


def _noop(_: str) -> None:
    pass


def _launch_env() -> dict[str, str]:
    """Entorno para los subprocesos con un PATH que incluye los directorios de
    herramientas habituales aunque la app se lance desde Finder/Dock."""
    env = os.environ.copy()
    parts = env.get("PATH", "").split(os.pathsep)
    parts = [p for p in parts if p]
    for d in _EXTRA_PATH_DIRS:
        if d not in parts and os.path.isdir(d):
            parts.append(d)
    env["PATH"] = os.pathsep.join(parts)
    return env


def _stream(cmd: str, cwd: Path, log: LogFn) -> int:
    """Ejecuta un comando de shell en cwd, retransmitiendo stdout/err línea a línea."""
    log(f"$ {cmd}  (cwd={cwd})")
    proc = subprocess.Popen(
        cmd,
        cwd=str(cwd),
        shell=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True,
        bufsize=1,
        env=_launch_env(),
    )
    assert proc.stdout is not None
    for line in proc.stdout:
        log(line.rstrip())
    return proc.wait()


def run_game(root: Path, game: Game, log: LogFn = _noop) -> int:
    """Compila (si hay build_cmd) y ejecuta el juego. Devuelve el código de salida.

    Si build_cmd falla, aborta sin ejecutar. Lanza FileNotFoundError si el repo
    no está clonado.
    """
    repo = repo_dir(root, game.id)
    if not (repo / ".git").exists():
        raise FileNotFoundError(
            f"{game.name} no està descarregat. Prem Descarrega primer."
        )

    if game.build_cmd.strip():
        log(f"Compilant {game.name}…")
        code = _stream(game.build_cmd, repo, log)
        if code != 0:
            log(f"La compilació ha fallat (codi {code}). No s'executa.")
            return code

    log(f"Executant {game.name}…")
    return _stream(game.run_cmd, repo, log)
