"""Claude CLI headless transport — the OAuth lane that actually works.

The SDK ingress (`/v1/messages` + `anthropic-beta: oauth-2025-04-20`) applies
an opaque per-model-tier throttle that 429s opus/sonnet/fable while the
Claude Code CLI lane — the same subscription, different ingress — runs
uninterrupted (live-proven 2026-06-11: 16 harness phases + 3 consult rounds
on the CLI lane during hours of SDK-lane 429s; see memory
reference-claude-oauth-token-studio ADDENDUM).

This module is the CLI-lane sibling of :mod:`recoil.core.anthropic_client`.
Callers that need text (or image-grounded) completions during SDK-lane
outages route here. Pattern copied from the proven invocations:
``harness_orchestrator.sh:481`` and ``consult.py:604``.

Transport selection convention for callers:
    RECOIL_CLAUDE_TRANSPORT = "cli" (default) | "sdk"
"""
from __future__ import annotations

import os
import subprocess
from pathlib import Path


class ClaudeCliError(RuntimeError):
    """Raised when the claude CLI call fails or returns empty output."""


def claude_transport() -> str:
    """Resolve the transport for production Claude calls. Fail-loud on junk."""
    value = os.environ.get("RECOIL_CLAUDE_TRANSPORT", "cli").strip().lower()
    if value not in ("cli", "sdk"):
        raise ValueError(
            f"RECOIL_CLAUDE_TRANSPORT must be 'cli' or 'sdk', got {value!r}"
        )
    return value


def claude_cli_call(
    prompt: str,
    images: list[Path] | None = None,
    *,
    system_prompt: str | None = None,
    model: str | None = None,
    timeout_s: int = 900,
) -> str:
    """Run one headless `claude -p` completion and return stdout text.

    Images are passed as file paths the agent is instructed to Read (the
    CLI's Read tool renders images natively). The caller owns putting image
    bytes on disk first.
    """

    sections: list[str] = []
    if system_prompt:
        sections.append(system_prompt)
    if images:
        lines = ["Before answering, use the Read tool to view these images IN ORDER:"]
        for ordinal, path in enumerate(images, start=1):
            lines.append(f"image {ordinal}: {path}")
        sections.append("\n".join(lines))
    sections.append(prompt)
    full_prompt = "\n\n".join(sections)

    cmd = [
        "claude",
        "-p",
        full_prompt,
        "--permission-mode",
        "bypassPermissions",
    ]
    if model:
        cmd.extend(["--model", model])

    try:
        proc = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout_s,
        )
    except FileNotFoundError as exc:
        raise ClaudeCliError("claude CLI not found on PATH") from exc
    except subprocess.TimeoutExpired as exc:
        raise ClaudeCliError(f"claude CLI timed out after {timeout_s}s") from exc

    if proc.returncode != 0:
        stderr_tail = (proc.stderr or "")[-500:]
        raise ClaudeCliError(
            f"claude CLI exited {proc.returncode}: {stderr_tail}"
        )
    out = (proc.stdout or "").strip()
    if not out:
        raise ClaudeCliError("claude CLI returned empty output")
    return out


__all__ = ["ClaudeCliError", "claude_cli_call", "claude_transport"]
