"""
Phase 3A + Phase 4A: Goal-conditioned attention vectors.
Weights search sources based on task type or context priorities.
Phase 4: Blends static weights with empirically learned weights
from accumulated reward credit data (RPE prompt adaptation).
"""
import logging

log = logging.getLogger("pulse.attention")


def get_attention_weights(task_type: str = "general") -> dict:
    """
    Returns multipliers for 17 search sources based on task type.
    Phase 4: Blends static priors with learned weights when sufficient data exists.
    Baseline is 1.0. Blend: BLEND_RATIO * learned + (1 - BLEND_RATIO) * static.
    Cold start (no learned data): static weights returned unchanged.
    """
    weights = {
        "hot_cache": 1.0,
        "index": 1.0,
        "predictions": 1.0,
        "anti_patterns": 1.0,
        "graph": 1.0,
        "facts": 1.0,
        "episodic": 1.0,
        "working_memory": 1.0,
        "decisions": 1.0,
        "wins": 1.0,
        "fails": 1.0,
        "domains": 1.0,
        "patterns": 1.0,
        "evidence": 1.0,
        "private": 1.0,
        "schemas": 1.0,
        "procedures": 1.0
    }
    
    task_type = str(task_type).lower()

    # Derive canonical type — must match keys stored by update_from_credits
    if "debug" in task_type or "fix" in task_type:
        _canonical = "debug"
        weights["fails"] = 1.5
        weights["anti_patterns"] = 1.5
        weights["predictions"] = 1.5
        weights["episodic"] = 1.2

    elif "arch" in task_type or "design" in task_type:
        _canonical = "architecture"
        weights["schemas"] = 1.5
        weights["procedures"] = 1.5
        weights["graph"] = 1.5
        weights["patterns"] = 1.2

    elif "feature" in task_type or "impl" in task_type:
        _canonical = "feature"
        weights["patterns"] = 1.3
        weights["wins"] = 1.3
        # In reality DMN is not a standard search source, but we boost relevant concepts
        weights["procedures"] = 1.3

    elif "review" in task_type or "audit" in task_type:
        _canonical = "review"
        weights["anti_patterns"] = 1.5
        weights["decisions"] = 1.5
        weights["fails"] = 1.2

    else:
        _canonical = "general"

    # Phase 4: Blend with empirically learned weights (RPE prompt adaptation)
    try:
        from pulse.brain.metacognition.learned_prompt import get_learned_weights, BLEND_RATIO
        learned = get_learned_weights(_canonical)
        if learned:
            for source, learned_w in learned.items():
                if source in weights:
                    # Clamp to safe range in case of corrupted/edited weights file
                    learned_w = max(0.3, min(2.5, float(learned_w)))
                    # Blend: BLEND_RATIO * learned + (1 - BLEND_RATIO) * static
                    static_w = weights[source]
                    weights[source] = round(
                        BLEND_RATIO * learned_w + (1 - BLEND_RATIO) * static_w, 3)
    except Exception as e:
        # Cold start never throws (get_learned_weights returns None → if-guard).
        # Any exception here is an unexpected error; warn so it's visible in logs.
        log.warning("Learned weight blending failed (static fallback active): %s", e)

    return weights

def apply_attention_to_results(results: dict, task_type: str) -> dict:
    """Biased competition: boost attended sources, suppress competitors (Phase 3).

    Desimone & Duncan 1995: boosting one representation suppresses competitors.
    Sources with weight > 1.0 get boosted. Sources with weight 1.0 get
    suppressed proportionally to how much the boosted sources dominate.
    """
    if str(task_type).lower() == "general":
        return results  # No bias for general queries
    weights = get_attention_weights(task_type)

    # Find max boost factor — used to compute suppression
    boosted = {s: w for s, w in weights.items() if w > 1.0}
    if not boosted:
        return results
    # Suppression factor: non-boosted sources get inhibited
    suppression = 0.7  # 30% reduction for non-boosted sources

    for source, hits in results.get("results", {}).items():
        if not isinstance(hits, list):
            continue
        weight = weights.get(source, 1.0)
        for hit in hits:
            if weight > 1.0:
                # Boosted source: enhance
                if "salience" in hit:
                    hit["_attention_salience"] = float(hit["salience"]) * weight
                if "confidence" in hit:
                    hit["_attention_confidence"] = min(1.0, float(hit["confidence"]) * weight)
            else:
                # Non-boosted: lateral inhibition (suppress)
                if "salience" in hit:
                    hit["_attention_salience"] = float(hit["salience"]) * suppression
                if "confidence" in hit:
                    hit["_attention_confidence"] = float(hit["confidence"]) * suppression
                hit["_inhibited_by"] = list(boosted.keys())[:3]
    return results