#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Brain Query: Pre-action brain search for all PULSE agents (CLI + hook mode)."""

import json
import os
import sys
import io
import select
from pathlib import Path

# Windows Unicode fix (only when running as script, not when imported)
if os.name == 'nt' and __name__ == '__main__':
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

# When run directly (hook/CLI), ensure pulse package is importable
SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))

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

try:
    from pulse.brain.query_search import (
        _find_refinable_items, _search_skills, _search_tools,
        _load_active_broadcasts, _get_recommended_sequences,
        _get_competence_lines,
    )
except Exception:
    _find_refinable_items = lambda hits: []
    _search_skills = lambda q: ""
    _search_tools = lambda q: ""
    _load_active_broadcasts = lambda: []
    _get_recommended_sequences = lambda q: []
    _get_competence_lines = lambda q: []

try:
    from pulse.brain.workspace_integrator import integrate as _integrate
except Exception:
    _integrate = None

try:
    from pulse.daemons.bridge.reflex_arc import detect_mistakes, check_reflex
except Exception:
    detect_mistakes = None
    check_reflex = None


def _query_from_filepath(file_path: str) -> str:
    """Build a search query from a file path."""
    p = Path(file_path)
    name = p.stem.replace("_", " ").replace("-", " ")
    parent = p.parent.name
    if parent and parent not in (".", "", "Tools", "Daemons", "Utilities"):
        return f"{name} {parent.replace('_', ' ')}".strip()
    return name.strip()


def _query_from_hook(hook_input: dict) -> str:
    """Extract search query from Claude Code hook stdin JSON."""
    event = hook_input.get("hook_event_name", "")

    if event == "UserPromptSubmit":
        return hook_input.get("prompt", "")[:200].strip()

    # PreToolUse: Edit or Write
    tool_input = hook_input.get("tool_input", {})
    file_path = tool_input.get("file_path", "")
    if file_path:
        return _query_from_filepath(file_path)

    # Fallback: try command for Bash hooks
    command = tool_input.get("command", "")
    if command:
        return command[:100].strip()

    return ""


def format_briefing(query: str, results: dict) -> str:
    """Format brain search results as compact actionable briefing."""
    hits = results.get("results", {})
    total = results.get("total_results", 0)
    elapsed = results.get("elapsed_ms", 0)

    if total == 0:
        return ""

    lines = []

    # Workspace Integration: synthesize cross-source connections
    if _integrate and total >= 3:
        try:
            synthesis = _integrate(results, query, fast_only=True)
            if synthesis:
                for s in synthesis[:3]:
                    lines.append(f"  !! SYNTHESIS: {s}")
        except Exception:
            pass

    # Predictions (CRITICAL -- show absolute first)
    for h in hits.get("predictions", [])[:2]:
        text = (h.get("title") or h.get("text", ""))[:120].strip()
        body = h.get("text", "")[:200]
        if text:
            lines.append(f"  !! PREDICTION: {text}")
            if len(body) > len(text):
                lines.append(f"     -> {body}")

    # Anti-patterns (warnings -- show first)
    for h in hits.get("anti_patterns", [])[:2]:
        text = (h.get("title") or h.get("text", ""))[:100].strip()
        if text:
            lines.append(f"  ! ANTI-PATTERN: {text}")

    # Failures (don't repeat)
    for h in hits.get("fails", [])[:2]:
        text = (h.get("text") or h.get("title", ""))[:100].strip()
        if text:
            lines.append(f"  ! PAST FAIL: {text}")

    # Render standard sections: (source_key, prefix, max_items)
    for key, prefix, n in [("wins", "  * PROVEN:", 2), ("domains", "  * DOMAIN:", 1),
            ("patterns", "  * PATTERN:", 1), ("schemas", "  @ SCHEMA:", 2),
            ("procedures", "  @ PROCEDURE:", 2), ("evidence", "  * EVIDENCE:", 2),
            ("episodic", "  ~ EPISODE:", 2)]:
        for h in hits.get(key, [])[:n]:
            text = (h.get("text") or h.get("title") or h.get("description", ""))[:100].strip()
            if text:
                lines.append(f"{prefix} {text}")

    # Facts (declarative reference knowledge)
    for h in hits.get("facts", [])[:3]:
        text = (h.get("text") or h.get("title", ""))[:100].strip()
        if not text:
            continue
        suffix = f" [{h.get('domain', '')}]" if h.get("domain") else ""
        if h.get("temporal") == "historical":
            suffix += " (historical)"
        lines.append(f"  > FACT: {text}{suffix}")

    # Decisions (N0) + Graph (causal)
    for h in hits.get("decisions", [])[:2]:
        text = (h.get("text") or h.get("title", ""))[:100].strip()
        if text:
            tag = "[SUPERSEDED] " if h.get("status") == "superseded" else ""
            lines.append(f"  @ DECISION: {tag}{text}")
    for h in hits.get("graph", [])[:2]:
        text = (h.get("text") or h.get("title", ""))[:80].strip()
        if text:
            lines.append(f"  # CAUSAL ({h.get('relationship', '')}): {text}")

    # Competence map (D12: Hebbian Balancer)
    lines.extend(_get_competence_lines(query))

    # Recommended sequences (U10: Dr. STDP)
    sequences = _get_recommended_sequences(query)
    lines.extend(sequences)

    # Active broadcasts (U8: Dr. Global Workspace Theory)
    broadcasts = _load_active_broadcasts()
    lines.extend(broadcasts)

    # Refinable items (Phase 5.9 -- suggest knowledge that needs updating)
    refinable = _find_refinable_items(hits)
    lines.extend(refinable)

    if not lines:
        return ""

    query_display = query[:50].strip()
    header = f"[BRAIN] Knowledge for: {query_display}"
    footer = f"({total} results, {elapsed}ms)"
    return "\n".join([header] + lines[:10] + [footer])


