import { hashOfAST } from "../../../zero-protocol/src/query-hash.js";
import { bindStaticParameters } from "../../../zql/src/builder/builder.js";
import { simplifyCondition } from "../../../zql/src/query/expression.js";
/**
 * Adds permission rules to the given query so it only returns rows that the
 * user is allowed to read.
 *
 * If the returned query is `undefined` that means that user cannot run
 * the query at all. This is only the case if we can infer that all rows
 * would be excluded without running the query.
 * E.g., the user is trying to query a table that is not readable.
 */
export function transformAndHashQuery(lc, id, query, permissionRules, authData, internalQuery) {
    const transformed = internalQuery
        ? query // application permissions do not apply to internal queries
        : transformQuery(lc, query, permissionRules, authData);
    return {
        id,
        transformedAst: transformed,
        transformationHash: hashOfAST(transformed),
    };
}
/**
 * For a given AST, apply the read-auth rules and bind static auth data.
 */
export function transformQuery(lc, query, permissionRules, authData) {
    const queryWithPermissions = transformQueryInternal(lc, query, permissionRules);
    return bindStaticParameters(queryWithPermissions, {
        authData: authData,
    });
}
function transformQueryInternal(lc, query, permissionRules) {
    let rowSelectRules = permissionRules.tables[query.table]?.row?.select;
    if (!rowSelectRules || rowSelectRules.length === 0) {
        // If there are no rules, we default to not allowing any rows to be selected.
        lc.warn?.("No permission rules found for table '" +
            query.table +
            "'. No rows will be returned. Use ANYONE_CAN to allow all users to access all rows.");
        rowSelectRules = [
            [
                'allow',
                {
                    type: 'or',
                    conditions: [],
                },
            ],
        ];
    }
    const updatedWhere = addRulesToWhere(query.where
        ? transformCondition(lc, query.where, permissionRules)
        : undefined, rowSelectRules);
    return {
        ...query,
        where: simplifyCondition(updatedWhere),
        related: query.related?.map(sq => {
            const subquery = transformQueryInternal(lc, sq.subquery, permissionRules);
            return {
                ...sq,
                subquery,
            };
        }),
    };
}
function addRulesToWhere(where, rowSelectRules) {
    return {
        type: 'and',
        conditions: [
            ...(where ? [where] : []),
            {
                type: 'or',
                conditions: rowSelectRules.map(([_, condition]) => condition),
            },
        ],
    };
}
// We must augment conditions so we do not provide an oracle to users.
// E.g.,
// `issue.whereExists('secret', s => s.where('value', 'sdf'))`
// Not applying read policies to subqueries in the where position
// would allow users to infer the existence of rows, and their contents,
// that they cannot read.
function transformCondition(lc, cond, auth) {
    switch (cond.type) {
        case 'simple':
            return cond;
        case 'and':
        case 'or':
            return {
                ...cond,
                conditions: cond.conditions.map(c => transformCondition(lc, c, auth)),
            };
        case 'correlatedSubquery': {
            const query = transformQueryInternal(lc, cond.related.subquery, auth);
            return {
                ...cond,
                related: {
                    ...cond.related,
                    subquery: query,
                },
            };
        }
    }
}
//# sourceMappingURL=read-authorizer.js.map