"""
Heuristic reward computation for evolution trajectories.

Computes a reward score for each trajectory based on tool usage
patterns and outcomes. Called during GRPO dataset build, not at
logging time, so reward logic can evolve without reprocessing.
"""
from __future__ import annotations

_RESEARCH_REPORT_TOOL = "generate_final_research_report"
_ERROR_PREFIX = "Error:"

_POSITIVE_TOOLS = frozenset({
    "search_web", "search_web_public", "read_webpage",
    "deep_web_search", "read_and_analyze_page",
})
_GROWTH_TOOLS = frozenset({"create_plugin", "create_dashboard_plugin", "resolve_drive", "create_drive"})


def compute_reward(trajectory: dict) -> float:
    """Compute a heuristic reward in [-1.0, 1.0] for a single trajectory."""
    messages = trajectory.get("messages") or []
    final_reply = (trajectory.get("final_reply") or "").strip()

    reward = 0.0

    tool_calls_seen = 0
    tool_errors = 0
    tool_successes = 0
    research_report_called = False
    skill_created = False
    plugin_created = False
    drive_resolved = False
    drive_created = False
    drive_dismissed = False
    drive_dismiss_reason_good = False
    tool_names_seen: set[str] = set()

    for m in messages:
        if not isinstance(m, dict):
            continue
        role = m.get("role", "")

        if role == "assistant":
            for tc in m.get("tool_calls") or []:
                fn = tc.get("function") if isinstance(tc, dict) else None
                if not isinstance(fn, dict):
                    fn = tc
                name = fn.get("name", "") if isinstance(fn, dict) else ""
                if name == _RESEARCH_REPORT_TOOL:
                    research_report_called = True
                if name:
                    tool_calls_seen += 1
                    tool_names_seen.add(name)
                if name in {"create_plugin", "create_dashboard_plugin"}:
                    plugin_created = True
                if name == "resolve_drive":
                    drive_resolved = True
                if name == "create_drive":
                    drive_created = True
                if name == "dismiss_drive":
                    drive_dismissed = True
                    args_raw = fn.get("arguments", "") if isinstance(fn, dict) else ""
                    if isinstance(args_raw, str):
                        if "reason" in args_raw and len(args_raw) > 40:
                            drive_dismiss_reason_good = True
                if name == "skill_manage":
                    args_raw = fn.get("arguments", "") if isinstance(fn, dict) else ""
                    if isinstance(args_raw, str) and '"action"' in args_raw and "create" in args_raw:
                        skill_created = True

        if role == "tool":
            content = (m.get("content") or "").strip()
            if content.startswith(_ERROR_PREFIX):
                tool_errors += 1
            elif content and len(content) > 20:
                tool_successes += 1

    if research_report_called and final_reply:
        reward += 1.0

    if skill_created and tool_errors == 0:
        reward += 0.8
    if plugin_created and tool_errors == 0:
        reward += 0.8
    if drive_resolved:
        reward += 0.5
    if drive_created:
        reward += 0.3

    # Lightweight novelty proxy: many distinct tools in one successful run.
    if len(tool_names_seen) >= 4 and tool_errors == 0:
        reward += 0.2

    if drive_dismissed and not drive_dismiss_reason_good:
        reward -= 0.2

    if tool_successes >= 3:
        reward += 0.3

    if tool_successes > 0 and tool_errors == 0:
        reward += 0.5
    elif tool_errors > 0:
        reward -= 0.5 * min(tool_errors, 3)

    if not final_reply and tool_calls_seen > 0:
        reward -= 0.3

    return max(-1.0, min(1.0, reward))