QUERY_LOG_FILE = ROOT_DIR / "state" / "brain_query_log.json"


def _log_query(query: str, result_count: int) -> None:
    """Append query to brain_query_log.json (last 100)."""
    try:
        from datetime import datetime, timezone
        QUERY_LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
        entries = []
        if QUERY_LOG_FILE.exists():
            entries = json.loads(QUERY_LOG_FILE.read_text(encoding="utf-8"))
            if not isinstance(entries, list):
                entries = []
        entries.append({"query": query[:200], "results": result_count,
                        "timestamp": datetime.now(timezone.utc).isoformat()})
        QUERY_LOG_FILE.write_text(json.dumps(entries[-100:], indent=2), encoding="utf-8")
    except Exception:
        pass


def _inline_reflex(user_text: str) -> list[str]:
    """Run reflex arc inline on user prompt — zero daemon latency."""
    if not detect_mistakes or not user_text or len(user_text) < 20:
        return []
    try:
        detections = detect_mistakes(user_text)
        if not detections:
            return []
        if check_reflex:
            try: check_reflex(user_text, "", "hook")
            except Exception: pass
        return [f"  !! REFLEX [{d.get('pattern_type', '')}]: {d.get('description', '')[:120]}"
                for d in detections[:3]]
    except Exception:
        return []


def run_query(query: str, raw_prompt: str = "") -> str:
    """Run brain search and return formatted briefing.

    If raw_prompt is provided, runs inline reflex detection first (<5ms).
    """
    reflex_lines = _inline_reflex(raw_prompt) if raw_prompt else []

    if not brain_search or not query or len(query) < 3:
        if reflex_lines:
            return "\n".join(["[BRAIN] Inline reflex detection"] + reflex_lines)
        return ""
    try:
        # Infer task_type for goal-conditioned attention (Phase 1 wiring)
        task_type = "general"
        ql = query.lower()
        if "fix" in ql or "debug" in ql or "error" in ql: task_type = "debug"
        elif "design" in ql or "arch" in ql: task_type = "design"
        elif "feat" in ql or "impl" in ql or "add" in ql: task_type = "feature"
        elif "review" in ql or "audit" in ql: task_type = "review"

        # Metacognitive control: adapt search depth to domain competence
        _max_per_source = 3
        try:
            from pulse.brain.metacognitive_control import get_search_directive
            directive = get_search_directive(query)
            _max_per_source = directive.max_per_source
        except Exception:
            pass

        results = brain_search(query, max_per_source=_max_per_source, task_type=task_type)
        total = results.get("total_results", 0)
        _log_query(query, total)
        briefing = format_briefing(query, results)

        # Phase 3A: Global Workspace Ignition
        try:
            from pulse.brain.global_workspace import run_workspace_ignition
            broadcast = run_workspace_ignition("brain_query", query, results, task_type)
            winners = broadcast.get("winning_sources", [])
            if winners:
                briefing = f"  [WORKSPACE IGNITION] Winning Coalitions: {', '.join(winners).upper()}\n" + briefing
        except Exception as e:
            import logging
            logging.debug(f"Workspace Ignition failed: {e}")
            
        if reflex_lines:
            # Inject reflex lines at the top, right after the header
            parts = briefing.split("\n", 1) if briefing else ["[BRAIN] Inline reflex"]
            header = parts[0]
            rest = parts[1] if len(parts) > 1 else ""
            return "\n".join([header] + reflex_lines + ([rest] if rest else []))
        return briefing
    except Exception:
        if reflex_lines:
            return "\n".join(["[BRAIN] Inline reflex detection"] + reflex_lines)
        return ""


