# Copyright (c) 2024-2026 YuyAI / InsAIts Team. All rights reserved.
# Proprietary and confidential. See LICENSE.premium for terms.
"""
InsAIts Settlement Detector - Premium Core
============================================
Determines when a tool result has been "consumed and acted on" -- the key
signal that it is safe to compress to a 1-line summary.

A result is settled when:
  - A subsequent message references it AND indicates a decision was made
  - OR the task it belonged to is marked complete
  - OR a newer result supersedes it (same file, same agent, newer timestamp)

Author: Cristi Bogdan -- Steddy Nova SRL / YuyAI
"""

from __future__ import annotations

import re
from typing import Any, Dict, List, Optional, Set


# Signals that a result has been acted on and is settled
_SETTLEMENT_SIGNALS: List[re.Pattern] = [
    re.compile(r"\b(?:proceeding|proceeded)\s+(?:with|to)\b", re.I),
    re.compile(r"\b(?:confirmed|done|complete[d]?|finished|resolved)\b", re.I),
    re.compile(r"\bnext\s+step\b", re.I),
    re.compile(r"\bmoving\s+(?:on|forward)\b", re.I),
    re.compile(r"\b(?:implemented|applied|fixed|merged|committed)\b", re.I),
    re.compile(r"\b(?:verified|validated|passes|passing|all\s+.*pass)\b", re.I),
    re.compile(r"\bSyntax\s+OK\b", re.I),
    re.compile(r"\bOK:\b"),
    re.compile(r"\bcorrect\b", re.I),
    re.compile(r"\b(?:shipped|deployed|pushed|published)\b", re.I),
]

# Anti-settlement signals -- block compression even if settlement signals present
_ANTI_SETTLEMENT_SIGNALS: List[re.Pattern] = [
    re.compile(r"\b(?:retry|retrying|retried)\b", re.I),
    re.compile(r"\b(?:failed\s+again|still\s+failing)\b", re.I),
    re.compile(r"\b(?:not\s+working|broken|bug|error)\b", re.I),
    re.compile(r"\brevisit\b", re.I),
    re.compile(r"\b(?:TODO|FIXME|HACK|BLOCKED|WAITING)\b"),
    re.compile(r"\b(?:unresolved|pending|in.progress)\b", re.I),
    re.compile(r"\b(?:investigate|debug|diagnose)\b", re.I),
]

# Tool types that produce file-specific results (superseded by newer same-file reads)
_FILE_TOOLS: Set[str] = {"Read", "Edit", "Write", "Glob"}
_COMMAND_TOOLS: Set[str] = {"Bash"}


class SettlementDetector:
    """Determines if a tool result has been consumed and acted on.

    Examines the message history after a given tool result to find
    settlement or anti-settlement signals.
    """

    def __init__(
        self,
        lookback_window: int = 10,
    ) -> None:
        """
        Args:
            lookback_window: Number of subsequent messages to scan
                for settlement signals after the target message.
        """
        self._lookback = lookback_window

    def is_settled(
        self,
        message_idx: int,
        messages: List[Dict[str, Any]],
    ) -> bool:
        """Check if the message at message_idx has been settled.

        Examines subsequent messages for settlement and anti-settlement
        signals. Anti-settlement takes priority.

        Args:
            message_idx: Index of the message to check in the messages list.
            messages: Full ordered message history.

        Returns:
            True if the message is settled (safe to compress).
        """
        if message_idx < 0 or message_idx >= len(messages):
            return False

        target = messages[message_idx]
        target_tool = target.get("tool", "")
        target_agent = target.get("model", target.get("agent", ""))
        target_file = self._extract_file_path(target)

        # Scan subsequent messages within lookback window
        end_idx = min(message_idx + self._lookback + 1, len(messages))
        subsequent = messages[message_idx + 1 : end_idx]

        if not subsequent:
            return False

        has_settlement = False
        has_anti_settlement = False

        for msg in subsequent:
            text = self._get_text(msg)
            if not text:
                continue

            # Check anti-settlement first (takes priority)
            for pattern in _ANTI_SETTLEMENT_SIGNALS:
                if pattern.search(text):
                    has_anti_settlement = True
                    break

            if has_anti_settlement:
                break

            # Check settlement signals
            for pattern in _SETTLEMENT_SIGNALS:
                if pattern.search(text):
                    has_settlement = True
                    break

            # Check for superseding: newer result on same file by same agent
            if target_file and target_tool in _FILE_TOOLS:
                msg_file = self._extract_file_path(msg)
                msg_agent = msg.get("model", msg.get("agent", ""))
                if (
                    msg_file
                    and msg_file == target_file
                    and msg_agent == target_agent
                    and msg.get("tool", "") in _FILE_TOOLS
                ):
                    has_settlement = True

        if has_anti_settlement:
            return False

        return has_settlement

    def is_duplicate(
        self,
        message_idx: int,
        messages: List[Dict[str, Any]],
        similarity_threshold: float = 0.85,
    ) -> Optional[int]:
        """Check if this message is a near-duplicate of a later message.

        Returns the index of the newer duplicate if found, None otherwise.
        Uses simple token overlap for speed (no embeddings).
        """
        target = messages[message_idx]
        target_text = self._get_text(target)
        target_agent = target.get("model", target.get("agent", ""))

        if not target_text or len(target_text) < 20:
            return None

        target_tokens = set(target_text.lower().split())
        if len(target_tokens) < 5:
            return None

        # Only compare with messages from the same agent
        for i in range(message_idx + 1, len(messages)):
            other = messages[i]
            other_agent = other.get("model", other.get("agent", ""))
            if other_agent != target_agent:
                continue

            other_text = self._get_text(other)
            if not other_text or len(other_text) < 20:
                continue

            other_tokens = set(other_text.lower().split())
            if len(other_tokens) < 5:
                continue

            # Jaccard similarity
            intersection = target_tokens & other_tokens
            union = target_tokens | other_tokens
            similarity = len(intersection) / len(union) if union else 0

            if similarity >= similarity_threshold:
                return i  # newer message supersedes

        return None

    def find_settled_indices(
        self,
        messages: List[Dict[str, Any]],
        start_idx: int = 0,
        min_age: int = 15,
    ) -> List[int]:
        """Find all settled message indices in the history.

        Args:
            messages: Full message history.
            start_idx: Start scanning from this index.
            min_age: Minimum number of messages after the target
                before it can be considered for settlement.

        Returns:
            List of indices that are settled.
        """
        settled = []
        max_check = len(messages) - min_age
        for i in range(start_idx, max(0, max_check)):
            if self.is_settled(i, messages):
                settled.append(i)
        return settled

    @staticmethod
    def _extract_file_path(msg: Dict[str, Any]) -> str:
        """Extract file path from a message's tool input if present."""
        tool_input = msg.get("tool_input", {})
        if isinstance(tool_input, dict):
            return tool_input.get("file_path", "") or tool_input.get("path", "")
        return ""

    @staticmethod
    def _get_text(msg: Dict[str, Any]) -> str:
        """Get text content from a message for analysis."""
        # Try multiple fields where text might be
        for field in ("text", "content", "description", "result", "output"):
            val = msg.get(field, "")
            if isinstance(val, str) and val:
                return val
        # For anomalies, combine descriptions
        anomalies = msg.get("anomalies", [])
        if isinstance(anomalies, list):
            descs = [
                a.get("description", "")
                for a in anomalies
                if isinstance(a, dict)
            ]
            if descs:
                return " ".join(descs)
        return ""
