/**
 * Static analysis gate that flags spreadsheet-mutating calls in read-only mode.
 * Detection rules live in the sibling JSON files, which the addin build
 * pipeline (`client/addin/scripts/extract-api.ts`) generates from JSDoc
 * `@tags` on the wrapper-API TypeScript interfaces. Both this runtime and
 * the LangGraph Python backend (`backend/agent/constants/allowed_functions.py`)
 * consume the same shape — keeping them in lockstep is the whole point.
 *
 * Detection is intentionally conservative — false positives (innocent
 * code blocked) are safer than false negatives (write code permitted in
 * read-only mode). Some patterns will match local-variable assignments
 * that happen to share an SDK property name (e.g. a regex for `\.position\s*=`
 * matches `let position = …`). This is by design.
 */
import { ExecuteCodeSurface } from '../../../contracts/agent-execute-code.js';
import allowedFunctionsJson from './allowed-functions.json' with { type: 'json' };
import rawSdkPatternsJson from './raw-sdk-patterns.json' with { type: 'json' };
const allowedFunctions = allowedFunctionsJson;
const rawSdkPatterns = rawSdkPatternsJson;
const OPTIONAL_TS_TYPE_ARGS = String.raw `(?:\s*<[^>\n]+>)?`;
function toAllowedFunctionsKey(surface) {
    return surface === ExecuteCodeSurface.Web ? 'web' : 'excel';
}
function getApprovalFunctions(surface) {
    return allowedFunctions[toAllowedFunctionsKey(surface)].approval;
}
function detectApprovalFunctionAliases(code, functionName) {
    const aliases = new Set();
    const destructurePattern = new RegExp(`(?:const|let|var)\\s*\\{[^}]*\\b${functionName}\\b(?:\\s*:\\s*([A-Za-z_$][\\w$]*))?[^}]*\\}\\s*=`, 'g');
    const assignmentPattern = new RegExp(`(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*[A-Za-z_$][\\w$]*\\s*(?:\\.\\s*${functionName}|\\[\\s*["']${functionName}["']\\s*\\])`, 'g');
    for (const match of code.matchAll(destructurePattern)) {
        aliases.add(match[1] ?? functionName);
    }
    for (const match of code.matchAll(assignmentPattern)) {
        if (match[1]) {
            aliases.add(match[1]);
        }
    }
    return [...aliases];
}
function detectApprovalFunctionWrites(code, surface) {
    const disallowed = new Set();
    for (const functionName of getApprovalFunctions(surface)) {
        const directCallPattern = new RegExp(`\\.${functionName}${OPTIONAL_TS_TYPE_ARGS}\\s*\\(`);
        const computedCallPattern = new RegExp(`\\[\\s*["']${functionName}["']\\s*\\]${OPTIONAL_TS_TYPE_ARGS}\\s*\\(`);
        if (directCallPattern.test(code) || computedCallPattern.test(code)) {
            disallowed.add(functionName);
            continue;
        }
        const aliases = detectApprovalFunctionAliases(code, functionName);
        if (aliases.some((alias) => new RegExp(`\\b${alias}\\s*\\(`).test(code))) {
            disallowed.add(functionName);
        }
    }
    return [...disallowed];
}
const EXCEL_RUN_ALIAS_PATTERN = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*Excel\s*\.\s*run\b/g;
const EXCEL_RUN_LABEL = 'Excel.run (raw Office.js)';
function detectRawSdkWrites(code, surface) {
    const disallowed = new Set();
    for (const entry of rawSdkPatterns[toAllowedFunctionsKey(surface)]) {
        if (new RegExp(entry.pattern).test(code)) {
            disallowed.add(entry.label);
        }
        // Excel.run is the canonical raw-Office.js entry point; LLM-generated
        // code occasionally aliases it (`const run = Excel.run; run(...)`),
        // which the simple pattern above misses.
        if (entry.label === EXCEL_RUN_LABEL) {
            const aliases = [...code.matchAll(EXCEL_RUN_ALIAS_PATTERN)]
                .map((match) => match[1])
                .filter((alias) => alias != null);
            if (aliases.some((alias) => new RegExp(`\\b${alias}\\s*\\(`).test(code))) {
                disallowed.add(entry.label);
            }
        }
    }
    return [...disallowed];
}
/** Returns the labels of any disallowed write paths the code would invoke. */
export function detectEmbeddedReadOnlyViolations(code, surface) {
    const disallowed = detectApprovalFunctionWrites(code, surface);
    disallowed.push(...detectRawSdkWrites(code, surface));
    return disallowed;
}
//# sourceMappingURL=read-only-gate.js.map