#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Search Source: Decisions (N0 — Conversational Memory Recall)

Searches Hippocampus/Decisions/auto_decisions.jsonl for matching decisions.
Returns salience-scored results matching the brain_search contract.

Dependencies: Python stdlib + brain_utils + brain_analysis
"""

import json
from pulse.core.brain_utils import DECISIONS_JSONL
from pulse.core.brain_analysis import text_similarity
from pulse.core.brain_io import _acquire


def search_decisions(query: str, config: dict = None) -> list:
    """Search decisions JSONL for choices matching query.

    Matches against choice + reasoning + context + tags.
    Boosts active decisions, penalizes superseded.
    Returns list of hit dicts compatible with brain_search format.
    """
    if not DECISIONS_JSONL.exists():
        return []

    try:
        with _acquire(DECISIONS_JSONL):
            lines = DECISIONS_JSONL.read_text(encoding="utf-8").splitlines()
    except OSError:
        return []

    query_lower = query.lower().strip()
    if not query_lower:
        return []

    hits = []
    for line in lines:
        line = line.strip()
        if not line:
            continue
        try:
            dec = json.loads(line)
        except json.JSONDecodeError:
            continue

        if dec.get("status", "active") not in ("active", "superseded"):
            continue

        choice = dec.get("choice", "")
        reasoning = dec.get("reasoning", "")
        context = dec.get("context", "")
        tags = " ".join(dec.get("tags", []))

        search_text = f"{choice} {reasoning} {context} {tags}".lower()

        # Similarity scoring
        score = 0.0

        # Choice match (strongest signal)
        choice_sim = text_similarity(query_lower[:80], choice.lower())
        score += choice_sim * 3.0

        # Reasoning/context match (full text, no truncation)
        ctx_sim = text_similarity(query_lower[:80], search_text)
        score += ctx_sim * 1.5

        # Keyword overlap
        query_words = set(query_lower.split())
        text_words = set(search_text.split())
        overlap = len(query_words & text_words)
        if overlap >= 2:
            score += overlap * 0.3

        if score < 0.5:
            continue

        # Status-based modifiers
        confidence = dec.get("confidence", 0.65)
        status = dec.get("status", "active")
        if status == "superseded":
            confidence *= 0.4

        # Build display text with status tag
        status_tag = "[SUPERSEDED] " if status == "superseded" else ""
        alts = dec.get("alternatives_rejected", [])
        alt_text = f" (over {', '.join(alts)})" if alts else ""
        reason_text = f" — {reasoning[:60]}" if reasoning else ""
        display = f"{status_tag}{choice}{alt_text}{reason_text}"

        hits.append({
            "text": display[:200],
            "title": f"[DECISION] {choice[:80]}",
            "id": dec.get("id", ""),
            "salience": round(min(score, 3.0), 3),
            "confidence": confidence,
            "source": "decisions",
            "scope": dec.get("scope", "general"),
            "status": status,
            "timestamp": dec.get("timestamp", ""),
        })

    hits.sort(key=lambda h: h["salience"], reverse=True)
    return hits[:10]
