# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Query Search: Skill, tool search and refinable-item suggestion helpers.

Extracted from brain_query.py for Law 7 compliance.
"""

import ast
import json
from datetime import datetime, timezone, timedelta
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent


def _find_refinable_items(hits: dict) -> list:
    """Identify items that should be refined (Phase 5.9 -- Memory Reconsolidation).

    Flags:
    - retrieval_count >= 5 AND confidence < 0.7 -> "frequently used but low confidence"
    - retrieval_count == 0 AND old -> "never used, consider removing"
    """
    suggestions = []
    for source_name in ("wins", "fails", "patterns", "evidence"):
        for h in hits.get(source_name, []):
            conf = h.get("confidence", 0.5)
            # Retrieval count isn't directly in search results, but items with
            # low salience + high relevance are refinement candidates
            salience = h.get("salience", 0)
            relevance = h.get("relevance", 0)
            text = (h.get("text") or h.get("title", ""))[:60].strip()
            if not text:
                continue
            # High relevance but very low salience -> stale knowledge
            if relevance >= 0.4 and salience < 0.1 and text:
                suggestions.append(f"  ~ REFINE? (stale): {text}")
            # Low confidence but relevant
            elif relevance >= 0.3 and conf < 0.5 and text:
                suggestions.append(f"  ~ REFINE? (low confidence): {text}")
    return suggestions[:2]  # Max 2 suggestions per query


def _search_skills(query: str) -> str:
    """Search available skills by keyword."""
    skills_dir = ROOT_DIR / ".cursor" / "skills"
    if not skills_dir.exists():
        return "No skills directory found."
    query_words = set(query.lower().split())
    matches = []
    for category in sorted(skills_dir.iterdir()):
        if not category.is_dir():
            continue
        for skill_file in sorted(category.glob("*.md")):
            name = skill_file.stem
            # Check if query matches skill name or first few lines
            name_words = set(name.lower().replace("_", " ").split())
            if query_words & name_words:
                matches.append(f"  - {category.name}/{name}")
                continue
            try:
                head = skill_file.read_text(encoding="utf-8", errors="replace")[:300].lower()
                if any(w in head for w in query_words):
                    matches.append(f"  - {category.name}/{name}")
            except Exception:
                pass
    if not matches:
        return f"No skills found matching '{query}'"
    return f"Skills matching '{query}' ({len(matches)}):\n" + "\n".join(matches[:15])


def _search_tools(query: str) -> str:
    """Search available tools by keyword."""
    tools_dir = SCRIPT_DIR
    query_words = set(query.lower().split())
    matches = []
    for py_file in sorted(tools_dir.glob("*.py")):
        if py_file.name.startswith("__"):
            continue
        name = py_file.stem
        name_words = set(name.lower().replace("_", " ").split())
        if query_words & name_words:
            # Get purpose from docstring
            try:
                source = py_file.read_text(encoding="utf-8", errors="replace")
                tree = ast.parse(source)
                doc = ast.get_docstring(tree) or ""
                purpose = ""
                for line in doc.split("\n"):
                    line = line.strip()
                    if line and ":" in line and len(line) < 100:
                        purpose = line.split(":", 1)[1].strip()
                        break
                matches.append(f"  - {name}.py: {purpose[:60]}")
            except Exception:
                matches.append(f"  - {name}.py")
            continue
        # Check docstring
        try:
            head = py_file.read_text(encoding="utf-8", errors="replace")[:500].lower()
            if any(w in head for w in query_words):
                matches.append(f"  - {name}.py")
        except Exception:
            pass
    if not matches:
        return f"No tools found matching '{query}'"
    return f"Tools matching '{query}' ({len(matches)}):\n" + "\n".join(matches[:15])


def _get_recommended_sequences(query: str) -> list:
    """Find matching causal chains from procedural_log (U10: Dr. STDP).

    Returns formatted lines like "Recommended: X -> Y -> Z (83% success, 5 runs)".
    """
    proc_log = ROOT_DIR / "Hippocampus" / "Evidence" / "Metrics" / "procedural_log.json"
    if not proc_log.exists():
        return []
    try:
        data = json.loads(proc_log.read_text(encoding="utf-8", errors="replace"))
        if not isinstance(data, list):
            return []
        query_lower = query.lower()
        query_words = set(query_lower.split())
        matches = []
        for proc in data:
            seq = proc.get("sequence", "")
            steps = proc.get("steps", 0)
            if steps < 3:
                continue
            seq_lower = seq.lower()
            # Match if any query word appears in the sequence
            if any(w in seq_lower for w in query_words if len(w) > 2):
                rate = proc.get("success_rate", 0)
                count = proc.get("count", 0)
                matches.append(f"  >> SEQUENCE: {seq} ({rate:.0%} success, {count} runs)")
        return matches[:2]
    except Exception:
        return []


def _get_competence_lines(query: str) -> list:
    """Get competence map lines for brain briefing (D12: Hebbian Balancer).

    Extracts domain keywords from query, looks up best/worst model pairings.
    Returns formatted lines like "COMPETENCE [domain]: best=model (0.82), 45 obs".
    """
    try:
        from pulse.daemons.synthesis.learning.hebbian_balancer import get_domain_competence
    except Exception:
        return []
    # Common domain keywords to check against query
    _DOMAINS = [
        "brain", "api", "cli", "web", "testing", "security", "docs",
        "refactor", "infrastructure", "deployment", "extraction", "general",
        "consolidation", "prediction", "synthesis", "governance", "swarm",
    ]
    query_lower = query.lower()
    matches = [d for d in _DOMAINS if d in query_lower]
    if not matches:
        matches = ["general"]
    lines = []
    for domain in matches[:2]:
        comp = get_domain_competence(domain)
        if not comp:
            continue
        best_model, best_weight = comp["best"]
        line = f"  % COMPETENCE [{domain}]: best={best_model[:20]} ({best_weight:.2f})"
        if comp.get("worst") and comp["models"] > 1:
            worst_model, worst_weight = comp["worst"]
            line += f", worst={worst_model[:20]} ({worst_weight:.2f})"
        line += f", {comp['observations']} obs"
        lines.append(line)
    return lines[:2]


def _load_active_broadcasts(max_age_hours: int = 48) -> list:
    """Load broadcast alerts younger than max_age_hours (U8: Dr. Global Workspace Theory).

    Returns list of formatted strings for brain_query injection.
    """
    broadcast_file = ROOT_DIR / "Hippocampus" / "Evidence" / "Sessions" / "broadcast_alerts.json"
    if not broadcast_file.exists():
        return []
    try:
        data = json.loads(broadcast_file.read_text(encoding="utf-8", errors="replace"))
        alerts = data.get("alerts", [])
        cutoff = datetime.now(timezone.utc) - timedelta(hours=max_age_hours)
        active = []
        for alert in alerts:
            ts_str = alert.get("timestamp", "")
            if ts_str:
                try:
                    ts = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
                    if ts < cutoff:
                        continue
                except (ValueError, TypeError):
                    pass
            failure = alert.get("failure", "")[:100]
            agent = alert.get("agent", "?")
            if failure:
                active.append(f"  !! BROADCAST [{agent}]: {failure}")
        return active[-5:]
    except Exception:
        return []
