#!/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,
    )
except Exception:
    _find_refinable_items = lambda hits: []
    _search_skills = lambda q: ""
    _search_tools = lambda q: ""
    _load_active_broadcasts = lambda: []
    _get_recommended_sequences = lambda q: []


def _query_from_filepath(file_path: str) -> str:
    """Build a search query from a file path."""
    p = Path(file_path)
    parts = []
    # Filename without extension, split snake_case
    name = p.stem.replace("_", " ").replace("-", " ")
    parts.append(name)
    # Parent directory name (often domain-relevant)
    if p.parent.name and p.parent.name not in (".", "", "Tools", "Daemons", "Utilities"):
        parts.append(p.parent.name.replace("_", " "))
    return " ".join(parts).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 = []

    # 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}")

    # Wins (proven approaches)
    for h in hits.get("wins", [])[:2]:
        text = (h.get("text") or h.get("title", ""))[:100].strip()
        if text:
            lines.append(f"  * PROVEN: {text}")

    # Domain knowledge
    for h in hits.get("domains", [])[:1]:
        text = (h.get("text") or h.get("title", ""))[:100].strip()
        if text:
            lines.append(f"  * DOMAIN: {text}")

    # Patterns (recurring observations)
    for h in hits.get("patterns", [])[:1]:
        text = (h.get("text") or h.get("title", ""))[:100].strip()
        if text:
            lines.append(f"  * PATTERN: {text}")

    # Schemas (proven cross-session patterns)
    for h in hits.get("schemas", [])[:2]:
        text = (h.get("title") or h.get("text", ""))[:100].strip()
        if text:
            lines.append(f"  @ SCHEMA: {text}")

    # Procedures (proven workflows)
    for h in hits.get("procedures", [])[:2]:
        text = (h.get("text") or h.get("title", ""))[:100].strip()
        if text:
            lines.append(f"  @ PROCEDURE: {text}")

    # Evidence (past observations)
    for h in hits.get("evidence", [])[:2]:
        text = (h.get("text") or h.get("description") or h.get("title", ""))[:100].strip()
        if text:
            lines.append(f"  * EVIDENCE: {text}")

    # Graph (causal relationships -- Phase 7)
    for h in hits.get("graph", [])[:2]:
        text = (h.get("text") or h.get("title", ""))[:80].strip()
        rel = h.get("relationship", "")
        if text:
            lines.append(f"  # CAUSAL ({rel}): {text}")

    # 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. Keep last 100 entries."""
    try:
        from datetime import datetime, timezone
        log_entry = {
            "query": query[:200],
            "results": result_count,
            "timestamp": datetime.now(timezone.utc).isoformat(),
        }
        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(log_entry)
        entries = entries[-100:]  # Keep last 100
        QUERY_LOG_FILE.write_text(json.dumps(entries, indent=2), encoding="utf-8")
    except Exception:
        pass  # Never block agent


def run_query(query: str) -> str:
    """Run brain search and return formatted briefing."""
    if not brain_search or not query or len(query) < 3:
        return ""
    try:
        results = brain_search(query, max_per_source=3)
        total = results.get("total_results", 0)
        _log_query(query, total)
        return format_briefing(query, results)
    except Exception:
        return ""


def _has_stdin_data() -> bool:
    """Check if there's data on stdin without blocking."""
    if os.name == 'nt':
        # Windows: check if stdin is a pipe/redirect (not interactive terminal)
        import msvcrt
        return not sys.stdin.isatty()
    else:
        return bool(select.select([sys.stdin], [], [], 0.0)[0])


def main():
    # --- CLI mode: --skills search ---
    if "--skills" in sys.argv:
        idx = sys.argv.index("--skills")
        query = sys.argv[idx + 1] if idx + 1 < len(sys.argv) else ""
        print(_search_skills(query))
        return

    # --- CLI mode: --tools search ---
    if "--tools" in sys.argv:
        idx = sys.argv.index("--tools")
        query = sys.argv[idx + 1] if idx + 1 < len(sys.argv) else ""
        print(_search_tools(query))
        return

    # --- CLI mode: --query or --file args ---
    if "--query" in sys.argv:
        idx = sys.argv.index("--query")
        query = sys.argv[idx + 1] if idx + 1 < len(sys.argv) else ""
        briefing = run_query(query)
        if briefing:
            print(briefing)
        return

    if "--file" in sys.argv:
        idx = sys.argv.index("--file")
        file_path = sys.argv[idx + 1] if idx + 1 < len(sys.argv) else ""
        query = _query_from_filepath(file_path)
        briefing = run_query(query)
        if briefing:
            print(briefing)
        return

    # --- Hook mode: read JSON from stdin ---
    if _has_stdin_data():
        try:
            raw = sys.stdin.read()
            if not raw.strip():
                return
            hook_input = json.loads(raw)
        except (json.JSONDecodeError, Exception):
            return  # Bad input -> silent exit

        query = _query_from_hook(hook_input)
        briefing = run_query(query)

        if not briefing:
            return  # No results -> no injection

        event = hook_input.get("hook_event_name", "PreToolUse")
        output = {
            "hookSpecificOutput": {
                "hookEventName": event,
                "additionalContext": briefing
            }
        }
        print(json.dumps(output))
        return

    # --- No args, no stdin: print usage ---
    print("PULSE Brain Query -- Pre-action brain search")
    print()
    print("CLI mode (any agent):")
    print('  python brain_query.py --query "dispatcher bug"')
    print('  python brain_query.py --file "pulse_dispatcher.py"')
    print('  python brain_query.py --skills "debugging"')
    print('  python brain_query.py --tools "brain"')
    print()
    print("Hook mode (Claude Code -- reads JSON from stdin):")
    print("  Configured in .claude/settings.json as PreToolUse hook")


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