"use strict";
// backend/src/memory-pool/global-pool-manager.ts
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.getGlobalPoolDir = getGlobalPoolDir;
exports.readSources = readSources;
exports.writeSources = writeSources;
exports.isGlobalPoolInitialized = isGlobalPoolInitialized;
exports.ensureGlobalPool = ensureGlobalPool;
exports.readGlobalPool = readGlobalPool;
exports.computeGlobalT = computeGlobalT;
exports.registerProject = registerProject;
exports.removeSource = removeSource;
exports.syncToGlobal = syncToGlobal;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const config_1 = require("../config");
const pool_manager_1 = require("./pool-manager");
const pool_lock_1 = require("./pool-lock");
const buoyancy_1 = require("./buoyancy");
const GLOBAL_POOL_DIR = path.join(os.homedir(), '.ccweb', 'memory-pool');
const SOURCES_FILE = 'sources.json';
// Concurrency guard
let syncInProgress = false;
function getGlobalPoolDir() {
    return GLOBAL_POOL_DIR;
}
// ── Sources management ──
function sourcesPath() {
    return path.join(GLOBAL_POOL_DIR, SOURCES_FILE);
}
function readSources() {
    const file = sourcesPath();
    if (!fs.existsSync(file))
        return { version: 1, sources: [] };
    try {
        return JSON.parse(fs.readFileSync(file, 'utf-8'));
    }
    catch {
        return { version: 1, sources: [] };
    }
}
function writeSources(data) {
    (0, config_1.atomicWriteSync)(sourcesPath(), JSON.stringify(data, null, 2));
}
// ── Global pool init ──
function isGlobalPoolInitialized() {
    return fs.existsSync(path.join(GLOBAL_POOL_DIR, 'pool.json'));
}
function ensureGlobalPool() {
    if (isGlobalPoolInitialized())
        return;
    fs.mkdirSync(path.join(GLOBAL_POOL_DIR, 'balls'), { recursive: true });
    const now = new Date().toISOString();
    const pool = {
        version: 2,
        t: 0,
        lambda: 0.99,
        alpha: 1.0,
        active_capacity: 40,
        surface_width: 10000,
        next_id: 1,
        pool: 'global',
        initialized_at: now,
        balls: [],
    };
    (0, pool_manager_1.writePool)(GLOBAL_POOL_DIR, pool);
    writeSources({ version: 1, sources: [] });
}
function readGlobalPool() {
    return (0, pool_manager_1.readPool)(GLOBAL_POOL_DIR);
}
// ── Calendar-based t ──
function computeGlobalT(initializedAt) {
    const epochDay = Math.floor(new Date(initializedAt).getTime() / 86400000);
    const nowDay = Math.floor(Date.now() / 86400000);
    return Math.max(0, nowDay - epochDay);
}
// ── Register project ──
async function registerProject(projectId, projectName, poolPath) {
    ensureGlobalPool();
    await (0, pool_lock_1.withPoolLock)(GLOBAL_POOL_DIR, () => {
        const sources = readSources();
        const existing = sources.sources.find((s) => s.pool_path === poolPath);
        if (existing) {
            existing.project_id = projectId;
            existing.project_name = projectName;
            existing.status = 'active';
            existing.unreachable_count = 0;
        }
        else {
            sources.sources.push({
                project_id: projectId,
                project_name: projectName,
                pool_path: poolPath,
                registered_at: new Date().toISOString(),
                last_synced_at: null,
                status: 'active',
                unreachable_count: 0,
            });
        }
        writeSources(sources);
    });
}
async function removeSource(projectId) {
    let removed = false;
    await (0, pool_lock_1.withPoolLock)(GLOBAL_POOL_DIR, () => {
        const sources = readSources();
        const idx = sources.sources.findIndex((s) => s.project_id === projectId);
        if (idx === -1)
            return;
        sources.sources.splice(idx, 1);
        writeSources(sources);
        removed = true;
    });
    return removed;
}
// ── Sync (global operation) ──
async function syncToGlobal() {
    if (syncInProgress) {
        throw new Error('SYNC_IN_PROGRESS');
    }
    syncInProgress = true;
    try {
        return await (0, pool_lock_1.withPoolLock)(GLOBAL_POOL_DIR, () => {
            ensureGlobalPool();
            const globalPool = readGlobalPool();
            const sources = readSources();
            const globalT = computeGlobalT(globalPool.initialized_at);
            globalPool.t = globalT;
            const result = {
                added: 0,
                updated: 0,
                skipped: 0,
                orphaned: 0,
                unreachable_projects: [],
                synced_projects: [],
            };
            // Build origin lookup: "projectId:ballId" → global ball index
            const originMap = new Map();
            globalPool.balls.forEach((gb, idx) => {
                if (gb.origins) {
                    for (const o of gb.origins) {
                        originMap.set(`${o.source_project}:${o.source_ball_id}`, idx);
                    }
                }
            });
            // Track which origins are still alive (from synced projects only)
            const aliveOrigins = new Set();
            // Track projects that were actually synced (for orphan detection scoping)
            const syncedProjectIds = new Set();
            // Step 1 & 2: Collect and merge
            for (const source of sources.sources) {
                if (source.status === 'unreachable')
                    continue;
                const projectPool = (0, pool_manager_1.readPool)(source.pool_path);
                if (!projectPool) {
                    source.unreachable_count = (source.unreachable_count ?? 0) + 1;
                    if (source.unreachable_count >= 3) {
                        source.status = 'unreachable';
                    }
                    result.unreachable_projects.push(source.project_name);
                    continue;
                }
                // Reset unreachable count on success
                source.unreachable_count = 0;
                source.last_synced_at = new Date().toISOString();
                result.synced_projects.push(source.project_name);
                syncedProjectIds.add(source.project_id);
                for (const ball of projectPool.balls) {
                    const originKey = `${source.project_id}:${ball.id}`;
                    aliveOrigins.add(originKey);
                    const existingIdx = originMap.get(originKey);
                    if (existingIdx !== undefined) {
                        // Already exists — check content change
                        const globalBall = globalPool.balls[existingIdx];
                        const projectContent = (0, pool_manager_1.readBallContent)(source.pool_path, ball.id);
                        const globalContent = (0, pool_manager_1.readBallContent)(GLOBAL_POOL_DIR, globalBall.id);
                        if (projectContent !== null && projectContent !== globalContent) {
                            // Content changed — update content, keep global H
                            (0, pool_manager_1.writeBallContent)(GLOBAL_POOL_DIR, globalBall.id, projectContent);
                            globalBall.summary = ball.summary;
                            globalBall.t_last = globalT;
                            result.updated++;
                        }
                        else {
                            // Touch: refresh t_last
                            globalBall.t_last = globalT;
                            result.skipped++;
                        }
                    }
                    else {
                        // New ball — compute project buoyancy as B0
                        const projectBuoyancy = (0, buoyancy_1.computeBuoyancy)(ball.B0, ball.H, projectPool.alpha, projectPool.lambda, projectPool.t, ball.t_last, ball.permanent);
                        const newId = `ball_${String(globalPool.next_id).padStart(4, '0')}`;
                        globalPool.next_id++;
                        const newBall = {
                            id: newId,
                            type: ball.type,
                            summary: ball.summary,
                            B0: Math.round(projectBuoyancy * 100) / 100,
                            H: 0,
                            t_last: globalT,
                            hardness: ball.hardness,
                            permanent: ball.permanent,
                            links: [], // Links resolved separately
                            created_at: ball.created_at,
                            origins: [{
                                    source_project: source.project_id,
                                    source_ball_id: ball.id,
                                    synced_at: new Date().toISOString(),
                                }],
                        };
                        // Write ball content first (gate pattern)
                        const content = (0, pool_manager_1.readBallContent)(source.pool_path, ball.id);
                        if (content !== null) {
                            (0, pool_manager_1.writeBallContent)(GLOBAL_POOL_DIR, newId, content);
                        }
                        globalPool.balls.push(newBall);
                        originMap.set(originKey, globalPool.balls.length - 1);
                        result.added++;
                    }
                }
            }
            // Step 3: Orphan detection (only check origins from synced projects)
            for (const gb of globalPool.balls) {
                if (!gb.origins || gb.origins.length === 0)
                    continue;
                // Only remove origins whose source project was actually synced and ball is gone
                gb.origins = gb.origins.filter((o) => {
                    if (!syncedProjectIds.has(o.source_project))
                        return true; // preserve unreachable/unsynced origins
                    return aliveOrigins.has(`${o.source_project}:${o.source_ball_id}`);
                });
                if (gb.origins.length === 0 && !gb.orphaned) {
                    gb.pre_orphan_B0 = gb.B0;
                    gb.orphaned = true;
                    gb.B0 = Math.round(gb.B0 * 0.5 * 100) / 100;
                    result.orphaned++;
                }
                else if (gb.origins.length > 0 && gb.orphaned) {
                    // Restore orphaned ball when origins reappear
                    if (gb.pre_orphan_B0 !== undefined) {
                        gb.B0 = gb.pre_orphan_B0;
                        delete gb.pre_orphan_B0;
                    }
                    gb.orphaned = false;
                }
            }
            // Step 4: Write pool.json LAST (gate)
            (0, pool_manager_1.writePool)(GLOBAL_POOL_DIR, globalPool);
            writeSources(sources);
            // Step 5: Rebuild global surface.md
            (0, pool_manager_1.buildSurface)(GLOBAL_POOL_DIR);
            return result;
        });
    }
    finally {
        syncInProgress = false;
    }
}
//# sourceMappingURL=global-pool-manager.js.map