from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Protocol

from dual_agent.domain import FixArtifact, ImplementationArtifact, PlanArtifact, ReviewArtifact


Artifact = PlanArtifact | ImplementationArtifact | ReviewArtifact | FixArtifact


@dataclass(frozen=True)
class AgentRequest:
    run_id: str
    stage: str
    worktree_path: Path
    goal: str
    handoff: str = ""
    #: Provider session to continue instead of starting cold. None starts a fresh session.
    session_id: str | None = None
    #: Recorded at task creation; the prompt states it so the agent knows its starting point.
    baseline_commit: str | None = None
    #: User-supplied limits carried into every stage prompt.
    constraints: tuple[str, ...] = ()
    #: Operator messages from the conversation. Unlike handoff, these are instructions.
    guidance: str = ""


@dataclass(frozen=True)
class AgentResult:
    status: str
    artifact: Artifact | None = None
    error: str | None = None
    session_id: str | None = None
    #: Provider-reported usage. Never estimated; absent when the CLI does not report it.
    usage: dict | None = None
    #: Raw evidence of the call, section 5.1. Kept so nothing is judged from prose alone.
    exit_code: int | None = None
    stdout_path: Path | None = None
    stderr_path: Path | None = None
    duration_ms: int | None = None

    @property
    def error_category(self) -> str | None:
        """The design names this field on the result; it is the status whenever it failed."""
        return None if self.status == "SUCCESS" else self.status


class AgentAdapter(Protocol):
    def run(self, request: AgentRequest, on_start: Callable[[int], None] | None = None) -> AgentResult: ...


@dataclass(frozen=True)
class Reply:
    """A plain answer to a question. Never advances the state machine.

    The design's rule is that *state* moves only on validated JSON. Discussion happens
    before a task exists and changes nothing, so prose is the right shape here; the
    moment a discussion becomes work, it is converted back into a validated artifact.
    """

    text: str
    ok: bool = True
    error: str | None = None
    session_id: str | None = None
    usage: dict | None = None

