# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Brain Analysis: Text similarity, counting, and neuroscience helpers.

Split from brain_utils.py for Law 7 compliance.
"""

from pathlib import Path


# === TEXT UTILITIES ===


def text_similarity(a: str, b: str) -> float:
    """Simple word-overlap Jaccard similarity (0.0-1.0)."""
    if not a or not b:
        return 0.0
    wa = set(a.lower().split())
    wb = set(b.lower().split())
    if not wa or not wb:
        return 0.0
    return len(wa & wb) / len(wa | wb)


def search_relevance(query: str, document: str) -> float:
    """Query-coverage similarity for search (0.0-1.0).

    Returns fraction of query words found in document.
    Unlike Jaccard, long documents don't dilute the score.
    """
    if not query or not document:
        return 0.0
    qw = set(query.lower().split())
    dw = set(document.lower().split())
    if not qw:
        return 0.0
    return len(qw & dw) / len(qw)


def extract_domain(filename: str) -> str:
    """Extract domain name from evidence filename."""
    name = filename.replace(".json", "")
    parts = name.split("_")
    return parts[0] if parts else "unknown"


# === COUNTING ===


def count_lines(path: Path) -> int:
    """Count lines in a file."""
    if not path.exists():
        return 0
    try:
        with open(path, "r", encoding="utf-8") as f:
            return sum(1 for _ in f)
    except OSError:
        return 0


def count_json_items(path: Path) -> int:
    """Count items in a JSON array file."""
    from .brain_io import load_json
    data = load_json(path)
    return len(data) if isinstance(data, list) else 0


def count_md_sections(path: Path) -> int:
    """Count ## sections in a markdown file."""
    if not path.exists():
        return 0
    try:
        with open(path, "r", encoding="utf-8") as f:
            return sum(1 for line in f if line.startswith("## "))
    except OSError:
        return 0


# === CONTENT-AWARE SALIENCE & CONFIDENCE (Phase 0 — Salience Calibration) ===

_SALIENCE_KEYWORDS = {
    "critical": (["never", "always", "must not", "crashes", "data loss", "security",
                  "corruption", "deadlock", "race condition", "destroys", "silent fail",
                  "zero-day", "vulnerability", "irreversible"], 7.0),
    "actionable": (["fix", "use instead", "switch to", "replace with", "root cause",
                    "because", "prevents", "caused by", "broke", "workaround",
                    "migrate", "upgrade", "downgrade", "revert"], 4.0),
    "standard": (["should", "prefer", "better to", "works", "confirmed",
                  "discovered", "learned", "tested", "verified", "refactored",
                  "improved", "reduced"], 2.0),
}


def compute_item_salience(description: str, priority: float = 1.0,
                          emotional_intensity: float = 0.0) -> float:
    """Content-aware salience scoring based on keyword tiers.

    Replaces the old priority*emul formula that clustered 83% of entries at 2.0.
    Tiers: critical=7.0, actionable=4.0, standard=2.0, ambient=1.0.
    Boosts: priority (0-4.5 for priority 1-10), emotional (0-1.5), specificity (0-0.5).
    Range: 1.0-10.0.

    Note: _compute_priority() returns 1-10. At priority=5 (default), boost=2.0.
    At priority=7 (high), boost=3.0. At priority=10 (max), boost=4.5.
    """
    desc_lower = description.lower()
    base = 1.0  # ambient default
    for _tier_name, (keywords, tier_base) in _SALIENCE_KEYWORDS.items():
        if any(kw in desc_lower for kw in keywords):
            base = tier_base
            break
    priority_boost = max(0, (priority - 1)) * 0.5  # 0-4.5 for priority 1-10
    emotional_boost = emotional_intensity * 1.5      # 0-1.5 for intensity 0-1
    word_count = len(description.split())
    specificity_boost = min(0.5, word_count / 40.0)  # longer = more specific

    # Phase 4+: Negativity bias — negative-valence items get safety boost
    valence_boost = 0.0
    try:
        from pulse.core.valence import compute_valence
        valence, _ = compute_valence(description)
        if valence < -0.3:
            valence_boost = 0.5  # Negative content gets safety salience boost
    except Exception:
        pass

    return min(10.0, round(base + priority_boost + emotional_boost + specificity_boost + valence_boost, 1))


