#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Per-item scoring, filtering, and post-scoring enrichment for brain search.

Extracted from search_engine.py — all D/E/A/B dimensions, quality gate,
metamemory annotation, attention weighting, drift detection, spreading
activation, semantic priming, and deduplication live here.

P27/P28 extensions delegated to search_scoring_ext.py (items 113-119).
"""
import logging

try:
    from pulse.brain.brain_search.search_scoring_ext import (
        apply_urgency_boost,          # Item 114
        apply_semantic_satiation,     # Item 115
        apply_contextual_inhibition,  # Item 118
    )
except Exception:
    apply_urgency_boost = lambda results, query: None
    apply_semantic_satiation = lambda results: None
    apply_contextual_inhibition = lambda results, query: None

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

# _score_item extracted to search_scoring_core for Law 7 SoC — re-export for callers
from pulse.brain.brain_search.search_scoring_core import _score_item, build_scoring_cache  # noqa: F401, E402


def _load_contradiction_texts():
    """Return set of lowered contradiction texts (unresolved)."""
    try:
        from pulse.admin.audit.truth_verifier import get_registry
        out = set()
        for c in get_registry().get("contradictions", []):
            if not c.get("resolution"):
                for s in ("item_a", "item_b"):
                    out.add(c[s].get("text", "")[:80].lower())
        return out
    except Exception as e:
        log.debug("Contradiction registry load failed: %s", e)
        return set()


def _load_trust():
    """Return (get_trust_fn or None, trust_data dict)."""
    try:
        from pulse.admin.audit.agent_trust import get_trust, _load_trust as _lt
        return get_trust, _lt().get("agents", {})
    except Exception as e:
        log.debug("Agent trust load failed: %s", e)
        return None, {}


def _fetch_superseded_replacement(item: dict) -> dict | None:
    """Item 190: Fetch the replacement item for a superseded entry (fail-open)."""
    try:
        import json as _json
        meta = _json.loads(item.get("metadata") or "{}")
        new_id = meta.get("superseded_by", "")
        if not new_id:
            return None
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        for table in ("lessons", "failures", "patterns", "facts", "decisions"):
            row = conn.execute(
                f"SELECT id, content, title, domain, confidence, type, validity, timestamp FROM {table} WHERE id=?",
                (new_id,)
            ).fetchone()
            if row:
                replacement = dict(row)
                replacement["_is_replacement_for"] = item.get("id", "")
                replacement["_replacement_source"] = table
                return replacement
    except Exception as e:
        log.debug("_fetch_superseded_replacement failed: %s", e)
    return None


def _apply_post_scoring(results, query, task_type):
    """Delegate to search_post_scoring (extracted for SoC)."""
    try:
        from pulse.brain.brain_search.search_post_scoring import apply_post_scoring_impl
        apply_post_scoring_impl(results, query, task_type,
                                apply_urgency_boost, apply_semantic_satiation,
                                apply_contextual_inhibition, log)
    except Exception as e:
        log.debug("_apply_post_scoring delegation failed: %s", e)
def score_and_filter_results(results, query, task_type, config, source_query_counts, agent_name=None):
    """Score, filter, and enrich all search results in-place. Returns results dict."""
    from pulse.core.temporal import temporal_confidence, temporal_salience
    from pulse.core.uncertainty import wilson_interval
    ctr_texts = _load_contradiction_texts()
    get_trust_fn, trust_data = _load_trust()
    try:  # Working memory: track session items for agent
        from pulse.brain.memory.working_memory import _add_to_session
        _all_hits = [h for src in results.values() if isinstance(src, list) for h in src]
        if agent_name and _all_hits:
            _add_to_session(agent_name, _all_hits[:10])
    except Exception:
        pass

    # Phase 4 perf: batch-load all per-item data before the scoring loop
    scoring_cache = build_scoring_cache()

    for source_name in list(results.keys()):
        hits = results[source_name]
        if not isinstance(hits, list):
            continue
        enriched = []
        for h in hits:
            scored = _score_item(
                h, source_name, query, task_type, ctr_texts,
                get_trust_fn, trust_data, source_query_counts,
                temporal_confidence, temporal_salience, wilson_interval,
                scoring_cache=scoring_cache,
            )
            if scored is not None:
                enriched.append(scored)
                # Item 190: Surface the replacement alongside a superseded item
                try:
                    if scored.get("validity") == "superseded" or "[SUPERSEDED]" in scored.get("title", ""):
                        replacement = _fetch_superseded_replacement(scored)
                        if replacement is not None:
                            replacement["_superseded_chain"] = True
                            enriched.append(replacement)
                except Exception:
                    pass
        enriched.sort(key=lambda x: x.get("_wilson_lower", 0), reverse=True)
        # Item 108: Retrieval interference scoring — same-domain competitor suppresses lower-ranked items
        try:
            domain_seen = {}
            for h in enriched:
                dom = h.get("domain", "")
                if dom in domain_seen:
                    h["confidence"] *= 0.95  # Suppressed by same-domain competitor
                    h["_retrieval_interference"] = True
                else:
                    domain_seen[dom] = True
        except Exception:
            pass
        # Item 89: Confidence floor - never wipe a source entirely
        if not enriched and hits and isinstance(hits, list):
            best = max(hits, key=lambda x: x.get("confidence", 0))
            best["_below_gate"] = True
            enriched = [best]
        results[source_name] = enriched

    _apply_post_scoring(results, query, task_type)
    return results
