"""v3.6 Runtime Harness — guard engine (evaluate orchestrator).

chair_authorization_id=CHAIR-AUTH-TASK-2703-V36-HARNESS-MVP-260528

Public API:
    evaluate(tool_name, tool_input, context) -> Decision

Decision TypedDict:
    decision: "ALLOW" | "DENY" | "HOLD_FOR_CHAIR"
    matched_rule: str | None   (None for ALLOW)
    reason: str | None         (None for ALLOW)
"""
from __future__ import annotations

from typing import Any, TypedDict, Optional

from .rules import ALL_RULES  # noqa


class Decision(TypedDict):
    decision: str          # "ALLOW" | "DENY" | "HOLD_FOR_CHAIR"
    matched_rule: Optional[str]   # None for ALLOW
    reason: Optional[str]         # None for ALLOW


def evaluate(
    tool_name: Any,
    tool_input: Any,
    context: Any,
) -> Decision:
    """Evaluate a tool call against all v36 harness rules.

    Args:
        tool_name: Name of the Claude Code tool (e.g. "Bash", "Write", "Edit").
        tool_input: The tool's input dict as received from the hook stdin.
        context: Additional context dict (task_id, session_id, new_code_files, etc.)

    Returns:
        Decision dict with decision, matched_rule, reason.

    Contract:
        - First matched rule wins (ordered by ALL_RULES list).
        - No match → ALLOW with matched_rule=None, reason=None.
        - Any exception in a rule → that rule returns None (rules are fail-safe).
        - The evaluate function itself is also fail-safe: any unhandled exception → ALLOW.
    """
    try:
        tool_name_str = str(tool_name or "")
        tool_input_dict = dict(tool_input) if isinstance(tool_input, dict) else {}
        context_dict = dict(context) if isinstance(context, dict) else {}

        for rule_fn in ALL_RULES:
            try:
                result = rule_fn(tool_name_str, tool_input_dict, context_dict)
            except Exception:
                # Individual rule failure → skip, don't block
                result = None

            if result is not None:
                decision, matched_rule, reason = result
                return Decision(
                    decision=decision,
                    matched_rule=matched_rule,
                    reason=reason,
                )

        # No rule matched → ALLOW
        return Decision(decision="ALLOW", matched_rule=None, reason=None)

    except Exception:
        # Global fail-safe: any crash → ALLOW (never block tool on harness error)
        return Decision(decision="ALLOW", matched_rule=None, reason=None)
