#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""JSONL entry builder: constructs enriched knowledge entries for brain storage."""
import re
import threading
import uuid
from pulse.core.brain_utils import _make_fingerprint as make_fingerprint

_TRIGGER_RE = re.compile(
    r'(?:when|if|before|after|once|until)\s+(.{10,80}?)(?:\.|,|$)',
    re.IGNORECASE
)
# Item 62: Implementation intentions — WHEN/DO extraction
_WHEN_DO_RE = re.compile(
    r'when\s+(.{10,80}?),?\s*(?:then\s+|do\s+)?(.{10,80})',
    re.IGNORECASE
)


_TYPE_PREFIX = {"lesson": "L", "failure": "F", "pattern": "P", "private": "V"}
_MD_TO_TYPE = {"auto_lessons.md": "lesson", "auto_fails.md": "failure",
    "auto_patterns.md": "pattern", "private_lessons.md": "private",
    "auto_lessons.jsonl": "lesson", "auto_fails.jsonl": "failure",
    "auto_patterns.jsonl": "pattern", "private_lessons.jsonl": "private"}
_SESSION_ITEM_LOCAL = threading.local()  # Thread-local storage for _LAST_SESSION_ITEM_ID
def _make_fingerprint(text: str) -> str:
    """Thin wrapper for backward compat."""
    return make_fingerprint(text)
def _make_entry_id(knowledge_type: str) -> str:
    prefix = _TYPE_PREFIX.get(knowledge_type, "K"); return f"{prefix}-{uuid.uuid4().hex[:8]}"
def build_jsonl_entry(description: str, source: str, timestamp: str, domain: str,
                      confidence: float, salience: float, knowledge_type: str = "lesson",
                      provenance: dict = None) -> dict:
    """Build a JSONL knowledge entry with temporal/uncertainty fields."""
    from pulse.core.temporal import stamp_temporal_fields
    from pulse.core.uncertainty import stamp_uncertainty_fields
    from pulse.core.brain_analysis import truncate_at_sentence
    
    # QR2 P1: Semantic Title Generation (Fallback to smart truncation)
    # 80 chars often cuts mid-word. We use sentence-aware truncation.
    clean_desc = description.replace("\n", " ").strip()
    smart_title = truncate_at_sentence(clean_desc, max_chars=120)
    if len(smart_title) > 120:
        smart_title = smart_title[:117] + "..."

    entry = {
        "id": _make_entry_id(knowledge_type),
        "fingerprint": _make_fingerprint(description),
        "type": knowledge_type,
        "timestamp": timestamp,
        "agent": source,
        "content": description,
        "title": smart_title,
        "domain": domain,
        "salience": salience,
        "confidence": confidence,
        "consolidation_count": 1,
        "evidence_count": 1,
        "tags": [],
        "cross_refs": [],
        "_provenance": provenance or {},
    }
    entry = stamp_temporal_fields(entry, knowledge_type)
    entry = stamp_uncertainty_fields(entry)
    # Stamp embedding at write time
    try:
        from pulse.brain.index.rule_embeddings.backends import _ST_MODEL
        from pulse.brain.index.rule_embeddings import embed_texts, EMBED_INDEX_FILE
        # Only embed when model is already warm — cold load takes 14s (PyTorch import)
        if _ST_MODEL is not None and EMBED_INDEX_FILE.exists():
            vectors = embed_texts([description[:500]])
            if vectors and len(vectors) > 0 and len(vectors[0]) > 0:
                entry["_embedding"] = vectors[0]
    except Exception:
        pass
    try:  # Stamp valence for emotion-gated decay
        from pulse.core.valence import compute_valence
        valence, arousal = compute_valence(description)
        if valence != 0.0 or arousal > 0.0:
            entry["_valence"] = valence; entry["_arousal"] = arousal
    except Exception: pass
    # Item 10: Flashbulb memory — high-emotion + high-salience = permanent validity
    try:
        ei = float(entry.get("emotional_intensity", 0) or entry.get("_arousal", 0))
        if ei >= 0.8 and float(entry.get("salience", 0)) >= 3.0:
            entry["validity"] = "permanent"
    except Exception:
        pass
    try:  # Prospective memory: extract trigger conditions
        if knowledge_type == "intent":
            _m = _TRIGGER_RE.search(description)
            if _m: entry["trigger_condition"] = _m.group(1).strip()
    except Exception: pass
    # Item 62: Implementation intentions — WHEN/DO structured extraction
    try:
        if knowledge_type == "intent":
            wd_match = _WHEN_DO_RE.search(description)
            if wd_match:
                entry["when_trigger"] = wd_match.group(1).strip(); entry["do_action"] = wd_match.group(2).strip()
    except Exception: pass
    try:  # Item 21: Generation effect
        _sl = str(source).lower(); _pa = (provenance or {}).get("saving_agent", "") or ""
        if ((_pa and source == _pa) or "self" in _sl or "generated" in _sl):
            entry["salience"] = entry.get("salience", 1.0) + 0.3; entry["_generation_effect"] = True
    except Exception: pass
    try:  # Item 25: Emotional salience boost
        _em = float((provenance or {}).get("emotional_intensity", 0) or entry.get("emotional_intensity", 0) or 0)
        if _em >= 0.6: entry["salience"] = entry.get("salience", 1.0) + (_em * 0.5); entry["_emotional_salience"] = True
    except Exception: pass
    try:  # Bayesian hyperpriors
        from pulse.brain.metacognition.metamemory import get_metamemory
        _cp = get_metamemory().get(domain, {}).get("competence_level", "")
        if _cp == "expert": entry["confidence"] = max(entry.get("confidence", 0.5), 0.7)
        elif _cp == "novice": entry["confidence"] = min(entry.get("confidence", 0.5), 0.3)
    except Exception: pass
    try:  # Episode ID binding via EPISODE_OF edges
        _eid = entry.get("id")
        _last_sid = getattr(_SESSION_ITEM_LOCAL, "last_id", None)
        if _last_sid and _eid:
            from pulse.core.brain_db import get_conn as _ep_conn
            _c = _ep_conn()
            _c.execute("INSERT OR IGNORE INTO graph_edges (from_node, to_node, edge_type, confidence) VALUES (?, ?, 'EPISODE_OF', 0.7)",
                       (_last_sid, _eid))
            _c.commit()
        _SESSION_ITEM_LOCAL.last_id = entry.get("id")
    except Exception: pass  # fail-open

    try:
        if knowledge_type == "failure" and float(entry.get("confidence", 0)) >= 0.8:
            entry["broadcast"] = True; entry["_social_broadcast"] = True
    except Exception: pass
    # Item 147: Domain emotional valence tracking
    try:
        from pulse.brain.metacognition.metamemory_p34 import record_domain_valence
        record_domain_valence(entry.get("domain", "general"), entry.get("content", "") or str(float(entry.get("_arousal", 0) or 0)))
    except Exception: pass
    # Item 80: Emotional contagion
    try:
        from datetime import datetime, timezone, timedelta; from pulse.core.brain_db import get_conn
        _ei = float(entry.get("emotional_intensity", 0) or entry.get("_arousal", 0) or 0)
        if _ei >= 0.7:
            _ec = get_conn(); _r = (datetime.now(timezone.utc) - timedelta(minutes=5)).isoformat()
            for _tb in ("lessons","failures"): _ec.execute(f"UPDATE {_tb} SET salience=MIN(10.0,salience+0.2) WHERE timestamp>? AND domain=?", (_r, entry.get("domain","general")))
            _ec.commit()
    except Exception: pass

    return entry

