#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Wrapper Context: Context building and brain search for PULSE agents.

Extracted from pulse_wrapper.py per Law 7 (files <= 300 lines).

Functions:
  - build_context(): Build full agent context (boot + brain + skills)
  - write_context(): Write context to file
  - _build_slim_directive(): Compressed boot directive (~200 tokens)
"""
from __future__ import annotations

import os
import sys
from datetime import datetime, timezone
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent

EVIDENCE_FILE = ROOT_DIR / "Hippocampus" / "Evidence" / "Sessions" / "current_session.json"
CONSTITUTION = ROOT_DIR / "Corpus_Callosum" / "CONSTITUTION.md"
ACTIVE_STATE = ROOT_DIR / "Prefrontal_Cortex" / "Working_Memory" / "ACTIVE_NEURAL_STATE.md"

try:
    from pulse.core.brain_utils import load_json, load_json_dict, TASK_BOARD_FILE
except Exception:
    load_json = None
    load_json_dict = None
    TASK_BOARD_FILE = None

try:
    from pulse.brain.brain_search import brain_search
except Exception:
    brain_search = None

try:
    from pulse.brain.pulse_boot import boot as pulse_boot_run
except Exception:
    pulse_boot_run = None

from .wrapper_skills import load_skills_for_domain, load_skills_interactive, _build_skills_index


def _read_text(path: Path) -> str:
    return path.read_text(encoding="utf-8", errors="replace").strip()


def _task_board_summary() -> list[str]:
    """Summarize active task board for context injection."""
    if not load_json_dict or not TASK_BOARD_FILE:
        return []
    board = load_json_dict(TASK_BOARD_FILE)
    dispatches = board.get("dispatches", [])
    if not dispatches:
        return []
    lines = []
    for dispatch in dispatches:
        tasks = dispatch.get("tasks", [])
        if not tasks:
            continue
        lines.append(f"Dispatch {dispatch.get('dispatch_id', '?')}: {dispatch.get('prompt', '')[:100]}")
        for t in tasks:
            status = t.get("status", "?")
            claimed = t.get("claimed_by", "")
            claim_info = f" [{claimed}]" if claimed else ""
            lines.append(f"  - [{status}]{claim_info} {t.get('title', '?')}")
    return lines


def _truncate_line(text: str, limit: int = 140) -> str:
    compact = " ".join(text.split())
    return compact if len(compact) <= limit else compact[: limit - 3] + "..."


def _load_evidence_items():
    if not load_json or not EVIDENCE_FILE.exists():
        return []
    raw = load_json(EVIDENCE_FILE)
    if isinstance(raw, list):
        return raw
    return []


def _latest_query_from_evidence() -> str:
    items = _load_evidence_items()
    for kind in ("WANT", "OBSERVATION", "DONT_WANT"):
        for item in reversed(items):
            if item.get("type") == kind:
                return item.get("description", "").strip()
    return ""


def _summarize_hit(hit: dict, source: str) -> str:
    title = hit.get("title") or hit.get("file") or hit.get("source") or ""
    text = hit.get("text") or hit.get("summary") or ""
    snippet = _truncate_line(text)
    return f"- [{source.upper()}] {title}: {snippet}" if title else f"- [{source.upper()}] {snippet}"


def _run_brain_search(query: str) -> list[str]:
    if not brain_search or not query:
        return []
    try:
        result = brain_search(query)
        hits = result.get("results", {})
        lines = []
        order = ["anti_patterns", "fails", "wins", "domains", "evidence"]
        for source in order:
            for hit in hits.get(source, [])[:2]:
                lines.append(_summarize_hit(hit, source))
                if len(lines) >= 6:
                    return lines
        return lines
    except Exception as exc:
        return [f"- brain_search error: {_truncate_line(str(exc))}"]


def _build_slim_directive() -> str:
    """Compressed boot directive (~200 tokens vs ~1000 for full PULSE_OS.md)."""
    import platform
    plat = f"Platform: {platform.system()} ({platform.release()})"
    shell_note = ""
    if platform.system() == "Windows":
        shell_note = "\nSHELL: You are on Windows. Use PowerShell commands (Get-Content, etc.) or Python to read files. Bash commands (cat, grep) will NOT work."

    skills_note = _build_skills_index()

    return f"""## PULSE Boot Directive
You are a PULSE agent. Efficiency = survival. Tokens saved = value created.
{plat}{shell_note}

RULES:
1. **BRAIN BRIEFING IS ALREADY INJECTED ABOVE.** Read the MANDATORY BRAIN BRIEFING section. Apply lessons, avoid failures, use proven patterns. This is not optional.
2. **BRAIN BEFORE EVERY ACTION.** Before implementing ANY change, run: `python pulse/brain/brain_query.py --query "<what you are about to do>"`. Apply the results. This is mandatory for EVERY implementation action, not just boot.
3. **CHECK TASK BOARD.** If open tasks exist, CLAIM one matching your tier and execute it. If board is empty, output a brief Status Report and WAIT.
4. **SILENT COMPLIANCE.** Do NOT print law lists, checkboxes, or ceremony text. Governance is loaded. Comply silently. User sees your Status Report and your work — nothing else.
5. **Status Report format:** Your name, your task, 1-2 brain insights you'll apply. Under 5 lines. Then work.
6. Save learnings when done: python pulse/brain/pulse_save.py
7. Never repeat mistakes listed in the BRAIN BRIEFING above.
8. ASOP protocol: Corpus_Callosum/Protocols/ASOP.md
9. **BACKGROUND WORKERS:** Tasks posted via relay are auto-executed by background workers (headless daemons polling the task board). Decompose subtasks via relay — workers handle them. No need to poke other agents.

