"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.writeBlob = writeBlob;
exports.readBlob = readBlob;
exports.blobExists = blobExists;
exports.getBlobMeta = getBlobMeta;
exports.deleteBlob = deleteBlob;
exports.getBlobStats = getBlobStats;
exports.listBlobs = listBlobs;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const crypto = __importStar(require("crypto"));
const zlib = __importStar(require("zlib"));
// ============================================================
// blob-vault.ts — Content-addressed dedup storage
//
// Stores large content as SHA-256 hashed blobs
// Same content → 1 blob, N references
//
// Structure:
//   .cell/memory/blobs/sha256-xxxx.enc
//   .cell/memory/blobs/sha256-xxxx.meta.json
//
// Format: gzip compress → AES-256-GCM encrypt
// Key: centralized via key-provider chain (os-native → file → legacy)
// ============================================================
const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const TAG_LENGTH = 16;
const KEY_LENGTH = 32;
// ============================================================
// Internal helpers
// ============================================================
function getBlobsDir(projectDir) {
    const dir = path.join(projectDir, '.cell', 'memory', 'blobs');
    if (!fs.existsSync(dir))
        fs.mkdirSync(dir, { recursive: true });
    return dir;
}
function getBlobPath(projectDir, hash) {
    return path.join(getBlobsDir(projectDir), `sha256-${hash}.enc`);
}
function getMetaPath(projectDir, hash) {
    return path.join(getBlobsDir(projectDir), `sha256-${hash}.meta.json`);
}
function getBlobKey() {
    // Use FileKeyProvider directly (reliable, consistent)
    try {
        const { FileKeyProvider } = require('../privacy/key-provider-file');
        const provider = new FileKeyProvider();
        if (provider.isAvailable()) {
            const existing = provider.getKey('fivocell-blob-vault');
            if (existing && existing.length === KEY_LENGTH)
                return existing;
            const newKey = crypto.randomBytes(KEY_LENGTH);
            provider.setKey('fivocell-blob-vault', newKey);
            return newKey;
        }
    }
    catch { /* fallback below */ }
    // Fallback: machine-bound key derivation (legacy compat)
    const hostname = require('os').hostname();
    const username = require('os').userInfo().username;
    return crypto.scryptSync(`${hostname}:${username}:fivocell-blob-vault`, 'fivocell-blob-key', KEY_LENGTH);
}
function encrypt(data, key) {
    const iv = crypto.randomBytes(IV_LENGTH);
    const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { authTagLength: TAG_LENGTH });
    const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
    const authTag = cipher.getAuthTag();
    return Buffer.concat([iv, authTag, encrypted]);
}
function decrypt(data, key) {
    const iv = data.subarray(0, IV_LENGTH);
    const authTag = data.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
    const encrypted = data.subarray(IV_LENGTH + TAG_LENGTH);
    const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: TAG_LENGTH });
    decipher.setAuthTag(authTag);
    return Buffer.concat([decipher.update(encrypted), decipher.final()]);
}
function computeHash(data) {
    return crypto.createHash('sha256').update(data).digest('hex');
}
// ============================================================
// Public API
// ============================================================
/**
 * Write content to blob vault (dedup by hash)
 * Returns hash if written, null if error
 */
function writeBlob(projectDir, content, contentType = 'event') {
    try {
        const hash = computeHash(content);
        // Dedup: check if blob already exists
        if (blobExists(projectDir, hash)) {
            // Increment ref_count
            const metaPath = getMetaPath(projectDir, hash);
            if (fs.existsSync(metaPath)) {
                try {
                    const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
                    meta.ref_count++;
                    fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
                }
                catch { /* non-critical */ }
            }
            return hash;
        }
        // Compress
        const compressed = zlib.gzipSync(content, { level: 6 });
        // Encrypt
        const key = getBlobKey();
        const encrypted = encrypt(compressed, key);
        // Write encrypted blob
        fs.writeFileSync(getBlobPath(projectDir, hash), encrypted);
        // Write metadata sidecar
        const meta = {
            hash,
            original_size: content.length,
            compressed_size: compressed.length,
            encrypted_size: encrypted.length,
            content_type: contentType,
            created_at: new Date().toISOString(),
            ref_count: 1,
        };
        fs.writeFileSync(getMetaPath(projectDir, hash), JSON.stringify(meta, null, 2));
        return hash;
    }
    catch {
        return null;
    }
}
/**
 * Read content from blob vault by hash
 */
