"""The agent seam — one narrow contract every edge agent implements.

Narrow on purpose. An agent gets a READ view of one edge and answers questions; it cannot
record, seal or send, because those are the record path and a model does not belong in it.
The seam is a class rather than a protocol handshake so that adding an agent stays what
adding an adapter is: a small declarative thing, not a code path through the edge.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any


@dataclass(frozen=True)
class AgentContext:
    """Everything an agent may see. Note what is absent: no ERP connection, no document
    store, no credentials, no write handle. An agent that needs more than this is asking for
    something the design refuses to give it."""

    edge: Any                          # the NEOedge itself — read methods only, by convention
    member_label: str = ""
    disclosure_policy: str = "structure-only"   # structure-only | structure-and-bands


class EdgeAgent:
    """Base class. Subclasses answer `ask()`; that is the whole contract.

    `capabilities()` exists so a caller can show a company exactly what its own agent is able
    to answer BEFORE switching it on — consent is worth little if it is consent to an
    unspecified surface."""

    name: str = "agent"
    description: str = ""

    def __init__(self, context: AgentContext) -> None:
        self.context = context

    @property
    def edge(self) -> Any:
        return self.context.edge

    def capabilities(self) -> list[dict]:
        """[{question, returns}] — what this agent can be asked, in plain words."""
        raise NotImplementedError

    def ask(self, question: str, **kwargs: Any) -> dict:
        """Answer one question about this edge. Never raises for an unknown question —
        returns `{"answered": False, ...}`, because an agent that throws at the edge of its
        competence is worse than one that says it does not know."""
        raise NotImplementedError

    # ── shared guard ─────────────────────────────────────────────────────────────────────

    def _refuse(self, why: str) -> dict:
        return {"answered": False, "why": why, "agent": self.name}
