"""Agent adapter protocol. Subprocess CLI adapters, not SDK bindings — ADR-0003."""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Protocol


@dataclass
class TrialTelemetry:
    """Normalized contract every adapter must fill. `cost_usd=None` means the
    agent's CLI does not report cost (documented, not fabricated — see the
    Codex note in docs/03-architecture.md); it is never silently coerced to 0."""

    input_tokens: int = 0
    output_tokens: int = 0
    cache_read: int = 0
    cache_write: int = 0
    cost_usd: float | None = None
    turns: int = 0
    tool_calls: int = 0
    session_id: str | None = None
    #: What the agent reports it actually ran. Preferred over the requested
    #: model when present: a silent provider-side substitution would otherwise
    #: let one comparison mix two models without anyone noticing.
    model: str | None = None
    exit_status: int = 0
    raw_result: str = ""  # the agent's final message / summary
    error: str | None = None


@dataclass
class PreflightResult:
    ok: bool
    version: str | None = None
    detail: str = ""


@dataclass
class TrialSpec:
    """Everything an adapter needs to build one invocation."""

    prompt: str
    cwd: Path
    config_variant_text: str | None
    config_path: str = "CLAUDE.md"
    max_turns: int = 40
    timeout_s: int = 600
    #: None leaves the choice to the agent CLI's own default.
    model: str | None = None
    allowed_tools: tuple[str, ...] = ("Bash", "Read", "Edit", "Write", "Glob", "Grep")


class AgentAdapter(Protocol):
    name: str

    def preflight(self) -> PreflightResult:
        """Is the binary present, and what version? Called once before a run,
        not per trial."""
        ...

    def build_command(self, spec: TrialSpec) -> list[str]:
        """The subprocess argv. `cwd=spec.cwd` is the universal mechanism for
        directory scoping — never rely on a --cd/-C flag, since Codex doesn't
        document one (docs/03-architecture.md)."""
        ...

    def invoke(self, spec: TrialSpec) -> TrialTelemetry:
        """Run the adapter's command and return normalized telemetry. Owns
        the subprocess call so it can stream/parse output incrementally
        rather than buffering the whole thing (needed for --stream-json
        variants later); the default implementation here just buffers."""
        ...
