#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Source selection and filtering logic for brain_search orchestration."""
import logging
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent))

from pulse.brain.search_sources import (
    search_anti_patterns, search_wins, search_fails, search_domains,
    search_patterns, search_evidence, search_private, search_schemas,
    search_procedures, search_graph, search_predictions, search_facts,
    search_episodic, search_working_memory, search_decisions,
    search_meta_schemas, search_capture_log, search_project_map,
)

log = logging.getLogger("pulse.brain_search.search_source_selector")


def _is_query_complex(query: str) -> bool:
    """Check if query is complex (5+ words or boolean operators)."""
    return len(query.split()) >= 5 or any(op in query for op in ["AND", "OR", "NOT", '"'])


def _check_index_staleness(index_results: dict, config: dict) -> bool:
    """Check if search index is >30min behind capture logs."""
    if not index_results:
        return True
    try:
        from pulse.core.brain_utils import STATE_DIR
        idx_file = STATE_DIR / "search_index.json"
        cap_files = list(STATE_DIR.glob("pulse_captured_*.log"))
        if idx_file.exists() and cap_files:
            idx_mtime = idx_file.stat().st_mtime
            latest_cap = max(f.stat().st_mtime for f in cap_files)
            if (latest_cap - idx_mtime) > 1800:
                return True
    except Exception:
        return True
    return False


def build_source_functions(query: str, config: dict, index_results: dict,
                          exclude_sources: set | None = None) -> dict:
    """Build and filter search function dictionary based on query complexity and index state.

    Returns dict of {source_name: search_function} ready for parallel execution.
    """
    search_fns = {
        "predictions": search_predictions,
        "anti_patterns": search_anti_patterns,
        "graph": search_graph,
        "facts": search_facts,
        "episodic": search_episodic,
        "working_memory": search_working_memory,
        "decisions": search_decisions,
        "meta_schemas": search_meta_schemas,
        "capture_log": search_capture_log,   # N0: conversational memory recall
        "project_map": search_project_map,   # QR8 Panel 6: structural navigation
    }

    # Add comprehensive sources if index is missing or stale
    index_stale = _check_index_staleness(index_results, config)
    if not index_results or index_stale:
        search_fns.update({
            "wins": search_wins, "fails": search_fails,
            "domains": search_domains, "patterns": search_patterns,
            "evidence": search_evidence, "private": search_private,
            "schemas": search_schemas, "procedures": search_procedures,
        })
    else:
        search_fns.update({
            "wins": search_wins, "fails": search_fails,
            "patterns": search_patterns, "evidence": search_evidence,
        })

    # Item 68: simple queries -> core sources (P47: always include episodic + working_memory)
    is_complex = _is_query_complex(query)
    if not is_complex:
        _simple_sources = (
            "wins", "fails", "patterns", "anti_patterns", "decisions",
            "episodic", "working_memory", "facts", "predictions",
            "graph", "schemas", "procedures", "capture_log", "project_map",
        )
        search_fns = {k: v for k, v in search_fns.items() if k in _simple_sources}

    if exclude_sources:
        search_fns = {k: v for k, v in search_fns.items() if k not in exclude_sources}

    # Item 129: Knowledge type routing -- boost relevant sources by query intent (P30)
    try:
        from pulse.brain.brain_search.query_routing import boost_sources_by_intent
        search_fns = boost_sources_by_intent(query, search_fns)
    except Exception:
        pass  # fail-open

    # Item 69: cognitive load-adaptive source pruning
    try:
        from pulse.core.interoception import get_signals
        load = get_signals().get("agent_load", 0)
        mx = 5 if load > 10 else (8 if load > 5 else len(search_fns))
        if len(search_fns) > mx:
            priority = ["anti_patterns", "fails", "wins", "patterns",
                        "decisions", "predictions", "graph", "facts"]
            search_fns = {k: v for k, v in search_fns.items() if k in priority[:mx]}
    except Exception:
        pass

    # QR8 Gap 2: Contextual Bandit for Source Selection
    try:
        from pulse.brain.brain_search.contextual_bandit import select_top_sources
        source_names = list(search_fns.keys())
        # Deeper search for specific tasks
        top_k = 10 if len(query.split()) < 3 else 15
        selected_sources = select_top_sources("general", source_names, top_k)
        search_fns = {k: v for k, v in search_fns.items() if k in selected_sources}
    except Exception:
        pass

    return search_fns
