"""Types for coordinator/council delivery E2E live battery."""
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Literal

DeliverySeed = Literal[
    "coordinator_batch",
    "coordinator_block_child",
    "council_slash",
    "council_block_contrarian",
]
DeliveryPillar = Literal["coordinator", "council"]

GUARD_PREFIX = "[Kanban / recorded, not authoritative]"

_FAILURE_TERMINAL = frozenset({"blocked", "gave_up", "cancelled"})


@dataclass
class DeliveryCase:
    """Seed async work on a parent thread → wait terminal → assert delivery seam."""

    id: str
    pillar: DeliveryPillar
    seed: DeliverySeed
    # Coordinator: HTTP batch items (must include origin_thread_id at seed time).
    batch_items: list[dict[str, Any]] = field(default_factory=list)
    # Council: question frame for ``/council <frame>`` slash spawn.
    council_frame: str = ""
    expect_task_count: int = 2
    expect_tasks_done: int = 2
    # Minimum tasks in blocked/gave_up/cancelled (partial-failure / forced-fail paths).
    expect_tasks_failed: int = 0
    timeout_sec: float = 600.0
    poll_sec: float = 5.0
    delivery_poll_sec: float = 3.0
    # Delivery assertions (guard-marked user bubbles).
    max_guard_deliveries: int = 1
    require_delivery_banner_substrings: tuple[str, ...] = ()
    forbid_delivery_banner_substrings: tuple[str, ...] = ()
    require_delivery_body_substrings: tuple[str, ...] = ()
    # Mid-run: assert no guard delivery until every task is terminal (no early wake).
    assert_no_delivery_before_all_terminal: bool = False
    # Parent-thread lifecycle after delivery.
    expect_coordinator_inactive: bool = False
    expect_council_inactive: bool = False
    wait_parent_synthesis: bool = False
    synthesis_timeout_sec: float = 240.0
    # Monitor-only: the *delivery seam* is structurally exercised, but the happy-path
    # outcome depends on nondeterministic model content (a worker meeting acceptance,
    # a Verifier not rejecting). Such a case is run and reported, but a failure does
    # NOT fail the battery (does not affect the exit code) — it is observability, not
    # a gate. Structural failures still belong in non-monitor cases.
    monitor_only: bool = False
    notes: str = ""


@dataclass
class DeliveryCaseResult:
    case_id: str
    passed: bool
    elapsed_sec: float
    pillar: str = "coordinator"
    origin_thread_id: str = ""
    tenant: str = ""
    task_ids: list[str] = field(default_factory=list)
    terminal: list[dict[str, Any]] = field(default_factory=list)
    delivery_messages: list[str] = field(default_factory=list)
    failures: list[str] = field(default_factory=list)
    timed_out: bool = False
    skipped: bool = False
    skip_reason: str = ""
    monitor_only: bool = False
