"""Per-role execution parameters.

Roles differ by value, not by algorithm, so this is a value object rather than
a strategy. Owning the values here takes the final say back from the shell: the
dispatcher passed a global idle budget while each wrapper quietly overrode it
with its own `case "$role"` block.
"""
from __future__ import annotations

from dataclasses import dataclass

from .role import RoleCatalogError, normalize_role

# Implementation executor / verifier dispatches run whole build + test suites
# that legitimately emit nothing for minutes; the 600s cap reaped them mid-suite
# (observed: a silent jest + tsc build TERM'd at ~929s). This stays below the
# 1800s wall-clock polling cap so a genuine hang is still reaped first.
_BUILD_RUNNING_IDLE_SECONDS = 1500
_DEFAULT_IDLE_SECONDS = 600

_BUILD_RUNNING_ROLES = frozenset({"implementer", "verifier"})


@dataclass(frozen=True)
class WorkerRoleSpec:
    role: str
    idle_timeout_seconds: int


def role_spec(role: str) -> WorkerRoleSpec:
    canonical_role = _canonical_role_or_raw(role)
    seconds = (
        _BUILD_RUNNING_IDLE_SECONDS
        if canonical_role in _BUILD_RUNNING_ROLES
        else _DEFAULT_IDLE_SECONDS
    )
    return WorkerRoleSpec(role=canonical_role, idle_timeout_seconds=seconds)


def _canonical_role_or_raw(role: str) -> str:
    try:
        return normalize_role(role)
    except RoleCatalogError:
        return role
