#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""P32/P33 neuroscience scoring extensions for brain search.

  - Item 131: Attention blink (P32)
  - Item 133: Proactive control (P32)
  - Item 134: Memory competition (P32)
  - Item 140: Search result provenance chain (P33)
"""
import logging
import re
from typing import Dict, List, Any

log = logging.getLogger("pulse.brain_search.search_scoring_p32")

# ---------------------------------------------------------------------------
# Item 131: Attention blink -- suppress results after high-salience item (P32)
# ---------------------------------------------------------------------------
_ATTENTION_BLINK_SALIENCE_THRESHOLD = 5.0
_ATTENTION_BLINK_PENALTY = 0.30
_ATTENTION_BLINK_WINDOW = 2  # Next N items after a high-salience item are suppressed


def apply_attention_blink(results: Dict[str, List[Any]]) -> None:
    """After a very high-salience item (>5.0) in results, the next 2 items in the
    same source get 30% confidence reduction. Models the attentional blink -- after
    processing something intense, you briefly miss what follows.
    """
    try:
        for source_name, hits in results.items():
            if not isinstance(hits, list) or len(hits) < 2:
                continue
            blink_countdown = 0
            for h in hits:
                if blink_countdown > 0:
                    h["confidence"] = max(0.05, h.get("confidence", 0.5) * (1.0 - _ATTENTION_BLINK_PENALTY))
                    h["_attention_blink"] = True
                    blink_countdown -= 1
                sal = float(h.get("salience", 1.0) or 1.0)
                if sal > _ATTENTION_BLINK_SALIENCE_THRESHOLD:
                    blink_countdown = _ATTENTION_BLINK_WINDOW
    except Exception as e:
        log.debug("apply_attention_blink failed: %s", e)


# ---------------------------------------------------------------------------
# Item 134: Memory competition -- similar items suppress each other (P32)
# ---------------------------------------------------------------------------
_MEMORY_COMPETITION_SIMILARITY_THRESHOLD = 0.7
_MEMORY_COMPETITION_PENALTY = 0.15


def apply_memory_competition(results: Dict[str, List[Any]]) -> None:
    """Cluster-based memory competition: group by (domain, source), sort by confidence
    descending within each cluster, apply graduated penalty * (1 - 0.1 * (rank-1))
    capped at 50% suppression. O(n log n) vs the prior O(n²) pairwise approach.

    N43: Replaces O(n²) pairwise text_similarity scan with domain-cluster suppression.
    Items in crowded clusters get graduated penalties; top-ranked item is unpenalised.
    """
    try:
        all_items: List[Any] = []
        for hits in results.values():
            if isinstance(hits, list):
                all_items.extend(hits)
        if len(all_items) < 2:
            return

        # Group items into clusters keyed by (domain, source/agent)
        clusters: Dict[tuple, List[Any]] = {}
        for item in all_items:
            domain = item.get("domain", "") or ""
            source = item.get("agent", "") or item.get("source", "") or ""
            key = (domain, source)
            clusters.setdefault(key, []).append(item)

        # Within each cluster, sort confidence desc and apply graduated suppression
        _MAX_SUPPRESSION = 0.5  # Never suppress more than 50%
        for cluster_items in clusters.values():
            if len(cluster_items) < 2:
                continue
            # Sort in-place by confidence descending (rank 0 = winner)
            cluster_items.sort(key=lambda x: x.get("confidence", 0.5), reverse=True)
            for rank, item in enumerate(cluster_items):
                if rank == 0:
                    continue  # Winner is unpenalised
                penalty_fraction = min(_MAX_SUPPRESSION, 0.1 * rank)
                item["confidence"] = max(
                    0.05,
                    item.get("confidence", 0.5) * (1.0 - penalty_fraction)
                )
                item["_memory_competition"] = True
                item["_competition_rank"] = rank
    except Exception as e:
        log.debug("apply_memory_competition failed: %s", e)


# ---------------------------------------------------------------------------
# Item 140: Search result provenance chain (P33)
# ---------------------------------------------------------------------------
def apply_provenance_chain(results: Dict[str, List[Any]]) -> None:
    """For each search result, trace its provenance: who created it, when, what task.
    Surface this as _provenance_chain metadata so agents can evaluate source quality.
    """
    try:
        for source_name, hits in results.items():
            if not isinstance(hits, list):
                continue
            for h in hits:
                chain: Dict[str, Any] = {
                    "source": source_name,
                    "item_id": h.get("id", ""),
                    "agent": h.get("agent", ""),
                    "timestamp": h.get("timestamp", ""),
                    "domain": h.get("domain", ""),
                    "type": h.get("type", ""),
                }
                # Enrich with existing _provenance if present
                existing_prov = h.get("_provenance", {})
                if existing_prov and isinstance(existing_prov, dict):
                    chain["task_id"] = existing_prov.get("task_id", "")
                    chain["saving_agent"] = existing_prov.get("saving_agent", "")
                    chain["session_id"] = existing_prov.get("session_id", "")
                # Try to look up from provenance store
                fp = h.get("fingerprint", "")
                if fp and not chain.get("task_id"):
                    try:
                        from pulse.brain.integrity.provenance import get_provenance
                        prov_data = get_provenance(fp)
                        if prov_data and isinstance(prov_data, dict):
                            chain["task_id"] = prov_data.get("task_id", "")
                            chain["saving_agent"] = prov_data.get("agent", "")
                    except Exception:
                        pass
                h["_provenance_chain"] = chain
    except Exception as e:
        log.debug("apply_provenance_chain failed: %s", e)


# ---------------------------------------------------------------------------
# Item 133: Proactive control -- pre-filter by task goal (P32)
# ---------------------------------------------------------------------------
_GOAL_VERB_SOURCE_MAP: Dict[str, List[str]] = {
    "fix": ["fails", "failures", "anti_patterns"],
    "debug": ["fails", "failures", "anti_patterns"],
    "implement": ["wins", "patterns", "domains"],
    "add": ["wins", "patterns", "domains"],
    "build": ["wins", "patterns", "domains"],
    "test": ["patterns", "wins"],
    "deploy": ["patterns", "wins", "procedures"],
    "optimize": ["patterns", "wins", "facts"],
    "refactor": ["patterns", "wins", "anti_patterns"],
}
_GOAL_VERB_RE = re.compile(
    r"\b(fix|debug|implement|add|build|test|deploy|optimize|refactor)\b",
    re.IGNORECASE,
)
_PROACTIVE_BOOST = 0.15
_PROACTIVE_PENALTY = 0.10


def apply_proactive_goal_filter(results: Dict[str, Any], query: str) -> Dict[str, Any]:
    """Extract goal verb from query and boost/penalise sources accordingly.
    'fix' queries prioritise failures/anti_patterns; 'implement' queries prioritise wins.
    Fail-open: returns results unchanged if no goal verb found.
    """
    try:
        match = _GOAL_VERB_RE.search(query)
        if not match:
            return results
        verb = match.group(1).lower()
        priority_sources = _GOAL_VERB_SOURCE_MAP.get(verb, [])
        if not priority_sources:
            return results
        for source_name, hits in results.items():
            if not isinstance(hits, list):
                continue
            if source_name in priority_sources:
                for h in hits:
                    h["confidence"] = min(1.0, h.get("confidence", 0.5) + _PROACTIVE_BOOST)
                    h["_proactive_goal_boost"] = verb
            else:
                # Mild demotion of off-goal sources
                for h in hits:
                    h["confidence"] = max(0.05, h.get("confidence", 0.5) - _PROACTIVE_PENALTY)
                    h["_proactive_goal_penalty"] = verb
    except Exception as e:
        log.debug("apply_proactive_goal_filter failed: %s", e)
    return results


