"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.enumerateGrants = enumerateGrants;
exports.reindexDisclosures = reindexDisclosures;
exports.reindexDisclosuresForContract = reindexDisclosuresForContract;
/**
 * Disclosure-grant chain indexer (Phase 2 of expose-disclosure-grants).
 *
 * Reads the AttestationVault `disclosures` ledger Map back out of on-chain
 * state and materializes it into the `DisclosureGrants` entity, so tier
 * entitlement becomes a queryable, chain-derived source of truth (the contract
 * is the ACL — see docs/feature-requests/expose-disclosure-grants.md).
 *
 * CENTRAL CONSTRAINT (validated in scripts/spike-disclosure-indexer.mjs):
 * the OUTER `disclosures` map exposes only `member`/`lookup` — it is NOT
 * iterable. The inner per-payload map and the sibling `attestation_owners`
 * map ARE iterable. So we enumerate payload hashes via `attestation_owners`
 * (every disclosure is scoped to an attested payload), then drill into
 * `disclosures.lookup(payloadHash)` for the grantee→level entries.
 *
 * The decode + enumerate logic is dependency-injected (`ledger`,
 * `queryContractState`) so it unit-tests without the ESM-only SDK. The thin
 * `reindexDisclosuresForContract` wrapper wires the real providers + artifact.
 */
const cds_1 = __importDefault(require("@sap/cds"));
const node_url_1 = require("node:url");
const midnight_1 = require("#cds-models/midnight");
const { SELECT, INSERT, UPDATE } = cds_1.default.ql;
function hex(b) {
    return Buffer.from(b).toString('hex');
}
/**
 * Enumerate every active grant in a decoded ledger. Works around the
 * non-iterable outer `disclosures` map by iterating `attestation_owners`.
 */
function enumerateGrants(led) {
    const rows = [];
    for (const [payloadHashBytes] of led.attestation_owners) {
        if (!led.disclosures.member(payloadHashBytes))
            continue;
        for (const [granteeBytes, levelBig] of led.disclosures.lookup(payloadHashBytes)) {
            rows.push({
                payloadHash: hex(payloadHashBytes),
                grantee: hex(granteeBytes),
                level: Number(levelBig)
            });
        }
    }
    return rows;
}
/**
 * Re-materialize `DisclosureGrants` for one contract from current on-chain
 * state. Idempotent: existing rows are updated in place (preserving the
 * optimistic `grantedTxHash` the handler wrote), new on-chain grants are
 * inserted, and any previously-active row no longer present on-chain is swept
 * to `active=false` (its grantee was revoked).
 */
async function reindexDisclosures(deps) {
    const { db, contractAddress, ledger, queryContractState } = deps;
    const state = await queryContractState(contractAddress);
    if (!state)
        return { indexed: 0, deactivated: 0 };
    // ContractState carries the ledger in `.data` (a ChargedState); `ledger()`
    // also accepts a bare StateValue, so fall back to the state itself.
    const led = ledger(state.data ?? state);
    const onChain = enumerateGrants(led);
    const now = new Date().toISOString();
    const seen = new Set();
    for (const g of onChain) {
        seen.add(`${g.payloadHash}|${g.grantee}`);
        const existing = await db.run(SELECT.one.from(midnight_1.DisclosureGrants).where({
            contractAddress, payloadHash: g.payloadHash, grantee: g.grantee
        }));
        if (existing) {
            await db.run(UPDATE.entity(midnight_1.DisclosureGrants)
                .set({ level: g.level, active: true, revokedTxHash: null, modifiedAt: now })
                .where({ ID: existing.ID }));
        }
        else {
            await db.run(INSERT.into(midnight_1.DisclosureGrants).entries({
                ID: cds_1.default.utils.uuid(),
                payloadHash: g.payloadHash,
                grantee: g.grantee,
                level: g.level,
                contractAddress,
                grantedTxHash: null,
                revokedTxHash: null,
                active: true,
                createdAt: now,
                modifiedAt: now
            }));
        }
    }
    // Sweep: an active row for this contract that is no longer on-chain was
    // revoked. The chain is authoritative, so flip it inactive.
    const activeRows = (await db.run(SELECT.from(midnight_1.DisclosureGrants).where({ contractAddress, active: true }))) || [];
    let deactivated = 0;
    for (const r of activeRows) {
        if (!seen.has(`${r.payloadHash}|${r.grantee}`)) {
            await db.run(UPDATE.entity(midnight_1.DisclosureGrants)
                .set({ active: false, modifiedAt: now })
                .where({ ID: r.ID }));
            deactivated++;
        }
    }
    return { indexed: onChain.length, deactivated };
}
/**
 * Production wrapper: build a contract-only provider bundle, load the
 * artifact's `ledger`, and reindex. Best-effort by design — callers run this
 * after a successful grant/revoke submit and must not let an indexing failure
 * fail the submission (the optimistic row already records intent; a later
 * reindex reconciles). Dynamic import keeps the ESM-only SDK out of CJS load.
 */
async function reindexDisclosuresForContract(args) {
    const { db, contractAddress, artifactPath, contractProvidersConfig } = args;
    const { buildContractProviders } = await import('../midnight/providers.js');
    const bundle = await buildContractProviders(contractProvidersConfig);
    const artifact = await import((0, node_url_1.pathToFileURL)(artifactPath).href);
    return reindexDisclosures({
        db,
        contractAddress,
        ledger: artifact.ledger,
        queryContractState: (addr) => bundle.publicDataProvider.queryContractState(addr)
    });
}
