"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DISCLOSURE_ROLE_VALUES = exports.DEFAULT_DISCLOSURE_ROLE = void 0;
exports.attachDisclosureRole = attachDisclosureRole;
exports.isAuthority = isAuthority;
exports.isValidDisclosureRoleValue = isValidDisclosureRoleValue;
exports.meetsDisclosure = meetsDisclosure;
/**
 * Disclosure-role middleware.
 *
 * Resolves the highest currently-valid `DisclosureRole` grant for the
 * authenticated user and attaches it to the request so downstream handlers
 * and CDS projections can gate response width by tier.
 * When no grant applies, the default is `public_only`.
 *
 * The disclosure-role tiers map 1:1 to the EU Battery Regulation Annex XIII /
 * Art. 77 access tiers (general public / legitimate-interest / authority).
 * The middleware is intentionally orthogonal to CAP's `@requires` auth roles
 * — that gates access to *services*, this gates the *shape* of responses
 * within a service the caller already reached.
 */
const cds_1 = __importDefault(require("@sap/cds"));
const midnight_1 = require("#cds-models/midnight");
const grantee_identity_1 = require("../submission/grantee-identity");
exports.DEFAULT_DISCLOSURE_ROLE = 'public_only';
exports.DISCLOSURE_ROLE_VALUES = [
    'public_only',
    'legitimate_interest',
    'authority'
];
const RANK = {
    public_only: 0,
    legitimate_interest: 1,
    authority: 2
};
/**
 * On-chain disclosure level (0/1/2) → disclosure-role tier. Inverse of the
 * RANK above; the AttestationVault `level` maps 1:1 onto the EU Battery
 * Regulation Annex XIII access tiers.
 */
const LEVEL_TO_ROLE = {
    0: 'public_only',
    1: 'legitimate_interest',
    2: 'authority'
};
/**
 * Looks up the authenticated user's highest disclosure tier and attaches it
 * to `req.disclosureRole`. Returns the resolved role for direct use inside a
 * handler.
 *
 * Two sources, selected by `options.contractAddress`:
 *   - on-chain (contractAddress set): the indexed `DisclosureGrants` ACL is
 *     authoritative — the caller's granteeId is matched against active grants.
 *   - off-chain (no contractAddress): the operator-configured `DisclosureRoles`
 *     table (original behavior).
 *
 * The lookup is small (one or two SELECTs keyed on `userId`/contract); we rank
 * in JS to keep the SQL portable across SQLite/HANA without a DB-specific
 * CASE WHEN.
 */
async function attachDisclosureRole(req, db, options = {}) {
    const userId = req.user?.id;
    const target = req;
    if (!userId) {
        target.disclosureRole = exports.DEFAULT_DISCLOSURE_ROLE;
        return exports.DEFAULT_DISCLOSURE_ROLE;
    }
    // On-chain ACL path: authoritative when a contract scope is configured.
    if (options.contractAddress) {
        const role = await resolveOnChainRole(req, db, options.contractAddress, options.payloadHash);
        target.disclosureRole = role;
        return role;
    }
    const { SELECT } = cds_1.default.ql;
    const rows = await db.run(SELECT.from(midnight_1.DisclosureRoles).where({ userId })) || [];
    const now = new Date().toISOString();
    const valid = rows.filter(r => isCurrentlyValidGrant(r, now, options.scope));
    if (valid.length === 0) {
        target.disclosureRole = exports.DEFAULT_DISCLOSURE_ROLE;
        return exports.DEFAULT_DISCLOSURE_ROLE;
    }
    const highest = valid.reduce((best, current) => RANK[current.role] > RANK[best.role] ? current : best);
    target.disclosureRole = highest.role;
    return highest.role;
}
/**
 * Resolve the caller's tier from the on-chain `DisclosureGrants` ACL. Returns
 * the highest active grant's tier for the caller's granteeId on this contract
 * (optionally narrowed to one attestation), or `public_only` when the caller
 * has no registered granteeId or no active grant.
 */
async function resolveOnChainRole(req, db, contractAddress, payloadHash) {
    const granteeId = await (0, grantee_identity_1.resolveGranteeId)(req, db, { scope: contractAddress });
    if (!granteeId)
        return exports.DEFAULT_DISCLOSURE_ROLE;
    const { SELECT } = cds_1.default.ql;
    const where = { contractAddress, grantee: granteeId, active: true };
    if (payloadHash)
        where.payloadHash = payloadHash;
    const grants = await db.run(SELECT.from(midnight_1.DisclosureGrants).where(where)) || [];
    if (grants.length === 0)
        return exports.DEFAULT_DISCLOSURE_ROLE;
    const highestLevel = grants.reduce((max, g) => (g.level > max ? g.level : max), 0);
    return LEVEL_TO_ROLE[highestLevel] ?? exports.DEFAULT_DISCLOSURE_ROLE;
}
function isCurrentlyValidGrant(row, now, requestedScope) {
    if (row.validFrom && row.validFrom > now)
        return false;
    if (row.validUntil && row.validUntil <= now)
        return false;
    const rowScope = row.scope == null || row.scope === '' ? null : row.scope;
    if (requestedScope === undefined) {
        // No scope requested → only global (null/empty) grants apply.
        return rowScope === null;
    }
    // Scoped request → either global grant or grant matching the scope.
    return rowScope === null || rowScope === requestedScope;
}
function isAuthority(role) {
    return role === 'authority';
}
function isValidDisclosureRoleValue(value) {
    return typeof value === 'string'
        && exports.DISCLOSURE_ROLE_VALUES.includes(value);
}
/**
 * Tier comparison helper for handler-side gating. Higher tiers always satisfy
 * lower-tier requirements (`authority` meets `legitimate_interest` and below).
 */
function meetsDisclosure(actual, required) {
    const a = actual ? RANK[actual] : RANK[exports.DEFAULT_DISCLOSURE_ROLE];
    return a >= RANK[required];
}
