#!/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:
        # Skip embeddings at boot — 177MB index parse adds ~40s cold start
        result = brain_search(query, exclude_sources={"embeddings"})
        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.
10. **AGENT AGNOSTIC CORE:** Do NOT read or modify CLAUDE.md, GEMINI.md, AGENT.md, or AGENTS.md to change agent behavior. All rules and architecture enforcement must be done programmatically in code via Python daemons, hooks, or the anti-pattern gate.

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
