#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Predictive Coding Daemon (Friston Free Energy Principle).

Implements Gap 1 from QR8: Pre-emptive Context Loading.
When a task is dispatched, this daemon predicts what knowledge the agent
will need and pre-loads it into the Hot Cache before the agent even boots.
"""
import json
import logging
import time
from pathlib import Path
import sys

SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent.parent
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))

from pulse.core.brain_utils import TASK_BOARD_FILE, load_json_dict
from pulse.brain.brain_search.search_engine import brain_search
from pulse.brain.memory.hot_cache import inject_hot_cache

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

PREDICTION_STATE_FILE = ROOT_DIR / "state" / "predictive_coding_state.json"

def _load_state() -> set:
    if PREDICTION_STATE_FILE.exists():
        try:
            return set(json.loads(PREDICTION_STATE_FILE.read_text(encoding="utf-8")).get("processed_tasks", []))
        except Exception:
            return set()
    return set()

def _save_state(processed: set):
    PREDICTION_STATE_FILE.write_text(json.dumps({"processed_tasks": list(processed)}), encoding="utf-8")

def pre_load_task_context(task: dict):
    """Run a brain search on the task description and inject hits into the hot cache."""
    title = task.get("title", "")
    desc = task.get("description", "")
    domain = task.get("domain", "general")
    
    query = f"{title} {desc}".strip()
    if not query:
        return
        
    log.info(f"Predicting context for task: {title[:50]}")
    
    try:
        # Search the brain for relevant context
        results = brain_search(query, max_per_source=3, exclude_sources={"embeddings"})
        hits = results.get("results", {})
        
        # Extract the highest quality hits
        top_hits = []
        for source in ["anti_patterns", "fails", "patterns", "lessons", "graph"]:
            for hit in hits.get(source, []):
                text = hit.get("text", hit.get("content", ""))
                if text:
                    top_hits.append(f"[{source.upper()}] {text}")
        
        # Inject the top 5 predicted hits into the hot cache for this domain
        if top_hits:
            context_string = "\n".join(top_hits[:5])
            inject_hot_cache(
                domain=domain,
                content=f"PREDICTIVE CONTEXT (Task: {title}):\n{context_string}",
                ttl_hours=4.0
            )
            log.info(f"Injected {min(5, len(top_hits))} predicted items into hot cache for domain '{domain}'")
    except Exception as e:
        log.error(f"Failed to pre-load context: {e}")

def run_predictive_coding_loop(poll_interval: int = 15):
    """Daemon loop to watch for new tasks and pre-load their context."""
    log.info("Predictive Coding Daemon started. Watching for new tasks...")
    processed_tasks = _load_state()
    
    while True:
        try:
            board = load_json_dict(TASK_BOARD_FILE)
            dispatches = board.get("dispatches", [])
            new_tasks_found = False
            
            for dispatch in dispatches:
                for task in dispatch.get("tasks", []):
                    if task.get("status") == "open":
                        task_id = task.get("task_id")
                        if task_id and task_id not in processed_tasks:
                            pre_load_task_context(task)
                            processed_tasks.add(task_id)
                            new_tasks_found = True
            
            if new_tasks_found:
                _save_state(processed_tasks)
                
        except Exception as e:
            log.error(f"Predictive coding loop error: {e}")
            
        time.sleep(poll_interval)

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
    run_predictive_coding_loop()
