#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Item 121: Learning rate annealing per domain (Neuroplasticity — P29).

Mature domains (many items, high avg confidence) get lower STDP learning rates.
New/sparse domains get higher rates. Modeled after neuroplasticity research
showing critical periods of high plasticity in novel domains.

Extracted from stdp.py to keep that file under 300 lines.
"""
import logging
import threading

log = logging.getLogger("pulse.stdp_annealing")

_anneal_lock = threading.Lock()


def _get_domain_maturity_factor(domain: str) -> float:
    """Return STDP learning rate multiplier based on domain maturity.

    Mature domains (many items, high avg confidence) learn slowly (0.5).
    New/sparse domains learn fast (1.5). Prevents overwriting stable knowledge.
    """
    if not domain:
        return 1.0
    try:
        from pulse.core.brain_db import get_conn as _gc121
        conn = _gc121()
        row = conn.execute(
            "SELECT COUNT(*) as cnt, AVG(confidence) as avg_conf FROM lessons "
            "WHERE domain = ? AND validity = 'valid'",
            (domain,)
        ).fetchone()
        if not row:
            return 1.5  # Unknown domain — learn fast
        cnt = int(row["cnt"] or 0)
        avg_conf = float(row["avg_conf"] or 0.5)
        # Mature: many items AND high confidence → slow learning
        if cnt >= 20 and avg_conf >= 0.7:
            return 0.5
        # Intermediate domain
        if cnt >= 10 or avg_conf >= 0.5:
            return 1.0
        # New/sparse domain — learn fast
        return 1.5
    except Exception:
        return 1.0  # fail-open


def apply_stdp_with_annealing(session_id: str, success: bool, domain: str = "") -> int:
    """Apply STDP with per-domain learning rate annealing (Item 121).

    Wraps apply_stdp() and scales the global LTP/LTD deltas by domain maturity
    before calling the core algorithm. Restores original deltas afterward.
    """
    try:
        import pulse.brain.plasticity.stdp as _stdp
        factor = _get_domain_maturity_factor(domain)
        with _anneal_lock:
            old_ltp = _stdp.LTP_DELTA
            old_ltd = _stdp.LTD_DELTA
            try:
                _stdp.LTP_DELTA = round(old_ltp * factor, 5)
                _stdp.LTD_DELTA = round(old_ltd * factor, 5)
                return _stdp.apply_stdp(session_id, success)
            except Exception as e:
                log.debug("apply_stdp_with_annealing inner failed: %s", e)
                return 0
            finally:
                _stdp.LTP_DELTA = old_ltp
                _stdp.LTD_DELTA = old_ltd
    except Exception as e:
        log.debug("apply_stdp_with_annealing outer failed: %s", e)
        return 0  # fail-open


# ---------------------------------------------------------------------------
# Item 139: Error correction signal strength (P33)
# ---------------------------------------------------------------------------
def compute_failure_ltd(base_delta: float) -> float:
    """Make LTD proportional to how surprising the failure was.
    Expected failures (high failure rate domain) get mild weakening.
    Unexpected failures get strong weakening.
    Returns a negative delta.
    """
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        row = conn.execute(
            "SELECT COUNT(*) as total, "
            "SUM(CASE WHEN type='failure' THEN 1 ELSE 0 END) as fails "
            "FROM lessons WHERE timestamp >= datetime('now','-7 days')"
        ).fetchone()
        if row and row[0] and int(row[0]) > 0:
            failure_rate = float(row[1] or 0) / float(row[0])
        else:
            failure_rate = 0.3
        surprise = max(0.1, 1.0 - failure_rate)
        return round(base_delta * surprise, 4)  # base_delta negative (LTD)
    except Exception:
        return base_delta  # fail-open