function readBlob(projectDir, hash) {
    const blobPath = getBlobPath(projectDir, hash);
    if (!fs.existsSync(blobPath))
        return null;
    try {
        const encrypted = fs.readFileSync(blobPath);
        const key = getBlobKey();
        const compressed = decrypt(encrypted, key);
        return zlib.gunzipSync(compressed);
    }
    catch {
        return null;
    }
}
/**
 * Check if blob exists
 */
function blobExists(projectDir, hash) {
    return fs.existsSync(getBlobPath(projectDir, hash));
}
/**
 * Get blob metadata
 */
function getBlobMeta(projectDir, hash) {
    const metaPath = getMetaPath(projectDir, hash);
    if (!fs.existsSync(metaPath))
        return null;
    try {
        return JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
    }
    catch {
        return null;
    }
}
/**
 * Delete blob (only if ref_count <= 1)
 */
function deleteBlob(projectDir, hash) {
    const meta = getBlobMeta(projectDir, hash);
    if (!meta)
        return false;
    if (meta.ref_count > 1) {
        meta.ref_count--;
        fs.writeFileSync(getMetaPath(projectDir, hash), JSON.stringify(meta, null, 2));
        return true;
    }
    try {
        fs.unlinkSync(getBlobPath(projectDir, hash));
        fs.unlinkSync(getMetaPath(projectDir, hash));
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Get vault statistics
 */
function getBlobStats(projectDir) {
    const blobsDir = path.join(projectDir, '.cell', 'memory', 'blobs');
    if (!fs.existsSync(blobsDir)) {
        return {
            totalBlobs: 0,
            totalOriginalBytes: 0,
            totalEncryptedBytes: 0,
            dedupSavingsPercent: 0,
            avgRefCount: 0,
            contentTypes: {},
        };
    }
    let totalBlobs = 0;
    let totalOriginal = 0;
    let totalEncrypted = 0;
    let totalRefCount = 0;
    const contentTypes = {};
    for (const file of fs.readdirSync(blobsDir)) {
        if (!file.endsWith('.meta.json'))
            continue;
        try {
            const meta = JSON.parse(fs.readFileSync(path.join(blobsDir, file), 'utf-8'));
            totalBlobs++;
            totalOriginal += meta.original_size;
            totalEncrypted += meta.encrypted_size;
            totalRefCount += meta.ref_count;
            contentTypes[meta.content_type] = (contentTypes[meta.content_type] || 0) + 1;
        }
        catch { /* skip corrupt */ }
    }
    return {
        totalBlobs,
        totalOriginalBytes: totalOriginal,
        totalEncryptedBytes: totalEncrypted,
        dedupSavingsPercent: totalOriginal > 0
            ? Math.round((1 - totalEncrypted / totalOriginal) * 100)
            : 0,
        avgRefCount: totalBlobs > 0
            ? Math.round(totalRefCount / totalBlobs * 10) / 10
            : 0,
        contentTypes,
    };
}
/**
 * List all blobs
 */
function listBlobs(projectDir) {
    const blobsDir = path.join(projectDir, '.cell', 'memory', 'blobs');
    if (!fs.existsSync(blobsDir))
        return [];
    const results = [];
    for (const file of fs.readdirSync(blobsDir)) {
        if (!file.endsWith('.meta.json'))
            continue;
        try {
            results.push(JSON.parse(fs.readFileSync(path.join(blobsDir, file), 'utf-8')));
        }
        catch { /* skip */ }
    }
    return results;
}
//# sourceMappingURL=blob-vault.js.map