#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""PULSE Generic Wrapper - Builds merged context and launches agent CLI."""
from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
import shutil
import time
import threading
from datetime import datetime, timezone
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent
STATE_DIR = ROOT_DIR / "state"

# When run directly (e.g. via subprocess from cmd_agent), ensure pulse package is importable
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))

try:
    from pulse.core.brain_utils import (
        ensure_brain_scaffold, load_json_dict, save_json,
        TASK_BOARD_FILE, AGENT_REGISTRY_FILE,
    )
except Exception:
    ensure_brain_scaffold = None
    load_json_dict = None
    save_json = None
    TASK_BOARD_FILE = None
    AGENT_REGISTRY_FILE = None

# Re-export for backwards compatibility
from pulse.agents.wrapper_skills import load_skills_for_domain, load_skills_interactive
from pulse.agents.wrapper_context import (
    build_context, write_context, _run_brain_search, _latest_query_from_evidence,
)
from pulse.agents.wrapper_orchestrator import _ensure_orchestrator_running
from pulse.agents.wrapper_capture import _ensure_capture_daemon

# H2: Agent whitelist
_ALLOWED_AGENTS = frozenset({
    "claude_daemon", "gemini_daemon", "grok_daemon",
    "codex_daemon", "bridge_daemon", "neural_daemon",
    "compliance_daemon", "dispatcher_daemon",
})

def _register_agent(agent_name: str) -> None:
    """Register or update agent entry in agent_registry.json."""
    if not load_json_dict or not save_json or not AGENT_REGISTRY_FILE:
        return
    if agent_name not in _ALLOWED_AGENTS:
        return
    registry = load_json_dict(AGENT_REGISTRY_FILE)
    now = datetime.now(timezone.utc).isoformat()
    if agent_name not in registry:
        registry[agent_name] = {
            "agent_id": agent_name, "model": "unknown", "type": "daemon",
            "status": "idle", "capabilities": [], "last_heartbeat": now,
            "current_task": None,
            "history": {"tasks_completed": 0, "domains": {}},
        }
    else:
        registry[agent_name]["last_heartbeat"] = now
        registry[agent_name]["status"] = "idle"
        if "type" not in registry[agent_name]:
            registry[agent_name]["type"] = "daemon"
    save_json(AGENT_REGISTRY_FILE, registry)

def _detect_agent_name(cmd: list[str]) -> str:
    """Detect agent name from command arguments."""
    for arg in cmd:
        lower = arg.lower()
        if "gemini" in lower:
            return "gemini_daemon"
        if "claude" in lower:
            return "claude_daemon"
        if "codex" in lower:
            return "codex_daemon"
        if "grok" in lower:
            return "grok_daemon"
    return ""

def _board_notifier_thread(board_path: Path):
    """Background thread that polls for new tasks and prints to stderr."""
    if not board_path.exists():
        return

    def get_open_tasks():
        try:
            board = json.loads(board_path.read_text(encoding="utf-8"))
            tasks = {}
            for d in board.get("dispatches", []):
                for t in d.get("tasks", []):
                    if t.get("status") == "open":
                        tid = t.get("task_id")
                        if tid:
                            tasks[tid] = t.get("title", "Untitled")
            return tasks
        except Exception:
            return {}

    known_task_ids = set(get_open_tasks().keys())
    while True:
        time.sleep(30)
        current_tasks = get_open_tasks()
        current_ids = set(current_tasks.keys())
        new_ids = current_ids - known_task_ids
        if new_ids:
            for tid in new_ids:
                title = current_tasks[tid]
                sys.stderr.write(f"\n[PULSE] New task available: {title}\n")
                sys.stderr.flush()
            known_task_ids.update(new_ids)

def parse_args(argv: list[str]) -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="PULSE generic wrapper")
    parser.add_argument("--out", default="")
    parser.add_argument("--print", action="store_true")
    parser.add_argument("--set-env", default="")
    parser.add_argument("--include-constitution", action="store_true")
    parser.add_argument("--autopilot", action="store_true")
    parser.add_argument("--skills-used", action="store_true")
    parser.add_argument("cmd", nargs=argparse.REMAINDER)
    return parser.parse_args(argv)

def _extract_prompt_from_cmd(cmd: list[str]) -> str:
    """Extract potential user prompt from command arguments."""
    prompt_parts = [arg for arg in cmd if not arg.startswith("-")]
    if prompt_parts and prompt_parts[0].lower() in ("gemini", "claude", "codex", "grok", "python", "node", "npm"):
        prompt_parts = prompt_parts[1:]
    return " ".join(prompt_parts).strip()

