#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Helper functions for workspace synthesis: caching, scoring, LLM dispatch."""
import hashlib
import re
import time
from pulse.core.brain_utils import search_relevance

# Source type mapping
_SOURCE_TYPE = {
    "wins": "lesson", "fails": "failure", "anti_patterns": "warning",
    "patterns": "pattern", "predictions": "prediction", "evidence": "evidence",
    "graph": "causal", "schemas": "schema", "procedures": "procedure",
    "facts": "fact", "episodic": "episode", "decisions": "decision",
    "hot_cache": "recent", "index": "indexed", "working_memory": "session",
    "domains": "domain", "private": "private",
}

_NOISE_RE = re.compile(
    r"(?:^(?:bro|yo|hey|ok |let'?s go|OOOOO|BROOO|good morni|Capture:|<task-)"
    r"|capture_daemon:|pulse_captured_)", re.IGNORECASE)

# In-memory synthesis cache (query fingerprint -> lines, with TTL)
_cache: dict = {}
_CACHE_MAX = 50
_CACHE_TTL = 300  # 5 minutes


def _get_source_type(source_name: str) -> str:
    """Get semantic type for source name."""
    return _SOURCE_TYPE.get(source_name, source_name)


def _is_noise_item(source_name: str, text: str) -> bool:
    """Check if item is capture_log noise."""
    return source_name == "capture_log" and _NOISE_RE.search(text)


def top_k_global(hits_dict: dict, k: int = 8) -> list:
    """Flatten all sources, tag with source type, return top K by score."""
    from pulse.core.brain_analysis import text_similarity

    all_items = []
    for source_name, hits in hits_dict.items():
        if not isinstance(hits, list):
            continue
        stype = get_source_type(source_name)
        for h in hits:
            if isinstance(h, dict) and not h.get("error"):
                text = h.get("text") or h.get("title") or h.get("description", "")
                text_clean = text[:120].strip()
                # Skip low-confidence items
                conf = h.get("confidence", 0.5)
                if conf < 0.5:
                    continue
                if not text_clean or len(text_clean) < 20:
                    continue
                # Filter noise
                if is_noise_item(source_name, text_clean):
                    continue
                item = dict(h)
                item["_source_type"] = stype
                item["_text_norm"] = text_clean.lower()
                item["_text_short"] = text_clean[:60]
                item["_score"] = h.get("salience", 1.0) * h.get("confidence", 0.5)
                all_items.append(item)

    all_items.sort(key=lambda x: x["_score"], reverse=True)
    return all_items[:k]


def _cache_key(items: list, query: str) -> str:
    """MD5 of sorted item fingerprints + query for cache lookup."""
    parts = sorted(i.get("_text_norm", "")[:40] for i in items)
    raw = query[:200] + "|" + "|".join(parts)
    return hashlib.md5(raw.encode()).hexdigest()[:16]


def _cache_get(key: str) -> list | None:
    """Retrieve from cache if valid (within TTL)."""
    cached = _cache.get(key)
    if cached and (time.time() - cached[1]) < _CACHE_TTL:
        return cached[0]
    return None


def _cache_put(key: str, lines: list) -> None:
    """Store in cache with TTL, evict oldest half if over max."""
    global _cache
    if len(_cache) > _CACHE_MAX:
        _cache = dict(sorted(_cache.items(), key=lambda x: x[1][1])[_CACHE_MAX // 2:])
    _cache[key] = (lines, time.time())


def llm_synthesis(clusters: list, query: str) -> list:
    """LLM-based cross-source synthesis via llm_router."""
    from pulse.core.llm_router import dispatch
    import json

    # Format items for LLM
    formatted = []
    for cluster in clusters[:3]:
        for item in cluster[:4]:
            formatted.append(
                f"[{item.get('_source_type', '?').upper()}] {item.get('_text_short', '?')}")

    prompt = (
        f'Given these brain search results about "{query[:60]}", produce 1-3 '
        f"concise synthesis sentences connecting items from different sources.\n\n"
        f"Results:\n" + "\n".join(formatted) + "\n\n"
        "Rules: reference 2+ items from different sources per sentence. "
        "Note contradictions explicitly. Max 150 chars per sentence. Be specific.\n"
        'Output JSON: {"synthesis": ["sentence1", "sentence2"]}'
    )

    resp = dispatch(prompt, task_type="fast", max_tokens=300)
    text = resp.get("text", "") if isinstance(resp, dict) else str(resp)

    # Parse JSON from response
    try:
        start = text.index("{")
        end = text.rindex("}") + 1
        data = json.loads(text[start:end])
        return [s[:150] for s in data.get("synthesis", []) if isinstance(s, str) and len(s) > 30]
    except (ValueError, json.JSONDecodeError):
        return []
