var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
    var m = o[Symbol.asyncIterator], i;
    return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
    function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
    function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
import { computeScopeId } from './types/sync.js';
/** Separator used in compound LevelDB keys. */
const KEY_SEP = '^';
/**
 * Durable replication ledger — persists {@link ReplicationLinkState} for each
 * sync link in a LevelDB sublevel. Provides CRUD operations and replication
 * checkpoint helpers.
 *
 * Key format: `{tenantDid}^{remoteEndpoint}^{scopeId}`
 *
 * Each link tracks independent pull and push {@link DirectionCheckpoint}s.
 * The ledger does not own subscriptions or timers — it is a passive state
 * store called by the sync engine.
 */
export class ReplicationLedger {
    constructor(db) {
        this.db = db;
        this.sublevel = this.db.sublevel('replicationLinks');
    }
    // ---------------------------------------------------------------------------
    // Key helpers
    // ---------------------------------------------------------------------------
    /** Build the compound key for a link. */
    static buildKey(tenantDid, remoteEndpoint, scopeId) {
        return `${tenantDid}${KEY_SEP}${remoteEndpoint}${KEY_SEP}${scopeId}`;
    }
    // Note: compound keys use raw '^' separator. This is safe because tenantDid
    // (DID URI), remoteEndpoint (URL), and scopeId (base64url hash) cannot
    // contain '^'. If future fields can contain '^', keys must be escaped.
    // ---------------------------------------------------------------------------
    // CRUD
    // ---------------------------------------------------------------------------
    /**
     * Get-or-create a link. If the link does not exist, it is created with
     * `initializing` status and empty checkpoints.
     */
    getOrCreateLink(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const scopeId = yield computeScopeId(params.scope);
            const key = ReplicationLedger.buildKey(params.tenantDid, params.remoteEndpoint, scopeId);
            try {
                const raw = yield this.sublevel.get(key);
                const link = JSON.parse(raw);
                delete link.push; // strip legacy push field from old persisted links
                // connectivity is runtime state — always reset to 'unknown' on load
                // so stale 'online' from a previous session doesn't give false positives.
                link.connectivity = 'unknown';
                return link;
            }
            catch (error) {
                const e = error;
                if (e.code !== 'LEVEL_NOT_FOUND') {
                    throw error;
                }
            }
            // Create a new link with empty checkpoints.
            const link = {
                tenantDid: params.tenantDid,
                remoteEndpoint: params.remoteEndpoint,
                scopeId,
                scope: params.scope,
                status: 'initializing',
                connectivity: 'unknown',
                pull: {},
                needsReconcile: false,
                delegateDid: params.delegateDid,
                protocol: params.protocol,
            };
            yield this.sublevel.put(key, JSON.stringify(link));
            return link;
        });
    }
    /** Persist the current state of a link. */
    saveLink(link) {
        return __awaiter(this, void 0, void 0, function* () {
            const key = ReplicationLedger.buildKey(link.tenantDid, link.remoteEndpoint, link.scopeId);
            link.lastActivityAt = new Date().toISOString();
            yield this.sublevel.put(key, JSON.stringify(link));
        });
    }
    /** Delete a link. */
    deleteLink(tenantDid, remoteEndpoint, scopeId) {
        return __awaiter(this, void 0, void 0, function* () {
            const key = ReplicationLedger.buildKey(tenantDid, remoteEndpoint, scopeId);
            yield this.sublevel.del(key);
        });
    }
    /** List all links for a tenant. */
    getLinksForTenant(tenantDid) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, e_1, _b, _c;
            const prefix = `${tenantDid}${KEY_SEP}`;
            const links = [];
            try {
                for (var _d = true, _e = __asyncValues(this.sublevel.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
                    _c = _f.value;
                    _d = false;
                    const [key, value] = _c;
                    if (key.startsWith(prefix)) {
                        links.push(JSON.parse(value));
                    }
                }
            }
            catch (e_1_1) { e_1 = { error: e_1_1 }; }
            finally {
                try {
                    if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
                }
                finally { if (e_1) throw e_1.error; }
            }
            return links;
        });
    }
    /** List all links. */
    getAllLinks() {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, e_2, _b, _c;
            const links = [];
            try {
                for (var _d = true, _e = __asyncValues(this.sublevel.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
                    _c = _f.value;
                    _d = false;
                    const [, value] = _c;
                    links.push(JSON.parse(value));
                }
            }
            catch (e_2_1) { e_2 = { error: e_2_1 }; }
            finally {
                try {
                    if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
                }
                finally { if (e_2) throw e_2.error; }
            }
            return links;
        });
    }
    // ---------------------------------------------------------------------------
    // Delegate updates
    // ---------------------------------------------------------------------------
    /**
     * Update the `delegateDid` on all persisted links for a tenant and persist.
     * This ensures that repair and reconcile paths — which read `delegateDid`
     * from the durable {@link ReplicationLinkState} — use the current delegate
     * after a hot-swap via `updateIdentityOptions()`.
     *
     * @returns the links that were updated.
     */
    updateDelegateDid(tenantDid, delegateDid) {
        return __awaiter(this, void 0, void 0, function* () {
            const links = yield this.getLinksForTenant(tenantDid);
            const updated = [];
            for (const link of links) {
                if (link.delegateDid !== delegateDid) {
                    link.delegateDid = delegateDid;
                    yield this.saveLink(link);
                    updated.push(link);
                }
            }
            return updated;
        });
    }
    // ---------------------------------------------------------------------------
    // Status transitions
    // ---------------------------------------------------------------------------
    /** Transition a link to a new status and persist. */
    setStatus(link, status) {
        return __awaiter(this, void 0, void 0, function* () {
            link.status = status;
            yield this.saveLink(link);
        });
    }
    // ---------------------------------------------------------------------------
    // Minimal checkpoint helpers (durable state only — no progression logic)
    // ---------------------------------------------------------------------------
    /**
     * Compare two tokens by position (BigInt numeric comparison).
     * Returns negative if a < b, zero if equal, positive if a > b.
     * Caller must verify streamId and epoch match before calling.
     */
    static comparePosition(a, b) {
        const diff = BigInt(a.position) - BigInt(b.position);
        if (diff < BigInt(0)) {
            return -1;
        }
        if (diff > BigInt(0)) {
            return 1;
        }
        return 0;
    }
    /**
     * Check whether a token belongs to the same domain (streamId + epoch) as
     * the checkpoint's current baseline. Returns `true` if domains match or if
     * the checkpoint has no baseline yet.
     */
    static validateTokenDomain(checkpoint, token) {
        if (checkpoint.contiguousAppliedToken === undefined) {
            return true;
        }
        return token.streamId === checkpoint.contiguousAppliedToken.streamId &&
            token.epoch === checkpoint.contiguousAppliedToken.epoch;
    }
    /**
     * Update `receivedToken` to the highest seen token (for observability).
     * Does NOT advance `contiguousAppliedToken` — that is owned by the engine's
     * delivery-order tracking.
     */
    static setReceivedToken(checkpoint, token) {
        if (checkpoint.receivedToken === undefined ||
            ReplicationLedger.comparePosition(token, checkpoint.receivedToken) > 0) {
            checkpoint.receivedToken = token;
        }
    }
    /**
     * Commit a token as the new contiguous applied baseline. The caller (engine)
     * must have already verified that all earlier delivered tokens for this link
     * are durably committed before calling this.
     */
    static commitContiguousToken(checkpoint, token) {
        checkpoint.contiguousAppliedToken = token;
    }
    /**
     * Reset a replication checkpoint (e.g., after repair or domain change).
     * Clears all state.
     */
    static resetCheckpoint(checkpoint, token) {
        checkpoint.contiguousAppliedToken = token;
        checkpoint.receivedToken = token;
    }
    // ---------------------------------------------------------------------------
    // Reconciliation helpers
    // ---------------------------------------------------------------------------
    /**
     * Mark a link as needing SMT reconciliation and persist.
     * Idempotent — no-op if already set.
     */
    markNeedsReconcile(link, _reason) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!link.needsReconcile) {
                link.needsReconcile = true;
                yield this.saveLink(link);
            }
        });
    }
    /**
     * Clear the reconciliation flag after successful SMT reconciliation.
     */
    clearNeedsReconcile(link) {
        return __awaiter(this, void 0, void 0, function* () {
            if (link.needsReconcile) {
                link.needsReconcile = false;
                yield this.saveLink(link);
            }
        });
    }
}
//# sourceMappingURL=sync-replication-ledger.js.map