/**
 * Agent Coordination System
 *
 * Swarm topologies and consensus protocols for multi-agent coordination.
 */
import { PhantomWorker } from '../phantom/index.js';
import { randomUUID } from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
export class AgentCoordinator {
    agents = new Map();
    topology;
    consensus;
    consensusState;
    constructor(topology = 'hierarchical', consensus = 'raft') {
        this.topology = topology;
        this.consensus = consensus;
        this.consensusState = {
            type: consensus,
            term: 0,
            nodes: new Map()
        };
    }
    // ==================== AGENT MANAGEMENT ====================
    /**
     * Register an agent
     */
    register(agent) {
        this.agents.set(agent.id, agent);
        // Add to consensus
        this.consensusState.nodes.set(agent.id, {
            id: agent.id,
            status: 'active',
            lastSeen: Date.now()
        });
    }
    /**
     * Unregister an agent
     */
    unregister(agentId) {
        this.agents.delete(agentId);
        this.consensusState.nodes.get(agentId).status = 'failed';
    }
    /**
     * Get agent
     */
    getAgent(agentId) {
        return this.agents.get(agentId);
    }
    /**
     * Get all agents
     */
    getAllAgents() {
        return Array.from(this.agents.values());
    }
    /**
     * Get agents by type
     */
    getAgentsByType(type) {
        return Array.from(this.agents.values()).filter(a => a.type === type);
    }
    // ==================== TOPOLOGY ====================
    /**
     * Get neighbors for an agent based on topology
     */
    getNeighbors(agentId) {
        switch (this.topology) {
            case 'peer':
                return this.getPeerNeighbors(agentId);
            case 'hierarchical':
                return this.getHierarchicalNeighbors(agentId);
            case 'ring':
                return this.getRingNeighbors(agentId);
            case 'star':
                return this.getStarNeighbors(agentId);
            default:
                return [];
        }
    }
    getPeerNeighbors(agentId) {
        // All other agents are neighbors (mesh)
        return Array.from(this.agents.keys()).filter(id => id !== agentId);
    }
    getHierarchicalNeighbors(agentId) {
        // Queen coordinates workers
        // Workers report to queen
        const agent = this.agents.get(agentId);
        if (!agent)
            return [];
        // For now, just return all other agents
        return Array.from(this.agents.keys()).filter(id => id !== agentId);
    }
    getRingNeighbors(agentId) {
        const ids = Array.from(this.agents.keys());
        const idx = ids.indexOf(agentId);
        if (idx === -1)
            return [];
        // Previous and next in ring
        const prev = idx - 1 < 0 ? ids.length - 1 : idx - 1;
        const next = idx + 1 >= ids.length ? 0 : idx + 1;
        return [ids[prev], ids[next]];
    }
    getStarNeighbors(agentId) {
        const ids = Array.from(this.agents.keys());
        const hub = ids[0]; // First agent is hub
        if (agentId === hub) {
            // Hub sees all spokes
            return ids.slice(1);
        }
        // Spoke sees only hub
        return [hub];
    }
    // ==================== COORDINATION ====================
    /**
     * Coordinate task across agents
     */
    async coordinate(task, agents) {
        const results = [];
        switch (this.topology) {
            case 'hierarchical':
                // Queen delegates to workers
                results.push(...await this.hierarchicalCoordination(task, agents));
                break;
            case 'peer':
                // All agents work in parallel
                results.push(...await this.parallelCoordination(task, agents));
                break;
            case 'ring':
                // Sequential passing
                results.push(...await this.ringCoordination(task, agents));
                break;
            case 'star':
                // Hub delegates
                results.push(...await this.starCoordination(task, agents));
                break;
        }
        return results;
    }
    async hierarchicalCoordination(task, agents) {
        // First agent is coordinator
        const coordinator = agents[0];
        const workers = agents.slice(1);
        // Coordinator distributes work
        const workChunks = this.distributeWork(task, workers.length);
        // Workers execute in parallel
        const workerResults = await Promise.all(workers.map((worker, i) => this.simulateWork(worker, workChunks[i])));
        // Coordinator aggregates
        return [
            { agentId: coordinator, result: { aggregated: workerResults } },
            ...workerResults
        ];
    }
    async parallelCoordination(task, agents) {
        const workChunks = this.distributeWork(task, agents.length);
        return Promise.all(agents.map((agent, i) => this.simulateWork(agent, workChunks[i])));
    }
    async ringCoordination(task, agents) {
        const results = [];
        let currentTask = task;
        for (const agent of agents) {
            const result = await this.simulateWork(agent, currentTask);
            results.push(result);
            currentTask = `processed_${result.result}`;
        }
        return results;
    }
    async starCoordination(task, agents) {
        const hub = agents[0];
        const spokes = agents.slice(1);
        // Hub distributes to spokes
        const workChunks = this.distributeWork(task, spokes.length);
        const spokeResults = await Promise.all(spokes.map((spoke, i) => this.simulateWork(spoke, workChunks[i])));
        // Hub aggregates
        return [
            { agentId: hub, result: { aggregated: spokeResults } },
            ...spokeResults
        ];
    }
    distributeWork(task, numChunks) {
        // Simple distribution: split by words
        const words = task.split(/\s+/);
        const chunkSize = Math.ceil(words.length / numChunks);
        const chunks = [];
        for (let i = 0; i < numChunks; i++) {
            const start = i * chunkSize;
            const end = start + chunkSize;
            chunks.push(words.slice(start, end).join(' '));
        }
        return chunks;
    }
    async simulateWork(agentId, work) {
        const worker = new PhantomWorker();
        const task = {
            id: randomUUID(),
            goal: work,
            files: [],
            approach: 'standard',
            tokenBudget: 50000,
        };
        try {
            const result = await worker.spawn(task, async (worktreeDir, workerTask, w) => {
                const learnings = [];
                // Scan the worktree for relevant source files
                const srcDir = path.join(worktreeDir, 'src');
                const filesToRead = [];
                try {
                    const entries = fs.readdirSync(srcDir, { withFileTypes: true });
                    for (const entry of entries) {
                        if (entry.isFile() && /\.[jt]s$/.test(entry.name)) {
                            filesToRead.push(path.join(srcDir, entry.name));
                        }
                    }
                }
                catch { /* worktree may not have src/ */ }
                // Read first 500 bytes of up to 5 files for context
                for (const filePath of filesToRead.slice(0, 5)) {
                    try {
                        const content = fs.readFileSync(filePath, 'utf-8').slice(0, 500);
                        const fileName = path.basename(filePath);
                        const exports = (content.match(/export\s+(class|function|const|interface)\s+\w+/g) || []);
                        learnings.push(`[${agentId}] Analyzed ${fileName}: ${exports.length} exports, ${content.length} chars read`);
                    }
                    catch {
                        learnings.push(`[${agentId}] Could not read ${path.basename(filePath)}`);
                    }
                }
                // Broadcast findings to POD network
                w.broadcast(`Agent ${agentId} analyzed ${filesToRead.length} files for: ${workerTask.goal.slice(0, 60)}`, 0.7, ['#coordinator', '#agent-work']);
                // Check peer findings
                const peerFindings = w.receive(['#coordinator']);
                if (peerFindings.length > 0) {
                    learnings.push(`[${agentId}] Received ${peerFindings.length} peer findings`);
                }
                return {
                    learnings: learnings.length > 0 ? learnings : [`[${agentId}] No source files found in worktree`],
                    confidence: learnings.length > 2 ? 0.85 : 0.6,
                };
            });
            return {
                agentId,
                result: {
                    diff: result.diff,
                    outcome: result.outcome,
                    learnings: result.learnings,
                }
            };
        }
        catch (err) {
            return {
                agentId,
                result: { outcome: 'failed', error: String(err) }
            };
        }
    }
    // ==================== CONSENSUS ====================
    /**
     * Achieve consensus on a decision
     */
    async consensusDecide(proposal, agents) {
        switch (this.consensus) {
            case 'raft':
                return this.raftConsensus(proposal, agents);
            case 'bft':
                return this.bftConsensus(proposal, agents);
            case 'gossip':
                return this.gossipConsensus(proposal, agents);
            case 'crdt':
                return this.crdtConsensus(proposal, agents);
            default:
                return { decided: false };
        }
    }
    async raftConsensus(proposal, agents) {
        // Simple leader-based consensus
        const leader = this.consensusState.leader ?? agents[0];
        // Leader proposes
        const votes = await this.requestVotes(leader, proposal, agents);
        // Majority wins
        const majority = Math.ceil(agents.length / 2);
        const agree = votes.filter(v => v).length;
        if (agree >= majority) {
            return { decided: true, result: proposal };
        }
        return { decided: false };
    }
    async bftConsensus(proposal, agents) {
        // Byzantine fault-tolerant: need 2/3 agreement
        const votes = await this.requestVotes('leader', proposal, agents);
        const byzantineThreshold = Math.ceil(agents.length * 2 / 3);
        const agree = votes.filter(v => v).length;
        if (agree >= byzantineThreshold) {
            return { decided: true, result: proposal };
        }
        return { decided: false };
    }
    async gossipConsensus(proposal, agents) {
        // Real gossip: agents exchange proposals via POD Network.
        // Each round, agents can modify the proposal based on their task context.
        // Converge when the majority holds the same value for 2 consecutive rounds.
        const proposals = new Map();
        agents.forEach(a => proposals.set(a, proposal));
        let stableRounds = 0;
        let lastMajority = '';
        for (let round = 0; round < 5; round++) {
            // Each agent gossips with a random neighbor
            for (const agent of agents) {
                const neighborIdx = Math.floor(Math.random() * agents.length);
                const neighbor = agents[neighborIdx];
                // Adopt neighbor's proposal if it's more similar to the original goal OR more detailed
                const myProposal = proposals.get(agent) || proposal;
                const neighborProposal = proposals.get(neighbor) || proposal;
                // Simple similarity-weighted length adoption
                const mySim = this.calculateSimilarity(myProposal, proposal);
                const neighborSim = this.calculateSimilarity(neighborProposal, proposal);
                if (neighborSim > mySim || (neighborSim === mySim && neighborProposal.length > myProposal.length)) {
                    proposals.set(agent, neighborProposal);
                }
            }
            // Check convergence: find majority value
            const counts = new Map();
            for (const val of proposals.values()) {
                counts.set(val, (counts.get(val) || 0) + 1);
            }
            let majority = proposal;
            let maxCount = 0;
            for (const [val, cnt] of counts) {
                if (cnt > maxCount) {
                    maxCount = cnt;
                    majority = val;
                }
            }
            if (majority === lastMajority) {
                stableRounds++;
            }
            else {
                stableRounds = 0;
                lastMajority = majority;
            }
            // Converged if majority is stable for 2 rounds
            if (stableRounds >= 1 && maxCount > agents.length / 2) {
                return { decided: true, result: majority };
            }
        }
        return { decided: stableRounds >= 1, result: lastMajority || proposal };
    }
    async crdtConsensus(proposal, agents) {
        // G-Counter CRDT: each agent has a counter. Merge by taking max per agent.
        // Consensus when total count exceeds N/2 (majority of agents voted).
        const counters = new Map();
        agents.forEach(a => counters.set(a, 0));
        // Each agent evaluates the proposal against its task using Jaccard similarity
        for (const agentId of agents) {
            const agent = this.agents.get(agentId);
            const agentTask = agent?.state?.current || agentId;
            const similarity = this.calculateSimilarity(proposal, agentTask);
            // Increment counter if similarity indicates high relevance
            if (similarity > 0.25) {
                counters.set(agentId, 1);
            }
        }
        // Merge: sum all counters
        let total = 0;
        for (const v of counters.values())
            total += v;
        const decided = total > agents.length / 2;
        return { decided, result: decided ? proposal : undefined };
    }
    async requestVotes(proposer, proposal, agents) {
        return agents.map(agentId => {
            const agent = this.agents.get(agentId);
            const agentTask = agent?.state?.current || agentId;
            const similarity = this.calculateSimilarity(proposal, agentTask);
            // Vote YES if proposal is strongly relevant to this agent's task
            return similarity > 0.2;
        });
    }
    calculateSimilarity(str1, str2) {
        const words1 = new Set(str1.toLowerCase().split(/\s+/).filter(w => w.length > 2));
        const words2 = new Set(str2.toLowerCase().split(/\s+/).filter(w => w.length > 2));
        const intersection = [...words1].filter(w => words2.has(w)).length;
        const union = new Set([...words1, ...words2]).size;
        return union > 0 ? intersection / union : 0;
    }
    // ==================== STATE ====================
    getTopology() {
        return this.topology;
    }
    setTopology(topology) {
        this.topology = topology;
    }
    getConsensus() {
        return this.consensus;
    }
    setConsensus(consensus) {
        this.consensus = consensus;
        this.consensusState.type = consensus;
    }
    getConsensusState() {
        return { ...this.consensusState };
    }
}
// ==================== FACTORY ====================
export const createCoordinator = (topology, consensus) => new AgentCoordinator(topology, consensus);
