"""Public host-API surface tests. See SPEC.md §12.1.

The top-level `attractor` package promises a stable in-process surface for
out-of-tree orchestrators. These tests pin that promise:

1. Every name in `attractor.__all__` is actually defined on the module.
2. Each re-exported symbol is identity-equal to the canonical submodule
   symbol — guards against accidental rebinding or shadowing.
3. A minimum-viable surface (Engine, RunStatus, parse/validate, the
   event union and error base) is present; removing one of these
   counts as a breaking change and should require an explicit decision.

The test deliberately avoids enumerating *every* expected name — that
would just duplicate `__all__` — but it does pin the floor.
"""

# [unit->REQ-API-IN-PROCESS]

from __future__ import annotations

import attractor
from attractor import engine as engine_mod
from attractor import workflow as workflow_mod
from attractor import worktree as worktree_mod


def test_all_names_resolve() -> None:
    """Every entry in __all__ corresponds to an attribute on the package."""
    for name in attractor.__all__:
        assert hasattr(attractor, name), f"{name} listed in __all__ but missing"


def test_engine_reexports_identity() -> None:
    """Engine-namespace re-exports are the same objects as the submodule's."""
    engine_names = {
        "AbortNotPaused",
        "Aborted",
        "AgentToolUse",
        "Checkpointed",
        "Engine",
        "EngineError",
        "EngineEvent",
        "EventCallback",
        "HumanGate",
        "NodeOutcome",
        "NodeRecord",
        "NodeStarted",
        "NotPausedAtGate",
        "RunCompleted",
        "RunHandle",
        "RunSession",
        "RunStarted",
        "RunStatus",
        "RunSummary",
        "UnknownChoice",
        "UnknownRun",
        "WorkflowNotValid",
    }
    for name in engine_names:
        assert getattr(attractor, name) is getattr(engine_mod, name), (
            f"attractor.{name} is not the same object as attractor.engine.{name}"
        )


def test_workflow_reexports_identity() -> None:
    """Workflow-loader re-exports match the workflow submodule."""
    for name in ("ParseError", "ValidGraph", "ValidationFailed", "parse", "validate"):
        assert getattr(attractor, name) is getattr(workflow_mod, name), (
            f"attractor.{name} is not the same object as attractor.workflow.{name}"
        )


def test_worktree_reexports_identity() -> None:
    """InputCopy comes through from the worktree submodule."""
    assert attractor.InputCopy is worktree_mod.InputCopy


def test_minimum_viable_surface_present() -> None:
    """A host MUST be able to reach these names — they're the floor.

    If any of these names disappear, that is a breaking change to the
    host API and must be an explicit decision, not an accident.
    """
    required = {
        "Engine",
        "EngineError",
        "EngineEvent",
        "EventCallback",
        "HumanGate",
        "InputCopy",
        "ParseError",
        "RunSession",
        "RunStatus",
        "ValidGraph",
        "ValidationFailed",
        "parse",
        "validate",
    }
    missing = required - set(attractor.__all__)
    assert not missing, f"host API floor is missing names: {sorted(missing)}"


def test_version_is_string() -> None:
    """__version__ is exposed and is a non-empty string."""
    assert isinstance(attractor.__version__, str)
    assert attractor.__version__
