"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
Object.defineProperty(exports, "__esModule", { value: true });
exports.IdentityGate = void 0;
exports.sqlUsesProcessNameFilter = sqlUsesProcessNameFilter;
exports.collectSkillSql = collectSkillSql;
exports.skillUsesProcessNameFilter = skillUsesProcessNameFilter;
exports.getEffectiveIdentityConfig = getEffectiveIdentityConfig;
exports.extractProcessIdentityTarget = extractProcessIdentityTarget;
const types_1 = require("./types");
const PROCESS_NAME_FILTER_OPERATORS = '(?:NOT\\s+GLOB\\b|NOT\\s+LIKE\\b|GLOB\\b|LIKE\\b|IN\\b|IS(?:\\s+NOT)?\\b|=)';
const SQL_KEYWORDS = new Set([
    'where',
    'on',
    'using',
    'join',
    'left',
    'right',
    'inner',
    'outer',
    'cross',
    'full',
    'group',
    'order',
    'limit',
]);
function escapeRegex(value) {
    return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function stripSqlComments(sql) {
    return sql
        .replace(/--[^\n\r]*/g, ' ')
        .replace(/\/\*[\s\S]*?\*\//g, ' ');
}
function collectProcessTableAliases(sql) {
    const aliases = new Set(['process']);
    const re = /\b(?:FROM|JOIN)\s+process\b(?:\s+(?:AS\s+)?([A-Za-z_][A-Za-z0-9_]*))?/gi;
    for (const match of sql.matchAll(re)) {
        const alias = match[1]?.toLowerCase();
        if (alias && !SQL_KEYWORDS.has(alias)) {
            aliases.add(alias);
        }
    }
    return aliases;
}
function sqlUsesProcessNameFilter(sql) {
    if (!sql || typeof sql !== 'string')
        return false;
    const stripped = stripSqlComments(sql);
    const operator = PROCESS_NAME_FILTER_OPERATORS;
    const hasProcessTable = /\b(?:FROM|JOIN)\s+process\b/i.test(stripped);
    for (const alias of collectProcessTableAliases(stripped)) {
        const qualifiedNameRe = new RegExp(`\\b${escapeRegex(alias)}\\.name\\s+${operator}`, 'i');
        if (qualifiedNameRe.test(stripped))
            return true;
    }
    if (hasProcessTable) {
        const unqualifiedNameRe = new RegExp(`(?<!\\.)\\bname\\s+${operator}`, 'i');
        if (unqualifiedNameRe.test(stripped))
            return true;
    }
    const identityColumnRe = new RegExp(`\\b(?:[A-Za-z_][A-Za-z0-9_]*\\.)?(?:process_name|client_process|server_process|package_name)\\s+${operator}`, 'i');
    return identityColumnRe.test(stripped);
}
function collectStepSql(step, out) {
    if (!step || typeof step !== 'object')
        return;
    if (typeof step.sql === 'string')
        out.push(step.sql);
    if (Array.isArray(step.steps)) {
        for (const nested of step.steps)
            collectStepSql(nested, out);
    }
    if (Array.isArray(step.conditions)) {
        for (const branch of step.conditions) {
            if (branch?.then && typeof branch.then === 'object') {
                collectStepSql(branch.then, out);
            }
        }
    }
    if (step.else && typeof step.else === 'object') {
        collectStepSql(step.else, out);
    }
}
function collectSkillSql(skill) {
    const sql = [];
    if (typeof skill.sql === 'string')
        sql.push(skill.sql);
    if (Array.isArray(skill.steps)) {
        for (const step of skill.steps)
            collectStepSql(step, sql);
    }
    return sql.join('\n');
}
function skillUsesProcessNameFilter(skill) {
    return sqlUsesProcessNameFilter(collectSkillSql(skill));
}
function getEffectiveIdentityConfig(skill) {
    if (skill.name === 'process_identity_resolver') {
        return { policy: 'exempt', scope: 'process' };
    }
    const explicit = skill.identity;
    if (explicit?.policy) {
        return {
            scope: 'process',
            aliases: types_1.DEFAULT_PROCESS_IDENTITY_ALIASES,
            rewriteTo: 'recommended_process_name_param',
            minConfidence: 50,
            ...explicit,
        };
    }
    if (skillUsesProcessNameFilter(skill)) {
        return {
            policy: 'verify_if_present',
            scope: 'process',
            aliases: types_1.DEFAULT_PROCESS_IDENTITY_ALIASES,
            rewriteTo: 'recommended_process_name_param',
            minConfidence: 50,
        };
    }
    return { policy: 'none' };
}
function firstValue(source, keys) {
    for (const key of keys) {
        const value = source[key];
        if (value !== undefined && value !== null && String(value).trim() !== '')
            return value;
    }
    return undefined;
}
function coerceInteger(value) {
    if (value === undefined || value === null || String(value).trim() === '')
        return undefined;
    const n = Number(value);
    if (!Number.isInteger(n))
        return undefined;
    return n;
}
function extractProcessIdentityTarget(params, inherited, config) {
    const aliases = config.aliases?.length ? config.aliases : types_1.DEFAULT_PROCESS_IDENTITY_ALIASES;
    const requestedName = firstValue(params, aliases) ?? firstValue(inherited, aliases);
    const threadName = firstValue(params, ['thread_name', 'threadName']) ?? firstValue(inherited, ['thread_name', 'threadName']);
    const upid = coerceInteger(firstValue(params, ['upid']) ?? firstValue(inherited, ['upid']));
    const pid = coerceInteger(firstValue(params, ['pid']) ?? firstValue(inherited, ['pid']));
    const startTs = firstValue(params, ['start_ts', 'startTs']) ?? firstValue(inherited, ['start_ts', 'startTs']);
    const endTs = firstValue(params, ['end_ts', 'endTs']) ?? firstValue(inherited, ['end_ts', 'endTs']);
    return {
        ...(requestedName !== undefined ? { requestedName: String(requestedName).trim() } : {}),
        ...(threadName !== undefined ? { threadName: String(threadName).trim() } : {}),
        ...(upid !== undefined ? { upid } : {}),
        ...(pid !== undefined ? { pid } : {}),
        ...(startTs !== undefined ? { startTs } : {}),
        ...(endTs !== undefined ? { endTs } : {}),
    };
}
function hasTarget(target) {
    return Boolean(target.requestedName || target.threadName || target.upid !== undefined || target.pid !== undefined);
}
function isVerified(resolution, config) {
    if (resolution.status !== 'verified')
        return false;
    const minConfidence = config.minConfidence ?? 50;
    return resolution.confidenceScore >= minConfidence;
}
function rewriteParams(params, skill, target, resolution, config) {
    const rewritten = { ...params };
    if (!isVerified(resolution, config))
        return rewritten;
    if (config.rewriteTo === 'upid' && resolution.upids.length > 0) {
        rewritten.upid = resolution.upids[0];
        return rewritten;
    }
    const recommended = resolution.recommendedProcessNameParam;
    if (!recommended)
        return rewritten;
    const aliases = config.aliases?.length ? config.aliases : types_1.DEFAULT_PROCESS_IDENTITY_ALIASES;
    let rewroteExisting = false;
    for (const alias of aliases) {
        if (rewritten[alias] !== undefined && rewritten[alias] !== null && String(rewritten[alias]).trim() !== '') {
            rewritten[alias] = recommended;
            rewroteExisting = true;
        }
    }
    if (target.requestedName) {
        // Keep legacy YAML skills safe: most process filters read either package or
        // process_name regardless of which alias the caller originally supplied.
        const declaredInputs = new Set((skill.inputs || []).map(input => input.name));
        const hasInputDeclarations = Array.isArray(skill.inputs) && skill.inputs.length > 0;
        if (hasInputDeclarations) {
            for (const alias of aliases) {
                if (declaredInputs.has(alias) && rewritten[alias] === undefined) {
                    rewritten[alias] = recommended;
                }
            }
        }
        if (rewritten.package !== undefined || declaredInputs.has('package') || !hasInputDeclarations) {
            rewritten.package = recommended;
        }
        if (rewritten.process_name !== undefined || declaredInputs.has('process_name') || !hasInputDeclarations) {
            rewritten.process_name = recommended;
        }
        if (hasInputDeclarations) {
            for (const alias of aliases) {
                if (!declaredInputs.has(alias)) {
                    delete rewritten[alias];
                }
            }
        }
    }
    return rewritten;
}
class IdentityGate {
    async apply(input) {
        const inherited = input.inherited || {};
        const config = getEffectiveIdentityConfig(input.skill);
        const skipForInternalResolver = inherited.__skipIdentityGate === true &&
            input.skill.name === 'process_identity_resolver';
        if (skipForInternalResolver || config.policy === 'none' || config.policy === 'exempt') {
            return { allowed: true, params: input.params, inherited, config };
        }
        const target = extractProcessIdentityTarget(input.params, inherited, config);
        if (!hasTarget(target)) {
            if (config.policy === 'required') {
                return {
                    allowed: false,
                    params: input.params,
                    inherited,
                    config,
                    target,
                    error: `Process identity is required before running skill "${input.skill.name}", but no package/process/upid target was provided.`,
                };
            }
            return { allowed: true, params: input.params, inherited, config, target };
        }
        const resolution = await input.resolve(target);
        const verified = isVerified(resolution, config);
        if (!verified) {
            const base = `Process identity could not be verified for skill "${input.skill.name}"`;
            const reason = resolution.resolverError
                ? `${base}: resolver failed (${resolution.resolverError})`
                : `${base}: status=${resolution.status}, confidence=${resolution.confidenceScore}`;
            // Keep current broad overview flows resilient when the resolver itself is unavailable.
            if (config.policy === 'verify_if_present' && resolution.status === 'unresolved' && resolution.resolverError) {
                return {
                    allowed: true,
                    params: input.params,
                    inherited: {
                        ...inherited,
                        identity_resolution: resolution,
                        identity_gate_warning: reason,
                    },
                    config,
                    target,
                    resolution,
                };
            }
            return {
                allowed: false,
                params: input.params,
                inherited,
                config,
                target,
                resolution,
                error: reason,
            };
        }
        const params = rewriteParams(input.params, input.skill, target, resolution, config);
        return {
            allowed: true,
            params,
            inherited: {
                ...inherited,
                identity_resolution: resolution,
            },
            config,
            target,
            resolution,
        };
    }
}
exports.IdentityGate = IdentityGate;
//# sourceMappingURL=identityGate.js.map