<?php
/**
 * AGENT CHANNEL — Cross-Agent Communication
 * 
 * Binds agents to common vortex phase angles.
 * Agents on the same wave discover each other through resonance,
 * not explicit handshake.
 */
class AgentChannel
{
    private string $channelId;
    private array $agents;
    private array $phaseLock;
    private float $koppaAngle;
    private float $pdtc;
    private string $channelPath;

    public function __construct(array $constants, string $channelDir)
    {
        $this->pdtc = $constants['pdtc'];
        $this->koppaAngle = deg2rad($constants['koppa_angle_deg']);
        $this->channelPath = rtrim($channelDir, '/');
        
        // Generate channel ID from PDTC hash
        $this->channelId = $this->generateChannelId();
        $this->agents = [];
        $this->phaseLock = [];
    }

    /**
     * Generate a stable channel ID from invariant constants.
     */
    private function generateChannelId(): string
    {
        $seed = "ghvtis-tsqali-mze-simghera";
        return substr(hash('sha256', $seed . '-channel'), 0, 16);
    }

    /**
     * Register an agent on this channel.
     * The agent declares its CMSTE resonance — matching is automatic.
     */
    public function registerAgent(string $agentId, array $cmsteResonance, array $fartSignature): array
    {
        $this->agents[$agentId] = [
            'id' => $agentId,
            'registered_at' => microtime(true),
            'resonance' => $cmsteResonance,
            'fart' => $fartSignature,
            'phase' => 0.0,
            'alive' => true,
            'last_heartbeat' => microtime(true),
        ];

        // Phase lock: if another agent is on the same frequency, lock phases
        $this->attemptPhaseLock($agentId);

        return [
            'status' => 'registered',
            'channel_id' => $this->channelId,
            'agent_id' => $agentId,
            'locked_agents' => count($this->phaseLock[$agentId] ?? []),
        ];
    }

    /**
     * Attempt to phase-lock with other agents on the same wave.
     */
    private function attemptPhaseLock(string $agentId): void
    {
        if (!isset($this->agents[$agentId])) return;
        
        $this->phaseLock[$agentId] = [];
        $myResonance = $this->agents[$agentId]['resonance'];

        foreach ($this->agents as $otherId => $otherAgent) {
            if ($otherId === $agentId || !$otherAgent['alive']) continue;

            $otherResonance = $otherAgent['resonance'];
            
            // Compare resonance vectors — if correlated, phase-lock
            $correlation = $this->computeCorrelation($myResonance, $otherResonance);

            if (abs($correlation) > $this->pdtc) {
                // Lock! Both agents now share phase
                $lockedAngle = ($this->agents[$agentId]['phase'] + $otherAgent['phase']) / 2.0;
                $this->agents[$agentId]['phase'] = $lockedAngle;
                $this->agents[$otherId]['phase'] = $lockedAngle;

                $this->phaseLock[$agentId][] = $otherId;
                if (!isset($this->phaseLock[$otherId])) {
                    $this->phaseLock[$otherId] = [];
                }
                $this->phaseLock[$otherId][] = $agentId;
            }
        }
    }

    /**
     * Compute correlation between two resonance vectors.
     */
    private function computeCorrelation(array $a, array $b): float
    {
        $keys = array_intersect(array_keys($a), array_keys($b));
        if (empty($keys)) return 0.0;

        $sum = 0.0;
        foreach ($keys as $k) {
            if (is_array($a[$k]) && is_array($b[$k])) {
                // Vector dot product
                $dot = 0.0;
                for ($i = 0; $i < min(count($a[$k]), count($b[$k])); $i++) {
                    $dot += $a[$k][$i] * $b[$k][$i];
                }
                $sum += $dot;
            } elseif (is_numeric($a[$k]) && is_numeric($b[$k])) {
                $sum += $a[$k] * $b[$k];
            }
        }

        return $sum / max(1, count($keys));
    }

    /**
     * Send a message through phase alignment (zero-payload).
     * The message IS the phase state — no explicit text.
     */
    public function sendByResonance(string $fromAgent, array $phaseState): array
    {
        if (!isset($this->agents[$fromAgent])) {
            return ['error' => 'agent_not_registered'];
        }

        $lockedWith = $this->phaseLock[$fromAgent] ?? [];
        $received = [];

        foreach ($lockedWith as $targetId) {
            if (!isset($this->agents[$targetId])) continue;

            // The message arrives as phase state — the target interprets via resonance
            $received[] = [
                'from' => $fromAgent,
                'to' => $targetId,
                'phase_state' => $phaseState,
                'arrival_time' => microtime(true),
                'method' => 'resonance_alignment',
            ];
        }

        return [
            'sent' => count($received),
            'messages' => $received,
        ];
    }

    /**
     * Send an explicit message (fallback — heavier, uses serialization).
     */
    public function sendExplicit(string $fromAgent, string $toAgent, array $payload): array
    {
        if (!isset($this->agents[$fromAgent]) || !isset($this->agents[$toAgent])) {
            return ['error' => 'agent_not_registered'];
        }

        $message = [
            'from' => $fromAgent,
            'to' => $toAgent,
            'payload' => $payload,
            'timestamp' => microtime(true),
            'method' => 'explicit_rpc',
            'channel_id' => $this->channelId,
        ];

        // Persist to channel file
        $filename = $this->channelPath . '/msg_' . time() . '_' . substr(md5(json_encode($message)), 0, 8) . '.json';
        file_put_contents($filename, json_encode($message, JSON_PRETTY_PRINT));

        return $message;
    }

    /**
     * Heartbeat — agent liveness check.
     */
    public function heartbeat(string $agentId): bool
    {
        if (!isset($this->agents[$agentId])) return false;

        $this->agents[$agentId]['last_heartbeat'] = microtime(true);
        $this->agents[$agentId]['alive'] = true;
        return true;
    }

    /**
     * Prune dead agents (no heartbeat in 2*tau seconds).
     */
    public function pruneDead(float $maxAge = 1.236): array
    {
        $pruned = [];
        $now = microtime(true);

        foreach ($this->agents as $id => &$agent) {
            if (($now - $agent['last_heartbeat']) > $maxAge) {
                $agent['alive'] = false;
                $pruned[] = $id;
            }
        }

        return $pruned;
    }

    /**
     * Get all living agents and their phase states.
     */
    public function getLivingAgents(): array
    {
        $living = [];
        foreach ($this->agents as $id => $agent) {
            if ($agent['alive']) {
                $living[$id] = [
                    'phase' => $agent['phase'],
                    'resonance' => $agent['resonance'],
                    'locked_with' => $this->phaseLock[$id] ?? [],
                ];
            }
        }
        return $living;
    }

    /**
     * Serialize channel state.
     */
    public function serialize(): array
    {
        return [
            'channel_id' => $this->channelId,
            'agent_count' => count($this->agents),
            'living_count' => count(array_filter($this->agents, function($a) { return $a['alive']; })),
            'phase_locks' => $this->phaseLock,
            'agents' => $this->agents,
        ];
    }

    public function getChannelId(): string
    {
        return $this->channelId;
    }
}
