/**
 * Sync Module
 *
 * Provides synchronization capabilities including:
 * - State synchronization between clients/servers
 * - Conflict resolution strategies
 * - State reconciliation
 * - Version tracking and vector clocks
 */
import { logger } from "../utils/logger.js";
import { EventEmitter } from 'events';
import crypto from 'crypto';
import path from 'path';
import { UnifiedVfsRouter } from '../services/vfs/unified-vfs-router.js';
// ============================================================================
// Vector Clock Operations
// ============================================================================
export function createVectorClock(nodeId) {
    return { [nodeId]: 0 };
}
export function incrementVectorClock(clock, nodeId) {
    return {
        ...clock,
        [nodeId]: (clock[nodeId] || 0) + 1,
    };
}
export function mergeVectorClocks(clock1, clock2) {
    const merged = { ...clock1 };
    for (const [nodeId, value] of Object.entries(clock2)) {
        merged[nodeId] = Math.max(merged[nodeId] || 0, value);
    }
    return merged;
}
export function compareVectorClocks(clock1, clock2) {
    let before = false;
    let after = false;
    const allNodes = new Set([
        ...Object.keys(clock1),
        ...Object.keys(clock2),
    ]);
    for (const nodeId of allNodes) {
        const v1 = clock1[nodeId] || 0;
        const v2 = clock2[nodeId] || 0;
        if (v1 < v2)
            before = true;
        if (v1 > v2)
            after = true;
    }
    if (!before && !after)
        return 'equal';
    if (before && !after)
        return 'before';
    if (!before && after)
        return 'after';
    return 'concurrent';
}
export function isVectorClockDominated(clock1, clock2) {
    const comparison = compareVectorClocks(clock1, clock2);
    return comparison === 'before' || comparison === 'equal';
}
// ============================================================================
// State Management
// ============================================================================
export function createSyncState(data, nodeId, existingClock) {
    const vectorClock = existingClock
        ? incrementVectorClock(existingClock, nodeId)
        : createVectorClock(nodeId);
    return {
        id: generateId('state'),
        data,
        version: 1,
        timestamp: Date.now(),
        vectorClock,
        hash: computeHash(data),
        lastModifiedBy: nodeId,
    };
}
export function updateSyncState(state, data, nodeId) {
    return {
        ...state,
        data,
        version: state.version + 1,
        timestamp: Date.now(),
        vectorClock: incrementVectorClock(state.vectorClock, nodeId),
        hash: computeHash(data),
        lastModifiedBy: nodeId,
    };
}
export function computeHash(data) {
    let str;
    if (data === undefined) {
        str = 'undefined';
    }
    else if (typeof data === 'string') {
        str = data;
    }
    else {
        str = JSON.stringify(data);
    }
    return crypto.createHash('sha256').update(str).digest('hex').slice(0, 16);
}
export function generateId(prefix) {
    return `${prefix}_${crypto.randomBytes(8).toString('hex')}`;
}
// ============================================================================
// Conflict Detection and Resolution
// ============================================================================
export function detectConflict(localState, remoteState) {
    if (localState.id !== remoteState.id) {
        return null; // Different states, no conflict
    }
    if (localState.hash === remoteState.hash) {
        return null; // Same content, no conflict
    }
    const comparison = compareVectorClocks(localState.vectorClock, remoteState.vectorClock);
    if (comparison === 'equal' || comparison === 'before' || comparison === 'after') {
        return null; // One dominates the other, no conflict
    }
    return {
        stateId: localState.id,
        localState,
        remoteState,
        conflictType: 'concurrent-update',
    };
}
export class LastWriteWinsStrategy {
    canResolve(_conflict) {
        return true;
    }
    resolve(conflict) {
        return conflict.localState.timestamp >= conflict.remoteState.timestamp
            ? conflict.localState
            : conflict.remoteState;
    }
}
export class LocalWinsStrategy {
    canResolve(_conflict) {
        return true;
    }
    resolve(conflict) {
        return conflict.localState;
    }
}
export class RemoteWinsStrategy {
    canResolve(_conflict) {
        return true;
    }
    resolve(conflict) {
        return conflict.remoteState;
    }
}
export class MergeStrategy {
    canResolve(conflict) {
        return (typeof conflict.localState.data === 'object' &&
            typeof conflict.remoteState.data === 'object' &&
            conflict.localState.data !== null &&
            conflict.remoteState.data !== null);
    }
    resolve(conflict) {
        const mergedData = this.mergeObjects(conflict.localState.data, conflict.remoteState.data);
        return {
            ...conflict.localState,
            data: mergedData,
            version: Math.max(conflict.localState.version, conflict.remoteState.version) + 1,
            timestamp: Date.now(),
            vectorClock: mergeVectorClocks(conflict.localState.vectorClock, conflict.remoteState.vectorClock),
            hash: computeHash(mergedData),
        };
    }
    mergeObjects(local, remote) {
        const result = {};
        const allKeys = new Set([
            ...Object.keys(local),
            ...Object.keys(remote),
        ]);
        for (const key of allKeys) {
            const localVal = local[key];
            const remoteVal = remote[key];
            if (localVal === undefined) {
                result[key] = remoteVal;
            }
            else if (remoteVal === undefined) {
                result[key] = localVal;
            }
            else if (typeof localVal === 'object' &&
                typeof remoteVal === 'object' &&
                localVal !== null &&
                remoteVal !== null &&
                !Array.isArray(localVal) &&
                !Array.isArray(remoteVal)) {
                result[key] = this.mergeObjects(localVal, remoteVal);
            }
            else {
                // For conflicting primitive values, take the local value
                result[key] = localVal;
            }
        }
        return result;
    }
}
// ============================================================================
// Sync Manager
// ============================================================================
const DEFAULT_CONFIG = {
    nodeId: 'default',
    conflictStrategy: 'local-wins',
    autoSync: false,
    syncInterval: 30000,
    maxRetries: 3,
    retryDelay: 1000,
    persistPath: '.codebuddy/sync',
};
export class SyncManager extends EventEmitter {
    config;
    states = new Map();
    pendingOperations = [];
    conflictStrategy;
    status = 'idle';
    syncTimer = null;
    vfs = UnifiedVfsRouter.Instance;
    constructor(config = {}) {
        super();
        this.config = {
            ...DEFAULT_CONFIG,
            nodeId: config.nodeId || generateId('node'),
            ...config,
        };
        // Resolve persist path relative to CWD if not absolute
        if (!path.isAbsolute(this.config.persistPath)) {
            this.config.persistPath = path.join(process.cwd(), this.config.persistPath);
        }
        this.conflictStrategy = this.createStrategy(this.config.conflictStrategy);
        // Load initial state
        this.load().catch(err => logger.error('Failed to load sync state:', err));
        if (this.config.autoSync) {
            this.startAutoSync();
        }
    }
    createStrategy(resolution) {
        switch (resolution) {
            case 'remote-wins':
                return new RemoteWinsStrategy();
            case 'merge':
                // MergeStrategy requires object types, use LastWriteWins as fallback for non-objects
                return new MergeStrategy();
            case 'local-wins':
            default:
                return new LocalWinsStrategy();
        }
    }
    getNodeId() {
        return this.config.nodeId;
    }
    getStatus() {
        return this.status;
    }
    setStatus(status) {
        const previousStatus = this.status;
        this.status = status;
        this.emit('status-changed', { previous: previousStatus, current: status });
    }
    // Persistence
    async save() {
        try {
            await this.vfs.ensureDir(this.config.persistPath);
            const data = {
                nodeId: this.config.nodeId,
                timestamp: Date.now(),
                states: Array.from(this.states.entries()),
                pendingOperations: this.pendingOperations,
            };
            const filePath = path.join(this.config.persistPath, 'state.json');
            await this.vfs.writeFile(filePath, JSON.stringify(data, null, 2));
            this.emit('saved');
        }
        catch (error) {
            this.emit('error', error);
        }
    }
    async load() {
        try {
            const filePath = path.join(this.config.persistPath, 'state.json');
            if (await this.vfs.exists(filePath)) {
                const content = await this.vfs.readFile(filePath);
                const data = JSON.parse(content);
                if (data.states) {
                    this.states = new Map(data.states);
                }
                if (data.pendingOperations) {
                    this.pendingOperations = data.pendingOperations;
                }
                this.emit('loaded');
            }
        }
        catch (error) {
            this.emit('error', error);
        }
    }
    // State operations
    createState(data) {
        const state = createSyncState(data, this.config.nodeId);
        this.states.set(state.id, state);
        this.addOperation('create', state.id, data);
        this.emit('state-created', state);
        this.save(); // Save on change
        return state;
    }
    getState(stateId) {
        return this.states.get(stateId);
    }
    getAllStates() {
        return Array.from(this.states.values());
    }
    updateState(stateId, data) {
        const existing = this.states.get(stateId);
        if (!existing)
            return null;
        const updated = updateSyncState(existing, data, this.config.nodeId);
        this.states.set(stateId, updated);
        this.addOperation('update', stateId, data);
        this.emit('state-updated', updated);
        this.save();
        return updated;
    }
    deleteState(stateId) {
        const existing = this.states.get(stateId);
        if (!existing)
            return false;
        this.states.delete(stateId);
        this.addOperation('delete', stateId);
        this.emit('state-deleted', stateId);
        this.save();
        return true;
    }
    addOperation(type, stateId, data) {
        const operation = {
            id: generateId('op'),
            type,
            stateId,
            data,
            timestamp: Date.now(),
            nodeId: this.config.nodeId,
            vectorClock: this.getCurrentVectorClock(),
        };
        this.pendingOperations.push(operation);
    }
    getCurrentVectorClock() {
        let clock = { [this.config.nodeId]: 0 };
        for (const state of this.states.values()) {
            clock = mergeVectorClocks(clock, state.vectorClock);
        }
        return incrementVectorClock(clock, this.config.nodeId);
    }
    getPendingOperations() {
        return [...this.pendingOperations];
    }
    clearPendingOperations() {
        this.pendingOperations = [];
    }
    // Synchronization
    async applyRemoteState(remoteState) {
        const localState = this.states.get(remoteState.id);
        if (!localState) {
            // New state from remote
            this.states.set(remoteState.id, remoteState);
            this.emit('state-synced', remoteState);
            this.save();
            return { applied: true };
        }
        const conflict = detectConflict(localState, remoteState);
        if (!conflict) {
            // No conflict, check which one is newer
            const comparison = compareVectorClocks(localState.vectorClock, remoteState.vectorClock);
            if (comparison === 'before') {
                // Remote is newer
                this.states.set(remoteState.id, remoteState);
                this.emit('state-synced', remoteState);
                this.save();
                return { applied: true };
            }
            // Local is equal or newer
            return { applied: false };
        }
        // Conflict detected
        this.emit('conflict-detected', conflict);
        if (this.conflictStrategy.canResolve(conflict)) {
            const resolvedState = this.conflictStrategy.resolve(conflict);
            this.states.set(resolvedState.id, resolvedState);
            this.emit('conflict-resolved', { conflict, resolvedState });
            this.save();
            return { applied: true, conflict, resolvedState };
        }
        this.setStatus('conflict');
        return { applied: false, conflict };
    }
    async reconcile(remoteStates) {
        this.setStatus('syncing');
        const reconciledStates = [];
        const conflicts = [];
        try {
            for (const remoteState of remoteStates) {
                const result = await this.applyRemoteState(remoteState);
                if (result.applied) {
                    reconciledStates.push(result.resolvedState || this.states.get(remoteState.id));
                }
                if (result.conflict && !result.resolvedState) {
                    conflicts.push(result.conflict);
                }
            }
            this.setStatus(conflicts.length > 0 ? 'conflict' : 'idle');
            return {
                success: conflicts.length === 0,
                reconciledStates,
                conflicts,
                operations: this.pendingOperations,
            };
        }
        catch (error) {
            this.setStatus('error');
            throw error;
        }
    }
    // Manual conflict resolution
    resolveConflict(conflict, resolution, customData) {
        let resolvedState;
        switch (resolution) {
            case 'local-wins':
                resolvedState = conflict.localState;
                break;
            case 'remote-wins':
                resolvedState = conflict.remoteState;
                break;
            case 'merge':
                if (customData) {
                    resolvedState = {
                        ...conflict.localState,
                        data: customData,
                        version: Math.max(conflict.localState.version, conflict.remoteState.version) + 1,
                        timestamp: Date.now(),
                        vectorClock: mergeVectorClocks(conflict.localState.vectorClock, conflict.remoteState.vectorClock),
                        hash: computeHash(customData),
                        lastModifiedBy: this.config.nodeId,
                    };
                }
                else {
                    const strategy = new MergeStrategy();
                    resolvedState = strategy.resolve(conflict);
                }
                break;
            default:
                resolvedState = conflict.localState;
        }
        this.states.set(resolvedState.id, resolvedState);
        this.emit('conflict-resolved', { conflict, resolvedState });
        this.save();
        if (this.status === 'conflict') {
            this.setStatus('idle');
        }
        return resolvedState;
    }
    // Auto-sync
    startAutoSync() {
        if (this.syncTimer)
            return;
        this.syncTimer = setInterval(() => {
            this.emit('auto-sync-tick');
        }, this.config.syncInterval);
    }
    stopAutoSync() {
        if (this.syncTimer) {
            clearInterval(this.syncTimer);
            this.syncTimer = null;
        }
    }
    // Export/Import
    exportState() {
        return {
            states: this.getAllStates(),
            nodeId: this.config.nodeId,
            timestamp: Date.now(),
        };
    }
    importState(data) {
        for (const state of data.states) {
            this.states.set(state.id, state);
        }
        this.emit('state-imported', data);
    }
    // Cleanup
    dispose() {
        this.stopAutoSync();
        this.states.clear();
        this.pendingOperations = [];
        this.removeAllListeners();
    }
}
// ============================================================================
// Singleton Access
// ============================================================================
let syncManagerInstance = null;
export function getSyncManager(config) {
    if (!syncManagerInstance) {
        syncManagerInstance = new SyncManager(config);
    }
    return syncManagerInstance;
}
export function resetSyncManager() {
    if (syncManagerInstance) {
        syncManagerInstance.dispose();
        syncManagerInstance = null;
    }
}
export default SyncManager;
//# sourceMappingURL=index.js.map