"""Swarm layer — agent identity and provenance on top of BFF soup dynamics.

Thin wrapper over the existing concatenate-execute-split interaction loop.
Adds UUID-based identity, lineage tracking, and diversity monitoring via
the calibrated CrystallizationProbe.
"""

from dataclasses import dataclass, field
from uuid import uuid4

import numpy as np

from .interpreter import execute
from .probe import CrystallizationProbe, ProbeReading


@dataclass
class Provenance:
    """Full lineage of an agent."""
    agent_id: str
    parent_ids: list[str] = field(default_factory=list)
    merge_interaction: int = 0
    depth: int = 0
    created_at: int = 0


class Agent:
    """A named tape in the swarm."""

    def __init__(self, tape: np.ndarray, name: str | None = None):
        self.id: str = uuid4().hex[:12]
        self.name: str = name or f"agent-{self.id[:6]}"
        self.tape: np.ndarray = tape.copy()
        self.provenance = Provenance(
            agent_id=self.id,
            parent_ids=[],
            merge_interaction=0,
            depth=0,
            created_at=0,
        )
        self.interaction_count: int = 0


class KTermFusion:
    """Inter-agent fusion using concatenate-execute-split.

    Reuses bff.interpreter.execute() directly — same mechanics as Soup.interact().
    """

    def __init__(self, max_steps: int = 10_000):
        self.max_steps = max_steps

    def fuse(self, agent_a: Agent, agent_b: Agent,
             interaction: int) -> tuple[int, bool]:
        """Fuse two agents. Modifies tapes in-place.

        Returns (ops_count, depth_increased).
        """
        tape_len = len(agent_a.tape)

        # Concatenate two agent tapes into a single working tape
        combined = bytearray(
            bytes(agent_a.tape) + bytes(agent_b.tape)
        )

        # Execute — identical to Soup.interact()
        combined, ops = execute(combined, max_steps=self.max_steps)

        # Split back and write home
        agent_a.tape = np.frombuffer(
            combined[:tape_len], dtype=np.uint8
        ).copy()
        agent_b.tape = np.frombuffer(
            combined[tape_len:], dtype=np.uint8
        ).copy()

        # Update interaction counts
        agent_a.interaction_count += 1
        agent_b.interaction_count += 1

        # Depth tracking — same logic as Soup
        parent_depth = max(agent_a.provenance.depth, agent_b.provenance.depth)
        new_depth = parent_depth + 1 if ops > 0 else parent_depth
        depth_increased = ops > 0

        # Update provenance for both agents
        agent_a.provenance = Provenance(
            agent_id=agent_a.id,
            parent_ids=[agent_a.id, agent_b.id],
            merge_interaction=interaction,
            depth=new_depth,
            created_at=agent_a.provenance.created_at,
        )
        agent_b.provenance = Provenance(
            agent_id=agent_b.id,
            parent_ids=[agent_a.id, agent_b.id],
            merge_interaction=interaction,
            depth=new_depth,
            created_at=agent_b.provenance.created_at,
        )

        return ops, depth_increased


class DiversityProbe:
    """Swarm-level diversity monitor wrapping CrystallizationProbe."""

    def __init__(self, window_size: int = 3,
                 critical_threshold: float = -0.001):
        self._probe = CrystallizationProbe(
            window_size=window_size,
            critical_threshold=critical_threshold,
        )

    def measure(self, agents: list[Agent], interaction: int) -> ProbeReading:
        """Stack agent tapes into a soup-shaped array, delegate to probe."""
        soup = np.stack([a.tape for a in agents])
        return self._probe.measure(soup, interaction)

    def get_history(self) -> list[ProbeReading]:
        """Return all probe readings taken so far."""
        return self._probe.get_history()
