#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Brain Search Utilities: Helper functions for search operations (V43.13)

Provides salience computation, retrieval tracking, and date utilities.

Dependencies: Python stdlib + brain_utils
"""

import hashlib
import math
import sys
import time
from datetime import datetime, timezone
from pathlib import Path

from pulse.core.brain_utils import load_json_dict, RETRIEVAL_METRICS


# === RETRIEVAL TRACKING (LTP) ===

_retrieval_cache = {}  # {hash: count}
_cache_loaded_at = 0.0
_CACHE_TTL = 60  # seconds


def _item_key(text: str) -> str:
    """Stable key from first 60 chars — MD5 hash."""
    return hashlib.md5(text[:60].lower().encode("utf-8")).hexdigest()[:12]


def _load_retrieval_cache():
    """Load retrieval counts with 60s TTL cache."""
    global _retrieval_cache, _cache_loaded_at
    now = time.time()
    if _retrieval_cache and (now - _cache_loaded_at) < _CACHE_TTL:
        return _retrieval_cache
    data = load_json_dict(RETRIEVAL_METRICS)
    _retrieval_cache = {k: v.get("retrieval_count", 0)
                        for k, v in data.get("items", {}).items()}
    _cache_loaded_at = now
    return _retrieval_cache


def get_retrieval_count(text: str) -> int:
    """Get retrieval count for a text item."""
    cache = _load_retrieval_cache()
    return cache.get(_item_key(text), 0)


def compute_salience(relevance: float, days_old: int, config: dict,
                     item_salience: float = 1.0,
                     retrieval_count: int = 0) -> float:
    """Salience = weighted sum of 4 factors.

    Four factors:
      - relevance (0.45 weight): how well does query match content
      - recency (0.15 weight): exponential decay over time
      - item_salience (0.25 weight): how important is the event itself
      - retrieval (0.15 weight): how often has this been retrieved (LTP)
    """
    decay_const = config.get("decay", {}).get("benchmark_decay_constant", 100)
    recency = math.exp(-days_old / decay_const)
    # Normalize item_salience from 0.1-10.0 range to 0.0-1.0
    norm_salience = min(1.0, item_salience / 5.0) if item_salience > 0 else 0.2
    # Retrieval factor: log scale, caps at 10+ retrievals
    retrieval_factor = min(1.0, math.log(1 + retrieval_count) / math.log(11)) if retrieval_count > 0 else 0.0
    r_weight = config.get("search", {}).get("relevance_weight", 0.45)
    t_weight = config.get("search", {}).get("recency_weight", 0.15)
    s_weight = config.get("search", {}).get("salience_weight", 0.25)
    ltp_weight = config.get("search", {}).get("retrieval_weight", 0.15)
    return round(relevance * r_weight + recency * t_weight
                 + norm_salience * s_weight + retrieval_factor * ltp_weight, 4)


def days_since(date_str: str) -> int:
    """Compute days since a date string."""
    if not date_str:
        return 365
    try:
        ts = str(date_str).replace("Z", "+00:00")
        if "T" in ts:
            d = datetime.fromisoformat(ts)
        else:
            d = datetime.fromisoformat(ts).replace(tzinfo=timezone.utc)
        return max(0, (datetime.now(timezone.utc) - d).days)
    except (ValueError, TypeError):
        return 365