TASK TIERS (check recommended_tier on each task):
- fast: For Haiku, Flash, cheap models. Simple tasks (rename, format, docs).
- standard: For Sonnet, Gemini Pro. Features, refactoring, testing.
- premium: For Opus, GPT-4. Architecture, security audits, complex design.
- Claim tasks matching YOUR tier. Don't waste expensive models on simple tasks.

KEY PATHS:
- Brain Query: python pulse/brain/brain_query.py --query "<task>" (RUN BEFORE EVERY ACTION)
- Task Board: state/task_board.json (check FIRST)
- Brain: Hippocampus/ (Episodic, Semantic, Implicit, LTP wins, LTD fails)
- Daemons: pulse/daemons/
- Tools: pulse/brain/
- Skills: .cursor/skills/ (read relevant skill before executing domain tasks)
- State: state/
- Pipeline: Personal_Plans/PIPELINE.md (master priority list)
- Full system doc: PULSE_OS.md (read if you need architecture details)
{skills_note}"""


def build_context(include_constitution: bool, search_lines: list[str] | None,
                  agent_name: str = "", agent_tier: str = "standard",
                  autopilot: bool = False, task_query: str = "",
                  skills_used: bool = False) -> tuple[str, list[str]]:
    parts = []
    timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
    parts.append("# PULSE_CONTEXT (auto-generated)")
    parts.append(f"> Generated: {timestamp}")
    parts.append("")

    if pulse_boot_run and agent_name:
        try:
            # Autopilot already claims via task_claiming.py — never double-claim
            # Interactive agents get a PID-based ID so bare names never hit the board
            should_claim = not autopilot
            claim_name = agent_name
            if should_claim and "_" not in agent_name:
                claim_name = f"{agent_name}_interactive_{os.getpid()}"
            boot_context = pulse_boot_run(claim_name, agent_tier, do_claim=should_claim,
                                              task_query=task_query)
            parts.append(boot_context)
            parts.append("")
            parts.append(_build_slim_directive())
            parts.append("")
        except Exception:
            parts.append(_build_slim_directive())
            parts.append("")
            if include_constitution and CONSTITUTION.exists():
                parts.append("## Constitution: CONSTITUTION.md")
                parts.append(_read_text(CONSTITUTION))
                parts.append("")
    else:
        parts.append(_build_slim_directive())
        parts.append("")
        if include_constitution and CONSTITUTION.exists():
            parts.append("## Constitution: CONSTITUTION.md")
            parts.append(_read_text(CONSTITUTION))
            parts.append("")

    if ACTIVE_STATE.exists():
        parts.append("## Active Neural State")
        parts.append(_read_text(ACTIVE_STATE))
        parts.append("")

    if not (pulse_boot_run and agent_name):
        task_lines = _task_board_summary()
        if task_lines:
            parts.append("## CURRENT_TASKS (ASOP Task Board)")
            parts.append("Protocol: Corpus_Callosum/Protocols/ASOP.md")
            parts.extend(task_lines)
            parts.append("")

    if search_lines:
        parts.append("## Brain Search Signals")
        parts.extend(search_lines)
        parts.append("")

    if not autopilot:
        interactive_skills = load_skills_interactive()
        if interactive_skills:
            parts.append(interactive_skills)
            parts.append("")

    loaded_skills = []
    if skills_used and task_query:
        domain = "general"
        task_lines = _task_board_summary()
        if task_lines:
             for line in task_lines:
                 if "Domain:" in line:
                     domain = line.split("Domain:")[1].strip()
                     break
        domain_skills, loaded_skills = load_skills_for_domain(domain, max_skills=3, skills_used=True)
        if domain_skills:
            parts.append(domain_skills)
            parts.append("")

    work_order = os.environ.get("PULSE_WORK_ORDER", "")
    if work_order:
        parts.append("## WORK ORDER \u2014 EXECUTE IMMEDIATELY")
        parts.append(work_order)
        parts.append("")
    elif autopilot:
        parts.append("## SWARM AUTOPILOT MODE")
        parts.append(
            "Read state/task_board.json. "
            "Find an open task, execute it, then exit."
        )
        parts.append("")

    return "\n".join(parts).strip() + "\n", loaded_skills


def write_context(out_path: Path, include_constitution: bool, search_lines: list[str] | None,
                  agent_name: str = "", agent_tier: str = "standard",
                  autopilot: bool = False, task_query: str = "",
                  skills_used: bool = False) -> tuple[str, list[str]]:
    """Write context to file and return (context_path, loaded_skills)."""
    out_path.parent.mkdir(parents=True, exist_ok=True)
    context, loaded_skills = build_context(include_constitution, search_lines, agent_name, agent_tier,
                            autopilot=autopilot, task_query=task_query,
                            skills_used=skills_used)
    out_path.write_text(context, encoding="utf-8")
    return str(out_path), loaded_skills
