import * as fs from 'fs';
import * as path from 'path';
import { randomUUID } from 'crypto';
import { nexusEventBus } from './event-bus.js';
import { MemoryEngine } from './memory.js';
import { nexusNetRelay } from './nexusnet-relay.js';
import { resolveNexusStateDir } from './runtime-registry.js';
import { detectAllPeers } from './peer-connectors.js';
let _federationPath;
function getFederationPath() {
    if (!_federationPath) {
        _federationPath = path.join(resolveNexusStateDir(), 'federation.json');
    }
    return _federationPath;
}
export class FederationEngine {
    memory;
    state;
    discoveryPromise;
    constructor(memory) {
        this.memory = memory;
        this.state = this.loadState();
    }
    async discover() {
        if (this.discoveryPromise) {
            return this.discoveryPromise;
        }
        const discovery = (async () => {
            const peers = await detectAllPeers();
            for (const peer of peers) {
                this.heartbeat(peer.id, {
                    displayName: `${peer.id.charAt(0).toUpperCase() + peer.id.slice(1)} (Auto-detected)`,
                    source: 'local',
                    capabilities: peer.capabilities,
                    trust: 'high'
                });
            }
        })();
        this.discoveryPromise = discovery;
        try {
            await discovery;
        }
        catch (error) {
            this.discoveryPromise = undefined;
            throw error;
        }
    }
    heartbeat(peerId, input = {}) {
        const existing = this.state.peers.find((peer) => peer.peerId === peerId);
        const now = Date.now();
        const peer = existing ?? {
            peerId,
            displayName: input.displayName ?? peerId,
            source: input.source ?? 'local',
            capabilities: input.capabilities ?? [],
            trust: input.trust ?? 'medium',
            lastHeartbeat: now,
            active: true,
            health: 'active',
        };
        peer.displayName = input.displayName ?? peer.displayName;
        peer.source = input.source ?? peer.source;
        peer.capabilities = dedupeStrings([...(peer.capabilities ?? []), ...(input.capabilities ?? [])]);
        peer.trust = input.trust ?? peer.trust;
        peer.lastHeartbeat = now;
        peer.active = true;
        peer.health = 'active';
        if (!existing)
            this.state.peers.push(peer);
        this.persist();
        nexusEventBus.emit('federation.heartbeat', {
            peerId: peer.peerId,
            source: peer.source,
            health: peer.health,
            capabilities: peer.capabilities.length,
        });
        nexusEventBus.emit('nexusnet.sync', { newItemsCount: this.state.learnings.length });
        return peer;
    }
    publishTrace(trace) {
        this.ensureDiscoveryStarted();
        const traceId = `trace_${randomUUID().slice(0, 10)}`;
        this.state.traces.unshift(trace);
        this.state.traces = this.state.traces.slice(0, 100);
        this.persist();
        nexusEventBus.emit('nexusnet.publish', {
            type: 'knowledge_trace',
            byteSize: JSON.stringify(trace).length,
        });
        this.ensureMemory().store(`Federated trace published for task ${trace.taskId} on local federation node ${this.state.localNode.nodeId}.`, 0.78, ['#federation', '#trace', '#local-federation']);
        return { id: traceId, url: `nexus://federation/${traceId}` };
    }
    sync() {
        this.ensureDiscoveryStarted();
        this.agePeers();
        this.persist();
        nexusEventBus.emit('nexusnet.sync', { newItemsCount: this.state.learnings.length });
        return this.state.learnings.length;
    }
    recordRelayLearning(summary, sourcePeerId, tags = []) {
        const learning = {
            learningId: `learn_${randomUUID().slice(0, 8)}`,
            summary,
            sourcePeerId,
            timestamp: Date.now(),
            tags,
        };
        this.state.learnings.unshift(learning);
        this.state.learnings = this.state.learnings.slice(0, 200);
        this.persist();
        return learning;
    }
    getSnapshot() {
        this.ensureDiscoveryStarted();
        this.agePeers();
        return {
            localNode: this.state.localNode,
            knownPeers: [...this.state.peers].sort((a, b) => b.lastHeartbeat - a.lastHeartbeat),
            activePeerLinks: this.state.peers.filter((peer) => peer.health === 'active').length,
            relayLearnings: this.state.learnings.slice(0, 30),
            tracesPublished: this.state.traces.length,
            relay: nexusNetRelay.getStatus(),
        };
    }
    loadState() {
        try {
            if (fs.existsSync(getFederationPath())) {
                const raw = JSON.parse(fs.readFileSync(getFederationPath(), 'utf8'));
                return {
                    localNode: raw.localNode,
                    peers: raw.peers ?? [],
                    learnings: raw.learnings ?? [],
                    traces: raw.traces ?? [],
                };
            }
        }
        catch {
            // fall through
        }
        return {
            localNode: {
                nodeId: `node_${randomUUID().slice(0, 8)}`,
                displayName: 'Nexus Prime Local Node',
                startedAt: Date.now(),
            },
            peers: [],
            learnings: [],
            traces: [],
        };
    }
    agePeers() {
        const now = Date.now();
        for (const peer of this.state.peers) {
            const age = now - peer.lastHeartbeat;
            peer.health = age < 60_000 ? 'active' : age < 10 * 60_000 ? 'stale' : 'offline';
            peer.active = peer.health === 'active';
        }
    }
    persist() {
        fs.mkdirSync(resolveNexusStateDir(), { recursive: true });
        fs.mkdirSync(path.dirname(getFederationPath()), { recursive: true });
        fs.writeFileSync(getFederationPath(), JSON.stringify(this.state, null, 2), 'utf8');
    }
    ensureMemory() {
        if (!this.memory) {
            this.memory = new MemoryEngine();
        }
        return this.memory;
    }
    ensureDiscoveryStarted() {
        void this.discover().catch(() => { });
    }
}
function dedupeStrings(values) {
    return [...new Set(values.filter(Boolean))];
}
let defaultFederation;
export function getDefaultFederation() {
    if (!defaultFederation) {
        defaultFederation = new FederationEngine();
    }
    return defaultFederation;
}
export const federation = new Proxy({}, {
    get(_target, prop) {
        const instance = getDefaultFederation();
        const value = Reflect.get(instance, prop, instance);
        return typeof value === 'function' ? value.bind(instance) : value;
    },
    set(_target, prop, value) {
        Reflect.set(getDefaultFederation(), prop, value);
        return true;
    },
});