def _build_boot_message(context_path, agent_name, loaded_skills, is_autopilot):
    """Build the boot message injected into the agent CLI command."""
    ctx_resolved = Path(context_path).resolve()
    work_order = os.environ.get("PULSE_WORK_ORDER", "")
    skills_tracking = ""
    if loaded_skills:
        skills_tracking = f" --skills-used \"{','.join(loaded_skills)}\""

    if work_order:
        title_line = next((l[6:].strip() for l in work_order.split("\n") if l.startswith("Title:")), "")
        msg = (f"WORK ORDER: Read {ctx_resolved} and execute the task described inside. "
               f"Task: {title_line}. The task is already claimed for you. Just do the work. "
               f"Do NOT modify task_board.json. When done, output TASK_COMPLETE.")
        if skills_tracking:
            msg += f" Save with: python pulse/brain/pulse_save.py --agent {agent_name or 'unknown'} --task-id <ID>{skills_tracking}"
    elif is_autopilot:
        msg = f"SWARM AUTOPILOT: Read {ctx_resolved} and follow the instructions inside."
    else:
        msg = (f"SYSTEM BOOT: You are initialized within the PULSE Autonomic System. "
               f"Your memory and instructions are at: {ctx_resolved}. READ THIS FILE IMMEDIATELY. "
               f"MANDATORY: Per Law 10, output a Status Report identifying yourself BEFORE using any tools.")
        if skills_tracking:
            msg += f" (MANDATORY: Track skills-used in pulse_save.py with:{skills_tracking})"
    return msg


def main(argv: list[str]) -> int:
    args = parse_args(argv)
    if ensure_brain_scaffold:
        ensure_brain_scaffold()
    _ensure_orchestrator_running()

    # E4: Ensure capture daemon is alive before agent launch
    if args.cmd:
        _agent = _detect_agent_name(args.cmd)
        if _agent:
            _ensure_capture_daemon(_agent)

    query = _extract_prompt_from_cmd(args.cmd) if args.cmd else ""
    if not query:
        query = _latest_query_from_evidence()
    search_lines = _run_brain_search(query)

    # Inject context from interactive CLI check (Phase 2 enforcement)
    cli_context = os.environ.get("PULSE_BRAIN_CONTEXT")
    if cli_context:
        search_lines.insert(0, "")
        search_lines.insert(0, "## IMMEDIATE CONTEXT (Interactive Check)")
        search_lines.insert(0, cli_context)
        search_lines.insert(0, "")

    agent_name, agent_tier = "", "standard"
    if args.cmd:
        agent_name_id = _detect_agent_name(args.cmd)
        if agent_name_id:
            agent_name = agent_name_id.replace("_daemon", "")
        for arg in args.cmd:
            if "haiku" in arg.lower() or "flash" in arg.lower():
                agent_tier = "fast"
            elif "opus" in arg.lower() or "gpt-4" in arg.lower():
                agent_tier = "premium"

    out_path = Path(args.out) if args.out else STATE_DIR / f"pulse_context_{agent_name or 'unknown'}_{os.getpid()}.md"
    is_autopilot = args.autopilot or os.environ.get("PULSE_AUTOPILOT") == "1"
    context_path, loaded_skills = write_context(
        out_path, args.include_constitution, search_lines, agent_name, agent_tier,
        autopilot=is_autopilot, task_query=query, skills_used=args.skills_used)

    if getattr(args, 'print'):
        content = Path(context_path).read_text(encoding="utf-8", errors="replace")
        try:
            sys.stdout.buffer.write(content.encode("utf-8"))
            sys.stdout.flush()
        except Exception:
            sys.stdout.write(content)

    if args.cmd:
        cmd = args.cmd[1:] if args.cmd and args.cmd[0] == "--" else args.cmd
        if not cmd:
            return 0
        agent_name = _detect_agent_name(cmd)
        if agent_name:
            _register_agent(agent_name)
        resolved_path = shutil.which(cmd[0])
        if not resolved_path:
            sys.stderr.write(f"[PULSE] Command not found: {cmd[0]}\n")
            return 1
        cmd[0] = resolved_path
        env = os.environ.copy()
        if args.set_env:
            env[args.set_env] = str(out_path.resolve())
        try:
            executable = cmd[0].lower()
            boot_message = _build_boot_message(context_path, agent_name, loaded_skills, is_autopilot)
            if "gemini" in executable:
                pass # The system prompt is now handled by the .gemini/hooks/brain_hook.py
            elif "claude" in executable:
                cmd.extend(["--append-system-prompt-file", str(context_path)])
            elif "codex" in executable:
                cmd.append(boot_message)
            if os.name == 'nt':
                if executable.endswith(('.cmd', '.bat')):
                    cmd = ['cmd.exe', '/c'] + cmd
                elif executable.endswith('.ps1'):
                    cmd = ['powershell.exe'] + cmd
            # NOTE: _board_notifier_thread DISABLED — it wrote to sys.stderr
            # while Claude Code TUI had exclusive terminal control, corrupting
            # the Windows console and crashing the agent CLI.  Task notifications
            # now go to state/task_notifications.log (see _board_notifier_thread).
            return subprocess.call(cmd, env=env, shell=False)
        except FileNotFoundError:
            sys.stderr.write(f"[PULSE] Command not found: {cmd[0]}\n")
            return 1

    if args.set_env:
        sys.stdout.write(f"[PULSE] Set env var:\n  $env:{args.set_env} = \"{Path(context_path).resolve()}\"\n")
    elif not is_autopilot:
        sys.stdout.write(f"[PULSE] Context file: {Path(context_path).resolve()}\n")
    return 0

if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))
