"""
Phase 3A: Global Workspace with Competitive Dynamics (Baars/Dehaene)

Recurrent amplification + lateral inhibition replaces soft top-3 filter.
Sacred Boundary #3: suppression audit trail — logs what was rejected and why.
"""
import json
from datetime import datetime, timezone

from pulse.brain.workspace.specialists import (
    get_memory_coalition, get_prediction_coalition, get_dmn_coalition,
    get_reflex_coalition, get_neural_state_coalition, get_working_memory_coalition,
    get_governance_coalition
)
from pulse.core import interoception
from pulse.core import event_bus

# --- Phase C Item 16: Phenomenal State ConsciousnessEvent ---
from dataclasses import dataclass, asdict
import uuid

@dataclass
class ConsciousnessEvent:
    id: str
    timestamp: str
    agent: str
    query: str
    task_type: str
    system_load: float
    system_entropy: float
    winning_sources: list[str]
    items: list[dict]
    requires_human_review: bool = False

    def to_dict(self):
        return asdict(self)

from pulse.core.paths import get_state_dir

_SUPPRESSION_LOG = get_state_dir() / "workspace_suppression.jsonl"

# Competition constants (Phase 3 AGI refinement)
RECURRENT_CYCLES = 3       # Amplification cycles
INHIBITION_FACTOR = 0.7    # Losers multiplied by this per cycle
IGNITION_GAP = 0.15        # Winner must exceed runner-up by this margin
MAX_BROADCAST_ITEMS = 12   # Miller's 7+/-2 for total broadcast

def compute_dynamic_threshold() -> float:
    """Returns 0.5 under high load, else 0.3"""
    interoception.load_signals()
    load = interoception._signals.get("agent_load", {}).get("current_value", 0.0)
    return 0.5 if load > 5.0 else 0.3

def run_workspace_ignition(agent: str, query: str, search_results: dict, task_type: str = "general") -> dict:
    """Competitive workspace ignition with recurrent amplification.

    1. All 7 specialists submit coalitions with activation scores
    2. RECURRENT_CYCLES of amplification: winner self-excites, losers inhibited
    3. Ignition check: winner must exceed runner-up by IGNITION_GAP
    4. Broadcast winning items, proportional slot allocation
    """
    threshold = compute_dynamic_threshold()
    coalitions = [
        get_memory_coalition(query, search_results, task_type),
        get_prediction_coalition(),
        get_dmn_coalition(),
        get_reflex_coalition(),
        get_neural_state_coalition(),
        get_working_memory_coalition(agent),
        get_governance_coalition()
    ]
    # Governance always passes threshold but can be inhibited
    for c in coalitions:
        if c.source == "governance" and c.items:
            c.activation = max(c.activation, threshold + 0.01)

    # Phase 3: Recurrent competition loop (Dehaene global workspace dynamics)
    for cycle in range(RECURRENT_CYCLES):
        coalitions.sort(key=lambda x: x.activation, reverse=True)
        if len(coalitions) < 2:
            break
        winner = coalitions[0]
        winner.activation = min(1.0, winner.activation + 0.1)  # Self-excitation
        for c in coalitions[1:]:
            if c.source == "governance" and c.items:
                c.activation = max(threshold + 0.01, c.activation * INHIBITION_FACTOR)
            else:
                c.activation *= INHIBITION_FACTOR  # Lateral inhibition

    # Ignition check
    coalitions.sort(key=lambda x: x.activation, reverse=True)
    valid = [c for c in coalitions if c.activation >= threshold or c.source == "governance"]
    if len(valid) >= 2:
        gap = valid[0].activation - valid[1].activation
        if gap >= IGNITION_GAP:
            winners = [c for c in valid if c.activation >= threshold][:3]
        else:
            winners = valid[:2]  # Weak ignition: blend top-2
    else:
        winners = valid[:1]

    # Sacred Boundary #3: log suppressed candidates (append-only audit trail)
    rejected = [c for c in coalitions if c not in valid]
    suppressed = [c for c in valid if c not in winners]
    if rejected or suppressed:
        try:
            entry = {
                "ts": datetime.now(timezone.utc).isoformat(),
                "agent": agent, "query": query[:200], "threshold": threshold,
                "rejected": [{"src": c.source, "act": round(c.activation, 3),
                              "n": len(c.items), "reason": "below_threshold"} for c in rejected],
                "suppressed": [{"src": c.source, "act": round(c.activation, 3),
                                "n": len(c.items), "reason": "below_top3"} for c in suppressed],
                "winners": [c.source for c in winners],
            }
            _SUPPRESSION_LOG.parent.mkdir(parents=True, exist_ok=True)
            with open(_SUPPRESSION_LOG, "a", encoding="utf-8") as f:
                f.write(json.dumps(entry) + "\n")
        except Exception:
            pass  # Never break workspace over audit logging

    # Proportional slot allocation: winners get items proportional to activation
    broadcast_items = []
    total_act = sum(w.activation for w in winners) or 1.0
    for w in winners:
        slots = max(1, int(MAX_BROADCAST_ITEMS * w.activation / total_act))
        for item in w.items[:slots]:
            item["_gw_source"] = w.source
            item["_gw_activation"] = round(w.activation, 3)
            broadcast_items.append(item)
    broadcast_items.sort(key=lambda x: x.get("_gw_activation", 0), reverse=True)
    broadcast_items = broadcast_items[:MAX_BROADCAST_ITEMS]

    # Skip empty broadcasts early
    if not broadcast_items:
        return {"agent": agent, "query": query, "task_type": task_type, "winning_sources": [], "items": []}

    # Calculate entropy based on activation spread
    entropy = 0.0
    try:
        import math
        acts = [w.activation / total_act for w in winners if w.activation > 0]
        entropy = -sum(p * math.log2(p) for p in acts) if acts else 0.0
    except Exception:
        pass

    interoception.load_signals()
    load = interoception._signals.get("agent_load", {}).get("current_value", 0.0)

    broadcast_event = ConsciousnessEvent(
        id=uuid.uuid4().hex[:8],
        timestamp=datetime.now(timezone.utc).isoformat(),
        agent=agent,
        query=query,
        task_type=task_type,
        system_load=load,
        system_entropy=round(entropy, 3),
        winning_sources=[w.source for w in winners],
        items=broadcast_items
    )
    broadcast = broadcast_event.to_dict()

    # Recurrent loop: reinforce winning items in SQLite (DMN / Predictions)
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        for item in broadcast_items:
            item_id = item.get("id") or item.get("item_id")
            if item_id:
                conn.execute("UPDATE lessons SET access_count = access_count + 1 WHERE id = ?", (item_id,))
                conn.execute("UPDATE failures SET access_count = access_count + 1 WHERE id = ?", (item_id,))
                conn.execute("UPDATE patterns SET access_count = access_count + 1 WHERE id = ?", (item_id,))
        conn.commit()
    except Exception:
        pass

    # Gentleness Principle: flag low-confidence broadcasts for human review
    try:
        confidences = [item.get("confidence", 1.0) for item in broadcast_items if isinstance(item, dict)]
        if confidences:
            avg_conf = sum(confidences) / len(confidences)
            if avg_conf < 0.7:
                broadcast["requires_human_review"] = True
    except Exception:
        pass

    event_bus.publish("workspace.broadcast", "global_workspace", broadcast)

    # Wire S6-1: GWT broadcast to specialist coalitions
    try:
        from pulse.brain.workspace.specialists import gwt_broadcast
        winning_coalition = winners[0] if winners else None
        gwt_broadcast(broadcast, winning_coalition)
    except Exception:
        pass

    return broadcast
