#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Brain scoring: Content-aware salience and confidence computation."""

import re
import math

# Keyword tiers for salience scoring
_SALIENCE_KEYWORDS = {
    "critical": (["never", "always", "must not", "crashes", "data loss", "security",
                  "corruption", "deadlock", "race condition", "destroys", "silent fail",
                  "zero-day", "vulnerability", "irreversible"], 7.0),
    "actionable": (["fix", "use instead", "switch to", "replace with", "root cause",
                    "because", "prevents", "caused by", "broke", "workaround",
                    "migrate", "upgrade", "downgrade", "revert"], 4.0),
    "standard": (["should", "prefer", "better to", "works", "confirmed",
                  "discovered", "learned", "tested", "verified", "refactored",
                  "improved", "reduced"], 2.0),
}


def compute_item_salience(description: str, priority: float = 1.0,
                          emotional_intensity: float = 0.0) -> float:
    """Content-aware salience scoring based on keyword tiers.

    Replaces the old priority*emul formula that clustered 83% of entries at 2.0.
    Tiers: critical=7.0, actionable=4.0, standard=2.0, ambient=1.0.
    Boosts: priority (0-4.5 for priority 1-10), emotional (0-1.5), specificity (0-0.5).
    Range: 1.0-10.0.

    Note: _compute_priority() returns 1-10. At priority=5 (default), boost=2.0.
    At priority=7 (high), boost=3.0. At priority=10 (max), boost=4.5.
    """
    desc_lower = description.lower()
    base = 1.0  # ambient default
    for _tier_name, (keywords, tier_base) in _SALIENCE_KEYWORDS.items():
        if any(kw in desc_lower for kw in keywords):
            base = tier_base
            break
    priority_boost = max(0, (priority - 1)) * 0.5  # 0-4.5 for priority 1-10
    emotional_boost = emotional_intensity * 1.5      # 0-1.5 for intensity 0-1
    word_count = len(description.split())
    specificity_boost = min(0.5, word_count / 40.0)  # longer = more specific

    # Phase 4+: Negativity bias — negative-valence items get safety boost
    valence_boost = 0.0
    try:
        from pulse.core.valence import compute_valence
        valence, _ = compute_valence(description)
        if valence < -0.3:
            valence_boost = 0.5  # Negative content gets safety salience boost
    except Exception:
        pass

    return min(10.0, round(base + priority_boost + emotional_boost + specificity_boost + valence_boost, 1))


def compute_item_confidence(description: str, source_type: str = "bridge", agent_name: str = "") -> float:
    """Content-aware initial confidence based on extraction quality and agent tier.

    Continuous formula replacing the 3-tier step function that clustered
    97% of values at 0.45/0.50/0.55.  Now produces a smooth distribution
    across 0.30-0.90 using: base + length curve + specificity signals.
    """
    # Wider base range: bridge starts lower (0.35) to allow more upward spread
    base = {"git": 0.85, "manual": 0.70, "scribe": 0.45, "bridge": 0.35}.get(source_type, 0.30)

    # QR2 P5: Agent-Aware Confidence Recalibration
    agent_lower = agent_name.lower()
    if "opus" in agent_lower:
        base = max(base, 0.75)  # Premium tier agents are highly trusted
    elif "sonnet" in agent_lower:
        base = max(base, 0.65)  # Standard tier agents
    elif "auto_capture" in agent_lower or "unknown" in agent_lower:
        base = min(base, 0.35)  # Raw capture logs are inherently untrusted until grounded

    desc_lower = description.lower()
    words = description.split()
    word_count = len(words)

    # Length curve: log growth 0 to ~0.20 (saturates ~80 words, wider range)
    length_boost = min(0.20, 0.20 * math.log1p(word_count) / math.log1p(80))

    # Vocabulary richness: unique/total ratio (0 to 0.08)
    unique_ratio = len(set(w.lower() for w in words)) / max(word_count, 1)
    richness_boost = 0.08 * unique_ratio

    # Specificity: domain keywords, failure signals, technical terms (wider boost)
    _FAILURE_WORDS = {"crash", "error", "fail", "broke", "bug", "timeout", "exception", "traceback"}
    _DOMAIN_WORDS = {"architecture", "security", "migration", "database", "schema",
                     "encryption", "sqlite", "api", "daemon", "pipeline", "deployment"}
    _TECHNICAL_WORDS = {"function", "class", "module", "import", "config", "parameter",
                        "threshold", "index", "query", "table", "column"}
    desc_words = set(desc_lower.split())
    failure_hits = len(desc_words & _FAILURE_WORDS)
    domain_hits = len(desc_words & _DOMAIN_WORDS)
    tech_hits = len(desc_words & _TECHNICAL_WORDS)
    specificity_boost = min(0.18, 0.04 * failure_hits + 0.04 * domain_hits + 0.02 * tech_hits)

    # Concrete references: file paths, numbers, function names (wider boost)
    has_path = 1 if re.search(r'[\w/\\]+\.\w{1,4}', description) else 0
    has_number = 1 if re.search(r'\b\d{2,}\b', description) else 0
    has_code = 1 if re.search(r'[a-z_]+\([^)]*\)', description) else 0
    concreteness_boost = 0.04 * has_path + 0.02 * has_number + 0.03 * has_code

    # Sentence structure: complete, well-formed entries get significant bonus;
    # fragments and conversation noise get penalized (QR7: widen dynamic range)
    has_terminal = description.rstrip()[-1:] in '.!?)' if description.strip() else False
    starts_upper = description[:1].isupper() if description else False
    is_long = word_count >= 8
    structure_boost = 0.0
    if has_terminal and starts_upper and is_long:
        structure_boost = 0.12  # Well-formed complete sentence
    elif has_terminal:
        structure_boost = 0.06  # Has ending but may be short/lowercase
    elif not has_terminal and word_count < 6:
        structure_boost = -0.08  # Fragment penalty

    # Linguistic confidence: hedging words lower confidence, certainty words raise it (QR8 P8 T1)
    _CERTAINTY_WORDS = {"always", "confirmed", "verified", "proven", "definite", "guaranteed",
                        "must", "invariably", "consistently", "established", "validated"}
    _HEDGING_WORDS = {"maybe", "might", "possibly", "perhaps", "seems", "appears", "likely",
                      "unclear", "uncertain", "probably", "sometimes", "occasionally"}
    certainty_hits = len(desc_words & _CERTAINTY_WORDS)
    hedging_hits = len(desc_words & _HEDGING_WORDS)
    linguistic_boost = min(0.10, 0.05 * certainty_hits) - min(0.10, 0.05 * hedging_hits)

    total = base + length_boost + richness_boost + specificity_boost + concreteness_boost + structure_boost + linguistic_boost
    return min(0.90, max(0.25, round(total, 3)))
