"""Shared approval policy helpers for TUI/WEB/VSCode runtimes."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

APPROVAL_POLICY_MANUAL = "manual"
APPROVAL_POLICY_READ_ONLY = "read_only"
APPROVAL_POLICY_SAFE_WRITE = "safe_write"
APPROVAL_POLICY_FULL_TRUSTED = "full_trusted"
APPROVAL_POLICY_PLAN_ONLY = "plan_only"

APPROVAL_POLICY_ALIASES: dict[str, str] = {
    APPROVAL_POLICY_MANUAL: APPROVAL_POLICY_MANUAL,
    "default": APPROVAL_POLICY_MANUAL,
    APPROVAL_POLICY_READ_ONLY: APPROVAL_POLICY_READ_ONLY,
    "readonly": APPROVAL_POLICY_READ_ONLY,
    "read-only": APPROVAL_POLICY_READ_ONLY,
    APPROVAL_POLICY_SAFE_WRITE: APPROVAL_POLICY_SAFE_WRITE,
    "safe-write": APPROVAL_POLICY_SAFE_WRITE,
    APPROVAL_POLICY_FULL_TRUSTED: APPROVAL_POLICY_FULL_TRUSTED,
    "full-trusted": APPROVAL_POLICY_FULL_TRUSTED,
    "trusted": APPROVAL_POLICY_FULL_TRUSTED,
    APPROVAL_POLICY_PLAN_ONLY: APPROVAL_POLICY_PLAN_ONLY,
    "plan-only": APPROVAL_POLICY_PLAN_ONLY,
    "plan": APPROVAL_POLICY_PLAN_ONLY,
}

APPROVAL_POLICIES: tuple[str, ...] = (
    APPROVAL_POLICY_MANUAL,
    APPROVAL_POLICY_READ_ONLY,
    APPROVAL_POLICY_SAFE_WRITE,
    APPROVAL_POLICY_FULL_TRUSTED,
    APPROVAL_POLICY_PLAN_ONLY,
)

# Only used for HITL interrupt decisions. Unknown tools default to reject for safety.
_READ_ONLY_ALLOWED_TOOLS: set[str] = {"web_search", "fetch_url"}
_PLAN_ONLY_AUTO_APPROVE: set[str] = {"web_search", "fetch_url"}
_SAFE_WRITE_ALLOWED_TOOLS: set[str] = {
    "web_search",
    "fetch_url",
    "write_file",
    "edit_file",
}


def normalize_approval_policy(value: str | None, *, default: str = APPROVAL_POLICY_MANUAL) -> str:
    """Normalize user/operator-provided approval policy values."""
    key = (value or "").strip().lower()
    if not key:
        return APPROVAL_POLICY_ALIASES.get(default, APPROVAL_POLICY_MANUAL)
    return APPROVAL_POLICY_ALIASES.get(key, APPROVAL_POLICY_ALIASES.get(default, APPROVAL_POLICY_MANUAL))


def is_valid_approval_policy(value: str | None) -> bool:
    """Return True if *value* resolves to a known policy alias."""
    key = (value or "").strip().lower()
    return key in APPROVAL_POLICY_ALIASES


def approval_policy_labels() -> list[str]:
    """Return user-facing policy labels for help text."""
    return [
        f"{APPROVAL_POLICY_MANUAL} (ask every time)",
        f"{APPROVAL_POLICY_READ_ONLY} (auto-allow read-only web fetch/search; reject writes/shell)",
        f"{APPROVAL_POLICY_SAFE_WRITE} (auto-allow web + write/edit; reject shell)",
        f"{APPROVAL_POLICY_FULL_TRUSTED} (auto-allow all interruptable actions)",
    ]


def extract_action_tool_name(action_request: dict[str, Any]) -> str:
    """Extract a normalized tool name from an HITL action request payload."""
    tool_name = (
        action_request.get("tool")
        or action_request.get("name")
        or (action_request.get("action") or {}).get("tool")
    )
    return str(tool_name or "").strip()


@dataclass(frozen=True)
class PolicyDecision:
    """Result for a single HITL action under a policy."""

    tool_name: str
    decision: str  # "approve" or "reject"


def decide_action(policy: str, action_request: dict[str, Any]) -> PolicyDecision | None:
    """Return a policy decision for one action or ``None`` for manual mode."""
    normalized = normalize_approval_policy(policy)
    tool_name = extract_action_tool_name(action_request)

    if normalized == APPROVAL_POLICY_MANUAL:
        return None

    if normalized == APPROVAL_POLICY_FULL_TRUSTED:
        return PolicyDecision(tool_name=tool_name, decision="approve")

    if normalized == APPROVAL_POLICY_READ_ONLY:
        decision = "approve" if tool_name in _READ_ONLY_ALLOWED_TOOLS else "reject"
        return PolicyDecision(tool_name=tool_name, decision=decision)

    if normalized == APPROVAL_POLICY_SAFE_WRITE:
        decision = "approve" if tool_name in _SAFE_WRITE_ALLOWED_TOOLS else "reject"
        return PolicyDecision(tool_name=tool_name, decision=decision)

    if normalized == APPROVAL_POLICY_PLAN_ONLY:
        if tool_name in _PLAN_ONLY_AUTO_APPROVE:
            return PolicyDecision(tool_name=tool_name, decision="approve")
        # write/edit/shell → show ApprovalMenu (manual decision)
        return None

    return None


def decide_actions(policy: str, action_requests: list[dict[str, Any]]) -> list[PolicyDecision] | None:
    """Return policy decisions for all actions or ``None`` if manual mode."""
    first = decide_action(policy, action_requests[0]) if action_requests else None
    if first is None:
        return None
    decisions = [first]
    for action_request in action_requests[1:]:
        # Non-manual policy always returns a concrete decision per action.
        decision = decide_action(policy, action_request)
        if decision is None:
            decision = PolicyDecision(
                tool_name=extract_action_tool_name(action_request),
                decision="reject",
            )
        decisions.append(decision)
    return decisions
