"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.exportMemory = exportMemory;
exports.importMemory = importMemory;
exports.computeDiff = computeDiff;
exports.resolveConflicts = resolveConflicts;
exports.getSyncStatus = getSyncStatus;
exports.markEventsSynced = markEventsSynced;
exports.getUnsyncedEvents = getUnsyncedEvents;
exports.saveSyncSnapshot = saveSyncSnapshot;
exports.getLatestSnapshot = getLatestSnapshot;
const crypto = __importStar(require("crypto"));
const zlib = __importStar(require("zlib"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const database_1 = require("../database/database");
function getArchiveKey() {
    // Use FileKeyProvider as primary (persistent, always available)
    let provider;
    try {
        const { FileKeyProvider } = require('../privacy/key-provider-file');
        provider = new FileKeyProvider();
        const existing = provider.getKey('fivocell-archive');
        if (existing && existing.length === 32)
            return existing;
    }
    catch { /* fallback */ }
    // Fallback to canonical provider (OS-native if available)
    try {
        const { getCanonicalProvider } = require('../privacy/key-provider');
        const canon = getCanonicalProvider();
        const existing = canon.getKey('fivocell-archive');
        if (existing && existing.length === 32)
            return existing;
        provider = canon;
    }
    catch { /* fallback */ }
    const legacyKeyPath = path.join(os.homedir(), '.fivo', 'cell', 'keys', 'local.key');
    if (fs.existsSync(legacyKeyPath)) {
        try {
            const legacyKey = Buffer.from(fs.readFileSync(legacyKeyPath, 'utf-8').trim(), 'hex');
            if (legacyKey.length === 32 && provider) {
                provider.setKey('fivocell-archive', legacyKey);
                try {
                    fs.unlinkSync(legacyKeyPath);
                }
                catch { /* leave it */ }
                return legacyKey;
            }
        }
        catch { /* corrupt legacy key */ }
    }
    const newKey = crypto.randomBytes(32);
    if (provider)
        provider.setKey('fivocell-archive', newKey);
    return newKey;
}
function exportMemory(project) {
    const db = (0, database_1.getDb)();
    const events = db.prepare('SELECT * FROM memory_events WHERE project = ? ORDER BY id').all(project);
    const data = JSON.stringify({
        version: 1,
        project,
        exportedAt: new Date().toISOString(),
        events: events.map(e => ({
            id: e.id, type: e.type, topic: e.topic, summary: e.summary,
            importance: e.importance, files_json: e.files_json,
            details_json: e.details_json, created_at: e.created_at,
            branch: e.branch, tool: e.tool, model: e.model, tags_json: e.tags_json,
        })),
    });
    const compressed = zlib.gzipSync(Buffer.from(data, 'utf-8'));
    const key = getArchiveKey();
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
    const encrypted = Buffer.concat([cipher.update(compressed), cipher.final()]);
    const tag = cipher.getAuthTag();
    const checksum = crypto.createHash('sha256').update(compressed).digest('hex');
    return {
        version: 1,
        algorithm: 'aes-256-gcm',
        project,
        timestamp: new Date().toISOString(),
        checksum,
        encryptedData: encrypted.toString('base64'),
        iv: iv.toString('base64'),
        tag: tag.toString('base64'),
        compression: 'gzip',
        eventCount: events.length,
    };
}
function importMemory(payload, targetProject) {
    const result = { added: 0, updated: 0, conflicts: 0, unresolvedConflicts: 0, skipped: 0, errors: [] };
    try {
        const key = getArchiveKey();
        const encrypted = Buffer.from(payload.encryptedData, 'base64');
        const iv = Buffer.from(payload.iv, 'base64');
        const tag = Buffer.from(payload.tag, 'base64');
        const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
        decipher.setAuthTag(tag);
        const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
        const decompressed = zlib.gunzipSync(decrypted);
        const data = JSON.parse(decompressed.toString('utf-8'));
        const remoteChecksum = crypto.createHash('sha256').update(decrypted).digest('hex');
        if (remoteChecksum !== payload.checksum) {
            result.errors.push('Checksum mismatch — data may be corrupted');
            return result;
        }
        const db = (0, database_1.getDb)();
        const tx = db.transaction(() => {
            for (const event of data.events) {
                const existing = db.prepare('SELECT id FROM memory_events WHERE project = ? AND summary = ? AND created_at = ?').get(targetProject, event.summary, event.created_at);
                if (!existing) {
                    db.prepare(`
            INSERT INTO memory_events (project, branch, type, topic, summary, details_json, files_json, importance, tool, model, tags_json, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
          `).run(targetProject, event.branch || null, event.type, event.topic || null, event.summary, event.details_json || null, event.files_json || null, event.importance || 3, event.tool || null, event.model || null, event.tags_json || null, event.created_at);
                    result.added++;
                }
                else {
                    result.skipped++;
                }
            }
        });
        tx();
    }
    catch (err) {
        result.errors.push(err.message);
    }
    return result;
}
function computeDiff(project, remotePayload) {
    const localEvents = new Map((0, database_1.getDb)().prepare('SELECT id, summary, created_at FROM memory_events WHERE project = ?').all(project).map(r => [r.id, r]));
    const remoteEvents = new Map(parseRemoteEvents(remotePayload));
    const localOnly = [];
    const conflicts = [];
    let bothPresent = 0;
    for (const [id, local] of localEvents) {
        if (!remoteEvents.has(id)) {
            localOnly.push(id);
        }
        else {
            bothPresent++;
            const remote = remoteEvents.get(id);
            if (local.summary !== remote.summary || local.created_at !== remote.created_at) {
                conflicts.push({
                    eventId: id,
                    localSummary: local.summary,
                    remoteSummary: remote.summary,
                    localTimestamp: local.created_at,
                    remoteTimestamp: remote.created_at,
                    localImportance: 0,
                    remoteImportance: 0,
                    resolved: false,
                });
            }
        }
    }
    const remoteOnly = [...remoteEvents.keys()].filter(id => !localEvents.has(id));
    return { localOnly, remoteOnly, conflicts, bothPresent };
}
function resolveConflicts(conflicts, strategy) {
    return conflicts.map(c => {
        switch (strategy) {
            case 'local-wins':
                return { ...c, resolution: 'local-wins', resolved: true };
            case 'remote-wins':
                return { ...c, resolution: 'remote-wins', resolved: true };
            case 'importance': {
                const resolution = c.localImportance >= c.remoteImportance ? 'local-wins' : 'remote-wins';
                return { ...c, resolution, resolved: true };
            }
            case 'timestamp': {
                const resolution = new Date(c.localTimestamp) >= new Date(c.remoteTimestamp) ? 'local-wins' : 'remote-wins';
                return { ...c, resolution, resolved: true };
            }
            default:
                return c;
        }
    });
}
function getSyncStatus(project) {
    const db = (0, database_1.getDb)();
    const lastSync = db.prepare('SELECT MAX(last_sync_at) as ls FROM sync_state WHERE project = ?').get(project);
    const pendingUpload = db.prepare("SELECT COUNT(*) as c FROM memory_events WHERE project = ? AND (synced IS NULL OR synced = 0)").get(project);
    const { getCanonicalProvider } = require('../privacy/key-provider');
    try {
        const provider = getCanonicalProvider();
        const encryptionKeySet = provider.getKey('fivocell-archive') !== null;
        const conflicts = db.prepare('SELECT COUNT(*) as c FROM sync_conflicts WHERE project = ? AND resolved = 0').get(project);
        return {
            lastSyncAt: lastSync?.ls || null,
            pendingUpload: pendingUpload.c,
            pendingDownload: 0,
            conflicts: conflicts.c,
            lastSyncDuration: 0,
            encryptionKeySet,
        };
    }
    catch {
        return {
            lastSyncAt: lastSync?.ls || null,
            pendingUpload: pendingUpload.c,
            pendingDownload: 0,
            conflicts: 0,
            lastSyncDuration: 0,
            encryptionKeySet: false,
        };
    }
}
function markEventsSynced(eventIds) {
    if (eventIds.length === 0)
        return;
    const db = (0, database_1.getDb)();
    const placeholders = eventIds.map(() => '?').join(',');
    db.prepare(`UPDATE memory_events SET synced = 1 WHERE id IN (${placeholders})`).run(...eventIds);
}
function getUnsyncedEvents(project, limit = 500) {
    return (0, database_1.getDb)().prepare('SELECT * FROM memory_events WHERE project = ? AND (synced IS NULL OR synced = 0) ORDER BY id LIMIT ?').all(project, limit);
}
function saveSyncSnapshot(project) {
    const db = (0, database_1.getDb)();
    const events = db.prepare('SELECT id FROM memory_events WHERE project = ? ORDER BY id').all(project);
    const eventIds = events.map(e => e.id);
    const checksum = crypto.createHash('sha256').update(eventIds.join(',')).digest('hex');
    db.prepare(`
    INSERT INTO sync_state (project, last_snapshot_at, snapshot_checksum, pending_uploads, pending_downloads, conflicts)
    VALUES (?, ?, ?, 0, 0, 0)
    ON CONFLICT(project) DO UPDATE SET last_snapshot_at = ?, snapshot_checksum = ?
  `).run(project, new Date().toISOString(), checksum, new Date().toISOString(), checksum);
}
function getLatestSnapshot(project) {
    const db = (0, database_1.getDb)();
    const row = db.prepare('SELECT last_snapshot_at as ts, snapshot_checksum as chk FROM sync_state WHERE project = ?').get(project);
    if (!row?.ts)
        return null;
    const events = db.prepare('SELECT id FROM memory_events WHERE project = ? ORDER BY id').all(project);
    return {
        project,
        eventIds: events.map(e => e.id),
        timestamp: row.ts,
        checksum: row.chk || '',
    };
}
function parseRemoteEvents(payload) {
    try {
        const key = getArchiveKey();
        const encrypted = Buffer.from(payload.encryptedData, 'base64');
        const iv = Buffer.from(payload.iv, 'base64');
        const tag = Buffer.from(payload.tag, 'base64');
        const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
        decipher.setAuthTag(tag);
        const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
        const decompressed = zlib.gunzipSync(decrypted);
        const data = JSON.parse(decompressed.toString('utf-8'));
        return (data.events || []).map((e) => [e.id, { summary: e.summary, created_at: e.created_at }]);
    }
    catch {
        return [];
    }
}
//# sourceMappingURL=sync-engine.js.map