def compute_item_confidence(description: str, source_type: str = "bridge") -> float:
    """Content-aware initial confidence based on extraction quality.

    Replaces hardcoded 0.6-0.7 (bridge) and 0.8 (scribe) that defeated decay.
    source_type: "bridge" (regex extraction), "scribe" (LLM extraction), "manual" (pulse_save).
    Range: 0.3-0.7.
    """
    base = {"bridge": 0.45, "scribe": 0.50, "manual": 0.55}.get(source_type, 0.40)
    desc_lower = description.lower()
    # Specificity boost: longer, more detailed descriptions get higher confidence
    word_count = len(description.split())
    if word_count >= 20:
        base += 0.10
    elif word_count >= 12:
        base += 0.05
    # Failure signals get a small boost (more verifiable)
    if any(w in desc_lower for w in ("crash", "error", "fail", "broke", "bug", "timeout")):
        base += 0.05
    return min(0.70, round(base, 2))


# === NEUROSCIENCE HELPERS ===


def compute_weighted_count(items: list, config: dict) -> float:
    """Weighted count: FAIL items weighted higher (error-driven learning #33-34)."""
    fail_weight = config.get("synthesis", {}).get("fail_weight", 2.5)
    total = 0.0
    for item in items:
        if item.get("type") == "fail":
            total += fail_weight
        else:
            total += 1.0
    return total


def compute_confidence_decay(confidence: float, days_old: int,
                             decay_rate: float = 0.005,
                             knowledge_type: str = "lesson") -> float:
    """Apply confidence decay with type-aware half-life (Dimension D).

    Uses exponential half-life model instead of flat linear decay.
    Falls back to flat rate if temporal module unavailable.
    """
    try:
        from pulse.core.temporal import temporal_confidence
        return temporal_confidence(confidence, float(days_old),
                                   knowledge_type=knowledge_type)
    except ImportError:
        decayed = confidence * ((1.0 - decay_rate) ** days_old)
        return max(0.0, round(decayed, 4))


def detect_cross_domain_patterns(evidence_dir: Path) -> list:
    """Find patterns appearing in 2+ domains (transfer detection #16, #29)."""
    from .brain_io import load_json
    if not evidence_dir.exists():
        return []
    pattern_domains = {}
    for ef in sorted(evidence_dir.glob("*.json")):
        domain = extract_domain(ef.name)
        items = load_json(ef)
        if isinstance(items, list):
            for item in items:
                p = item.get("pattern", "")
                if p:
                    pattern_domains.setdefault(p, set()).add(domain)
    return [{"pattern": p, "domains": sorted(d), "count": len(d),
             "universal": len(d) >= 3}
                         for p, d in pattern_domains.items() if len(d) >= 2]


def get_brain_density() -> dict:
    """Calculates synaptic density and integrity across all evidence chambers."""
    from .brain_io import load_json
    from .brain_utils import EVIDENCE_DIR, PRIVATE_DIR
    stats = {"total_items": 0, "signed_items": 0, "domains": 0}
    chambers = [EVIDENCE_DIR, PRIVATE_DIR]

    for chamber in chambers:
        if not chamber.exists(): continue
        for ef in chamber.rglob("*.json"):
            items = load_json(ef)
            if not isinstance(items, list): continue
            stats["domains"] += 1
            for item in items:
                stats["total_items"] += 1
                if "_signature" in item:
                    stats["signed_items"] += 1

    stats["integrity_ratio"] = stats["signed_items"] / stats["total_items"] if stats["total_items"] > 0 else 1.0
    return stats
