#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Search Source #16: Project Map — structural/navigational codebase queries.

When agents ask "where does X happen?", "which file handles Y?", or
"what depends on Z?", this source returns file paths, purposes, and
dependency information from `state/project_map.json`.

Auto-generated by `pulse/admin/brain_ops/project_map.py` via AST parsing.
"""
import json
import logging
from pathlib import Path

from pulse.core.brain_analysis import text_similarity

log = logging.getLogger("pulse.search.project_map")

_MAP_FILE = Path(__file__).resolve().parent.parent.parent.parent / "state" / "project_map.json"
_NAVIGATIONAL_SIGNALS = (
    "where", "which file", "what file", "find", "locate", "path",
    "module", "package", "folder", "directory", "depends", "imports",
    "imported by", "what does", "purpose of", "handles", "responsible",
)
_cache: dict | None = None
_cache_mtime: float = 0.0


def _load_map() -> dict:
    """Load project map with mtime-based caching."""
    global _cache, _cache_mtime
    if not _MAP_FILE.exists():
        return {}
    mtime = _MAP_FILE.stat().st_mtime
    if _cache is not None and mtime == _cache_mtime:
        return _cache
    try:
        _cache = json.loads(_MAP_FILE.read_text(encoding="utf-8"))
        _cache_mtime = mtime
        return _cache
    except Exception as e:
        log.debug("Failed to load project map: %s", e)
        return {}


def _is_navigational(query: str) -> bool:
    """Detect if query has navigational intent."""
    q = query.lower()
    return any(sig in q for sig in _NAVIGATIONAL_SIGNALS)


def search_project_map(query: str, config: dict = None) -> list:
    """Search the project map for structural/navigational queries.

    Returns list of hits: [{text, source, confidence, type, file_path, ...}]
    """
    project_map = _load_map()
    if not project_map:
        return []

    packages = project_map.get("packages", {})
    if not packages:
        return []

    query_lower = query.lower()
    query_words = set(query_lower.split())
    is_nav = _is_navigational(query)

    hits = []
    for pkg_path, pkg_data in packages.items():
        for filename, file_data in pkg_data.get("files", {}).items():
            purpose = file_data.get("purpose", "")
            public_api = file_data.get("public_api", [])
            full_path = f"{pkg_path}/{filename}"

            # Score: filename match + purpose similarity + API match
            score = 0.0

            # Filename/path keyword match
            path_lower = full_path.lower().replace("/", " ").replace("_", " ").replace(".py", "")
            path_words = set(path_lower.split())
            path_overlap = len(query_words & path_words)
            if path_overlap:
                score += path_overlap * 2.0

            # Direct filename mention
            fname_bare = filename.replace(".py", "").replace("_", " ").lower()
            if fname_bare in query_lower or filename.lower() in query_lower:
                score += 5.0

            # Purpose similarity
            if purpose:
                sim = text_similarity(query, purpose)
                score += sim * 3.0

            # Public API match
            api_lower = " ".join(public_api).lower()
            api_words = set(api_lower.replace("_", " ").split())
            api_overlap = len(query_words & api_words)
            if api_overlap:
                score += api_overlap * 1.5

            # Navigational boost
            if is_nav and score > 0:
                score *= 1.5

            if score >= 1.5:
                text = f"{full_path}: {purpose}" if purpose else full_path
                if public_api:
                    text += f" [API: {', '.join(public_api[:5])}]"
                deps = file_data.get("imports_from", [])
                if deps:
                    text += f" [imports: {', '.join(deps[:3])}]"

                hits.append({
                    "text": text,
                    "source": "project_map",
                    "confidence": min(1.0, score / 10.0),
                    "score": score,
                    "type": "structural",
                    "file_path": full_path,
                    "purpose": purpose,
                    "public_api": public_api[:10],
                })

    hits.sort(key=lambda h: h["score"], reverse=True)
    return hits[:15]