def _has_stdin_data() -> bool:
    """Check if there's data on stdin without blocking."""
    if os.name == 'nt':
        return not sys.stdin.isatty()
    return bool(select.select([sys.stdin], [], [], 0.0)[0])


def _cli_arg(flag: str) -> str:
    """Get CLI argument value after flag."""
    if flag not in sys.argv: return ""
    idx = sys.argv.index(flag)
    return sys.argv[idx + 1] if idx + 1 < len(sys.argv) else ""


def _format_explain(query: str, results: dict) -> str:
    """Explain mode: show score breakdown for each result."""
    lines = [f"[BRAIN EXPLAIN] Query: {query}"]
    lines.append(f"Total: {results.get('total_results', 0)} results in {results.get('elapsed_ms', 0)}ms\n")
    hits = results.get("results", {})
    for source, items in sorted(hits.items()):
        if not isinstance(items, list) or not items:
            continue
        lines.append(f"── {source.upper()} ({len(items)} hits) ──")
        for h in items[:5]:
            text = (h.get("content") or h.get("text") or h.get("title", ""))[:60].strip()
            conf = h.get("confidence", 0)
            sal = h.get("salience", 0)
            wilson = h.get("_wilson_lower", 0)
            domain = h.get("domain", "?")
            competence = h.get("_domain_competence", "")
            flags = []
            if h.get("_contradiction_flag"): flags.append("CONTESTED")
            if h.get("_low_confidence_domain"): flags.append("LOW_DOMAIN")
            if h.get("_source_trust"): flags.append(f"trust={h['_source_trust']:.2f}")
            flag_str = f" [{', '.join(flags)}]" if flags else ""
            lines.append(f"  conf={conf:.2f} sal={sal:.1f} wilson={wilson:.2f} "
                         f"domain={domain}" + (f"({competence})" if competence else "") +
                         f"{flag_str}")
            lines.append(f"    {text}")
    return "\n".join(lines)


def main():
    for flag, fn in [("--skills", lambda: print(_search_skills(_cli_arg("--skills")))),
                     ("--tools", lambda: print(_search_tools(_cli_arg("--tools"))))]:
        if flag in sys.argv: fn(); return
    if "--explain" in sys.argv:
        q = _cli_arg("--explain")
        if brain_search and q:
            results = brain_search(q, max_per_source=5)
            print(_format_explain(q, results))
        return
    if "--query" in sys.argv:
        b = run_query(_cli_arg("--query"))
        if b: print(b)
        return
    if "--file" in sys.argv:
        b = run_query(_query_from_filepath(_cli_arg("--file")))
        if b: print(b)
        return
    if _has_stdin_data():
        try:
            raw = sys.stdin.read()
            if not raw.strip(): return
            hook_input = json.loads(raw)
        except (json.JSONDecodeError, Exception):
            return
        query = _query_from_hook(hook_input)
        event = hook_input.get("hook_event_name", "")
        raw_prompt = hook_input.get("prompt", "") if event == "UserPromptSubmit" else ""
        briefing = run_query(query, raw_prompt=raw_prompt)
        if not briefing: return
        print(json.dumps({"hookSpecificOutput": {
            "hookEventName": event or "PreToolUse", "additionalContext": briefing}}))
        return
    print('PULSE Brain Query\nCLI: --query "q" | --file "path" | --skills "q" | --tools "q"\nHook: JSON on stdin')


if __name__ == "__main__":
    try:
        main()
    except Exception:
        pass  # Never crash, never block agent
    sys.exit(0)
