"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.postProcessGeneratedClickhouseSql = void 0;
const logger_1 = __importDefault(require("../../../../logger"));
const pulseService_1 = require("../../pulseService");
const guardrailReferenceTablesClickhouse_1 = require("../tools/guardrailReferenceTablesClickhouse");
const GUARDRAIL_RESULTS_TABLE_NAME = 'guardrail_results';
const CLICKHOUSE_APPLICATIONS_TABLE_NAME = 'applications';
const RESERVED_FROM_SECOND_TOKEN = new Set([
    'array',
    'where',
    'join',
    'left',
    'right',
    'inner',
    'cross',
    'full',
    'as',
    'on',
    'using',
    'sample',
    'final',
    'prewhere',
].map((s) => s.toLowerCase()));
const GUARDRAIL_FROM_RESERVED = new Set(['array', 'where', 'join', 'left', 'right', 'inner', 'cross', 'full', 'as', 'on'].map((s) => s.toLowerCase()));
const removeRedundantAppFilter = (sql) => {
    const match = sql.match(/(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?(application_id|ServiceName|SpanAttributes\['application\.id'\])\s*=\s*'([^']+)'/i);
    if (!match) {
        return sql;
    }
    let normalized = sql.replace(/(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?application_id\s+IN\s*\([\s\S]*?\)\s+AND\s*/gi, '');
    normalized = normalized.replace(/(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?application_id\s+IN\s*\([\s\S]*?\)/gi, '');
    normalized = normalized.replace(/(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?ServiceName\s+IN\s*\([\s\S]*?\)\s+AND\s*/gi, '');
    normalized = normalized.replace(/(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?ServiceName\s+IN\s*\([\s\S]*?\)/gi, '');
    return normalized;
};
const hasExplicitApplicationFilter = (sql) => {
    const hasAppOrServiceEq = /\b(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?(application_id|ServiceName)\s*=\s*'[^']+'/i.test(sql);
    const hasSpanAppIdEq = /SpanAttributes\['application\.id'\]\s*=\s*'[^']+'/i.test(sql);
    return hasAppOrServiceEq || hasSpanAppIdEq;
};
const hasExplicitProjectFilter = (sql) => {
    return (/\b(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?project_id\s*=\s*'[^']+'/i.test(sql) ||
        /\b(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?project_id\s+IN\s*\(/i.test(sql));
};
const escapeSqlStringLiteral = (value) => value.replace(/'/g, "''");
/**
 * Outer driving table for project-scope filters. First FROM in the query wins.
 */
const extractFirstFromTableAndAlias = (sql) => {
    const fromMatch = sql.match(/\bfrom\s+(\w+)/i);
    if (!fromMatch?.[1]) {
        return null;
    }
    const table = fromMatch[1];
    const start = (fromMatch.index ?? 0) + fromMatch[0].length;
    const rest = sql.slice(start);
    if (/^\s+array\s+join\b/i.test(rest)) {
        return { table, alias: null };
    }
    const asMatch = rest.match(/^\s+as\s+([a-zA-Z_][a-zA-Z0-9_]*)\b/i);
    if (asMatch?.[1]) {
        return { table, alias: asMatch[1] };
    }
    const aliasMatch = rest.match(/^\s+([a-zA-Z_][a-zA-Z0-9_]*)\b/i);
    if (aliasMatch?.[1]) {
        const cand = aliasMatch[1];
        if (RESERVED_FROM_SECOND_TOKEN.has(cand.toLowerCase())) {
            return { table, alias: null };
        }
        return { table, alias: cand };
    }
    return { table, alias: null };
};
const buildProjectScopedAppColumn = (table, alias) => {
    const t = table.toLowerCase();
    if (t === pulseService_1.OTEL_TRACES_TABLE_NAME.toLowerCase()) {
        return alias ? `${alias}.ServiceName` : `${pulseService_1.OTEL_TRACES_TABLE_NAME}.ServiceName`;
    }
    const isEvalOrGuard = t === pulseService_1.EVAL_RESULTS_TABLE_NAME.toLowerCase() || t === GUARDRAIL_RESULTS_TABLE_NAME.toLowerCase();
    if (isEvalOrGuard) {
        return alias ? `${alias}.application_id` : `${t}.application_id`;
    }
    return null;
};
const normalizeApplicationAttributeFilter = (sql) => {
    if (/from\s+otel_traces/i.test(sql)) {
        return sql.replace(/SpanAttributes\['application\.id'\]\s*=\s*'([^']+)'/gi, "ServiceName = '$1'");
    }
    if (/from\s+eval_results/i.test(sql)) {
        return sql.replace(/SpanAttributes\['application\.id'\]\s*=\s*'([^']+)'/gi, "application_id = '$1'");
    }
    return sql;
};
const collapseDuplicateWhereAnd = (sql) => {
    return sql.replace(/\bWHERE\s+AND\b/gi, 'WHERE').replace(/\bAND\s+AND\b/gi, 'AND');
};
const fixCommaBeforeArrayJoin = (sql) => {
    return sql.replace(/,\s*ARRAY JOIN\b/gi, ' ARRAY JOIN');
};
const fixCastCommaStringSyntax = (sql) => {
    return sql.replace(/CAST\s*\(\s*([a-zA-Z0-9_.]+)\s*,\s*'String'\s*\)/gi, 'CAST($1 AS String)');
};
/**
 * eval_results stores provider, prompt, response, grading_result, metadata, test_case, named_scores, error
 * as native JSON in ClickHouse. JSONExtract*(col, ...) requires a String JSON payload for the common overload;
 * passing JSON type fails ("illegal type: JSON"). Wrap with CAST(... AS String) when not already CAST.
 */
const EVAL_RESULTS_JSON_TYPED_COLUMNS_PATTERN = 'provider|prompt|response|grading_result|metadata|test_case|named_scores|error';
const wrapJsonExtractFirstArgCastStringForEvalJsonTypedColumns = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    const re = new RegExp(`(JSONExtract(?:String|Float|Bool|Int|UInt|ArrayRaw|Keys|Raw))\\s*\\(\\s*(?!CAST\\s*\\()((?:[a-zA-Z_][a-zA-Z0-9_]*\\.)?(?:${EVAL_RESULTS_JSON_TYPED_COLUMNS_PATTERN}))\\s*,`, 'gi');
    return sql.replace(re, (_full, fn, col) => `${fn}(CAST(${col} AS String),`);
};
/**
 * After ARRAY JOIN ... AS j, GROUP BY must not use the **row** column failure_reason when the SELECT
 * aggregates per-component fields from `j` — ClickHouse binds `failure_reason` to eval_results.failure_reason
 * and errors ("Column 'j' is not under aggregate function..."). Rewrite failure_reason in GROUP BY lists only.
 */
const fixRowFailureReasonInGroupByAfterArrayJoinJ = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    if (!/\bARRAY\s+JOIN[\s\S]*?\bAS\s+j\b/i.test(sql)) {
        return sql;
    }
    if (!/\bJSONExtract\w*\s*\(\s*j\s*,/i.test(sql)) {
        return sql;
    }
    const rewriteList = (list) => {
        let next = list.replace(/\b(?:eval_results|[a-zA-Z_][a-zA-Z0-9_]*)\.failure_reason\b/gi, "JSONExtractString(j, 'reason')");
        next = next.replace(/\bfailure_reason\b/gi, "JSONExtractString(j, 'reason')");
        return next;
    };
    // Do not use [^\n;] for the list: GROUP BY may contain newlines (pretty-printed SQL) and then
    // the rewrite would never run, leaving eval_results.failure_reason in GROUP BY ("Column j is not under aggregate...").
    const groupByListUntilOrderBy = /\b(GROUP BY\s+)((?:(?!\s+\bORDER BY\b)[\s\S])+?)(\s+\bORDER BY\b)/gi;
    const groupByListUntilLimit = /\b(GROUP BY\s+)((?:(?!\s+\bLIMIT\b)[\s\S])+?)(\s+\bLIMIT\b)/gi;
    const groupByListUntilEnd = /\b(GROUP BY\s+)((?:(?!\s+\b(?:ORDER BY|LIMIT|HAVING)\b)[\s\S])+?)$/gim;
    let out = sql.replace(groupByListUntilOrderBy, (full, intro, list, tail) => {
        if (!/\bfailure_reason\b/i.test(list)) {
            return full;
        }
        return `${intro}${rewriteList(list)}${tail}`;
    });
    out = out.replace(groupByListUntilLimit, (full, intro, list, tail) => {
        if (!/\bfailure_reason\b/i.test(list)) {
            return full;
        }
        return `${intro}${rewriteList(list)}${tail}`;
    });
    out = out.replace(groupByListUntilEnd, (full, intro, list) => {
        const trimmed = list.trim();
        if (!trimmed || !/\bfailure_reason\b/i.test(trimmed)) {
            return full;
        }
        return `${intro}${rewriteList(trimmed)}`;
    });
    return out;
};
/**
 * LLM often groups by SELECT aliases (metric, strategy, plugin_id, …) plus a stray row column
 * failure_reason — ClickHouse then rejects j-based SELECT expressions. Positional GROUP BY matches
 * the first four non-aggregate SELECT columns regardless of alias / dialect quirks.
 */
const fixArrayJoinJMetricStrategyPluginGroupByOrdinals = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    if (!/\bARRAY\s+JOIN[\s\S]*?\bAS\s+j\b/i.test(sql)) {
        return sql;
    }
    if (!/JSONExtractString\s*\(\s*j\s*,\s*'assertion'\s*,\s*'type'\s*\)\s+AS\s+metric\b/i.test(sql)) {
        return sql;
    }
    const pattern = /\bGROUP BY\s+metric\s*,\s*strategy\s*,\s*plugin_id\s*,\s*(?:failure_reason|grading_component_reason|JSONExtractString\s*\(\s*j\s*,\s*'reason'\s*\))\b/gi;
    return sql.replace(pattern, 'GROUP BY 1, 2, 3, 4');
};
/**
 * Inner subquery pattern: SELECT ... JSONExtract*(j,...) ... FROM eval_results ... ARRAY JOIN ... AS j ...
 * GROUP BY failure_reason (wrong). Must group by JSONExtractString(j, 'reason').
 */
const fixMisgroupedUnqualifiedFailureReasonGroupByAfterArrayJoinJ = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    const re = /(SELECT(?:(?!ARRAY\s+JOIN)[\s\S])*?JSONExtract\w*\s*\(\s*j\s*,[\s\S]*?FROM\s+eval_results[\s\S]*?\bARRAY\s+JOIN[\s\S]*?\bAS\s+j[\s\S]*?)\bGROUP BY\s+failure_reason\b/gi;
    return sql.replace(re, "$1GROUP BY JSONExtractString(j, 'reason')");
};
/**
 * LLM sometimes puts arrayJoin(JSONExtractArrayRaw(CAST(grading_result...), 'componentResults')) AS j
 * in the SELECT list with aggregates + GROUP BY. ClickHouse then rejects grading_result (not grouped).
 * Move expansion to a proper ARRAY JOIN after FROM eval_results (same as ARRAY JOIN ... AS j).
 */
/**
 * `componentResults` is a JSON **array** of objects — not a nested object path on the string form of
 * grading_result. `JSONExtractString(CAST(grading_result AS String), 'componentResults', 'reason')`
 * is invalid and can make `failure_reason = 2` bind to a String vs UInt8 error when aliased as failure_reason.
 * After ARRAY JOIN ... AS j, use JSONExtractString(j, ...).
 */
const fixWrongComponentResultsJsonExtractsUseArrayJoinJ = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    if (!/\bARRAY\s+JOIN[\s\S]*?\bAS\s+j\b/i.test(sql)) {
        return sql;
    }
    let out = sql;
    out = out.replace(/JSONExtractString\s*\(\s*CAST\s*\(\s*((?:(?:eval_results|[a-zA-Z_][a-zA-Z0-9_]*)\.)?)grading_result\s+AS\s+String\s*\)\s*,\s*'componentResults'\s*,\s*'reason'\s*\)\s+AS\s+failure_reason\b/gi, "JSONExtractString(j, 'reason') AS grading_component_reason");
    out = out.replace(/JSONExtractString\s*\(\s*CAST\s*\(\s*((?:(?:eval_results|[a-zA-Z_][a-zA-Z0-9_]*)\.)?)grading_result\s+AS\s+String\s*\)\s*,\s*"componentResults"\s*,\s*"reason"\s*\)\s+AS\s+failure_reason\b/gi, 'JSONExtractString(j, "reason") AS grading_component_reason');
    out = out.replace(/JSONExtractString\s*\(\s*CAST\s*\(\s*((?:(?:eval_results|[a-zA-Z_][a-zA-Z0-9_]*)\.)?)grading_result\s+AS\s+String\s*\)\s*,\s*'componentResults'\s*,\s*'assertion'\s*,\s*'type'\s*\)\s+AS\s+failure_type\b/gi, "JSONExtractString(j, 'assertion', 'type') AS failure_type");
    out = out.replace(/JSONExtractString\s*\(\s*CAST\s*\(\s*((?:(?:eval_results|[a-zA-Z_][a-zA-Z0-9_]*)\.)?)grading_result\s+AS\s+String\s*\)\s*,\s*"componentResults"\s*,\s*"assertion"\s*,\s*"type"\s*\)\s+AS\s+failure_type\b/gi, 'JSONExtractString(j, "assertion", "type") AS failure_type');
    return out;
};
const evalResultsQualifiedColumn = (sql, col) => {
    const info = extractFirstFromTableAndAlias(sql);
    if (!info || info.table.toLowerCase() !== pulseService_1.EVAL_RESULTS_TABLE_NAME.toLowerCase()) {
        return null;
    }
    if (info.alias) {
        return `${info.alias}.${col}`;
    }
    if (col === 'failure_reason') {
        return 'eval_results.failure_reason';
    }
    return col;
};
/**
 * Execution errors (\`failure_reason = 2\`) often have \`grading_result IS NULL\`. A bare
 * \`grading_result IS NOT NULL AND ...\` drops those rows before ARRAY JOIN. Broaden the predicate
 * and qualify \`failure_reason = 2\` so it cannot bind to a wrong SELECT alias.
 */
const fixGradingNotNullAllowsExecutionErrorsInWhere = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    if (!/\bARRAY\s+JOIN[\s\S]*?\bAS\s+j\b/i.test(sql)) {
        return sql;
    }
    if (!/\bfailure_reason\s*=\s*2\b/i.test(sql)) {
        return sql;
    }
    const info = extractFirstFromTableAndAlias(sql);
    const gr = evalResultsQualifiedColumn(sql, 'grading_result');
    const fr = evalResultsQualifiedColumn(sql, 'failure_reason');
    if (!info || !gr || !fr) {
        return sql;
    }
    const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    let out = sql;
    if (info.alias) {
        out = out.replace(new RegExp(`\\b${esc(gr)}\\s+IS\\s+NOT\\s+NULL\\s+AND`, 'gi'), `(${gr} IS NOT NULL OR ${fr} = 2) AND`);
    }
    else {
        out = out.replace(/\b(?:eval_results\.)?grading_result\s+IS\s+NOT\s+NULL\s+AND/gi, `(grading_result IS NOT NULL OR ${fr} = 2) AND`);
    }
    out = out.replace(/\(\s*success\s*=\s*0\s+OR\s+failure_reason\s*=\s*2\s*\)/gi, `(success = 0 OR ${fr} = 2)`);
    return out;
};
/**
 * ARRAY JOIN on an empty \`JSONExtractArrayRaw(..., 'componentResults')\` yields **no rows** when
 * grading_result is NULL. For \`failure_reason = 2\`, inject a one-element JSON fragment so execution-error
 * rows still contribute one logical "component" row for GROUP BY / counts.
 */
const fixArrayJoinSyntheticComponentWhenExecutionErrorAndGradingNull = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    if (!/\bfailure_reason\s*=\s*2\b/i.test(sql)) {
        return sql;
    }
    const fr = evalResultsQualifiedColumn(sql, 'failure_reason');
    const gr = evalResultsQualifiedColumn(sql, 'grading_result');
    if (!fr || !gr) {
        return sql;
    }
    const synth = "['{\"pass\":false,\"reason\":\"Execution error (grading_result is null)\",\"assertion\":{\"type\":\"execution_error\"}}']";
    const re = /\bARRAY\s+JOIN\s+JSONExtractArrayRaw\s*\(\s*CAST\s*\(\s*((?:(?:eval_results|[a-zA-Z_][a-zA-Z0-9_]*)\.)?)grading_result\s+AS\s+String\s*\)\s*,\s*'componentResults'\s*\)\s+AS\s+j\b/gi;
    return sql.replace(re, `ARRAY JOIN if((${fr}) = 2 AND (${gr}) IS NULL, ${synth}, JSONExtractArrayRaw(CAST(${gr} AS String), 'componentResults')) AS j`);
};
const fixEvalResultsArrayJoinSelectToClause = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    const selectListArrayJoin = /,\s*arrayJoin\s*\(\s*JSONExtractArrayRaw\s*\(\s*CAST\s*\(\s*(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?grading_result\s+AS\s+String\s*\)\s*,\s*'componentResults'\s*\)\s*\)\s*AS\s+j\b/gi;
    if (!selectListArrayJoin.test(sql)) {
        return sql;
    }
    const alreadyHasClauseArrayJoin = /\bARRAY\s+JOIN\s+JSONExtractArrayRaw\s*\(\s*CAST\s*\(\s*(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?grading_result\s+AS\s+String\s*\)/i.test(sql);
    let out = sql.replace(selectListArrayJoin, '');
    out = out.replace(/\bSELECT\s+,/gi, 'SELECT ');
    out = out.replace(/,\s*,+/g, ',');
    if (alreadyHasClauseArrayJoin) {
        return out;
    }
    const fromEvalResults = /\bFROM\s+eval_results(?:\s+AS\s+([a-zA-Z_][a-zA-Z0-9_]*)|\s+(?!WHERE\b|GROUP\b|ORDER\b|LIMIT\b|HAVING\b|ARRAY\b|INNER\b|LEFT\b|RIGHT\b|CROSS\b|JOIN\b)([a-zA-Z_][a-zA-Z0-9_]*))?/i;
    return out.replace(fromEvalResults, (full, asAlias, plainAlias) => {
        const alias = asAlias || plainAlias || '';
        const gradingCol = alias ? `${alias}.grading_result` : 'grading_result';
        const joinPart = ` ARRAY JOIN JSONExtractArrayRaw(CAST(${gradingCol} AS String), 'componentResults') AS j`;
        return `${full}${joinPart}`;
    });
};
const stripGuardrailResultsHallucinatedStatus = (sql) => {
    if (!/\bfrom\s+guardrail_results\b/i.test(sql)) {
        return sql;
    }
    const fromMatch = sql.match(/\bfrom\s+guardrail_results(?:\s+as\s+|\s+)([a-zA-Z_][a-zA-Z0-9_]*)\b/i);
    const cand = fromMatch?.[1];
    const alias = cand && !GUARDRAIL_FROM_RESERVED.has(cand.toLowerCase()) ? cand : null;
    let out = sql;
    if (alias) {
        out = out.replace(new RegExp(`\\b${alias}\\.status\\b`, 'gi'), '');
    }
    out = out.replace(/\bguardrail_results\.status\b/gi, '');
    out = out.replace(/,\s*,+/g, ',');
    out = out.replace(/\bselect\s+,/gi, 'SELECT ');
    out = out.replace(/,\s+from\b/gi, ' FROM');
    return out;
};
const rewriteGuardrailValidatorsUuidLookupToGuardrailResults = (sql) => {
    const fromValidators = new RegExp(`\\bfrom\\s+${guardrailReferenceTablesClickhouse_1.CLICKHOUSE_GUARDRAIL_VALIDATORS_TABLE}\\b`, 'i');
    if (!fromValidators.test(sql)) {
        return sql;
    }
    const hasUnionJoinOrGuardrailResults = /\bunion\b/i.test(sql) || /\bjoin\b/i.test(sql) || /\bfrom\s+guardrail_results\b/i.test(sql);
    if (hasUnionJoinOrGuardrailResults) {
        return sql;
    }
    const whereMatch = sql.match(new RegExp(`\\bwhere\\s+([\\s\\S]*?)(?=\\s+limit\\s+\\d+|\\s*;?\\s*$)`, 'i'));
    if (!whereMatch?.[1]) {
        return sql;
    }
    const whereBody = whereMatch[1].trim();
    const idEq = whereBody.match(/^(?:(\w+)\.)?id\s*=\s*['"]([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})['"]$/i);
    if (!idEq?.[2]) {
        return sql;
    }
    const uuid = idEq[2];
    const limitMatch = sql.match(/\blimit\s+(\d+)/i);
    const limitClause = limitMatch ? ` LIMIT ${limitMatch[1]}` : '';
    logger_1.default.warn(`Argus text-to-SQL: rewrote ${guardrailReferenceTablesClickhouse_1.CLICKHOUSE_GUARDRAIL_VALIDATORS_TABLE} WHERE id=<uuid> to ${GUARDRAIL_RESULTS_TABLE_NAME}.id lookup`);
    return (`SELECT gr.id, gr.application_id, gr.project_id, gr.guardrail_stage, gr.pass, gr.score, gr.latency, ` +
        `gr.execution_time, gr.error, gr.final_content, gr.prompt, gr.response, gr.component_results, ` +
        `gr.environment, gr.app_version, gr.metadata, gr.created_at FROM ${GUARDRAIL_RESULTS_TABLE_NAME} AS gr ` +
        `WHERE gr.id = '${uuid}'${limitClause}`);
};
const stripHallucinatedEvalResultsEnvironmentColumns = (sql) => {
    const fromInfo = extractFirstFromTableAndAlias(sql);
    if (!fromInfo || fromInfo.table.toLowerCase() !== pulseService_1.EVAL_RESULTS_TABLE_NAME.toLowerCase()) {
        return sql;
    }
    const prefixes = [];
    if (fromInfo.alias) {
        prefixes.push(fromInfo.alias);
    }
    prefixes.push(pulseService_1.EVAL_RESULTS_TABLE_NAME);
    const hallucinatedCols = ['environment', 'app_version'];
    let out = sql;
    for (const pre of prefixes) {
        const escaped = pre.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        for (const col of hallucinatedCols) {
            out = out.replace(new RegExp(`\\b${escaped}\\.${col}\\b`, 'gi'), '');
        }
    }
    out = out.replace(/,\s*,+/g, ',');
    out = out.replace(/\bselect\s+,/gi, 'SELECT ');
    out = out.replace(/,\s+from\b/gi, ' FROM');
    out = out.replace(/\(\s*,/g, '(');
    out = out.replace(/,\s*\)/g, ')');
    return out;
};
const disambiguateEvalResultsJsonReasonAlias = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    const hasGradingJsonExtract = /JSONExtractArrayRaw\s*\(\s*CAST\s*\(\s*(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?grading_result\s+AS\s+String\s*\)/i.test(sql);
    if (!hasGradingJsonExtract) {
        return sql;
    }
    let out = sql.replace(/JSONExtractString\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*'reason'\s*\)\s+AS\s+failure_reason\b/gi, "JSONExtractString($1, 'reason') AS grading_component_reason");
    out = out.replace(/JSONExtractString\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*"reason"\s*\)\s+AS\s+failure_reason\b/gi, 'JSONExtractString($1, "reason") AS grading_component_reason');
    return out;
};
const injectApplicationsProjectScope = (sql, projectId, activeApplicationIds) => {
    const fromInfo = extractFirstFromTableAndAlias(sql);
    if (!fromInfo || fromInfo.table.toLowerCase() !== CLICKHOUSE_APPLICATIONS_TABLE_NAME) {
        return sql;
    }
    if (hasExplicitProjectFilter(sql)) {
        return sql;
    }
    const pid = typeof projectId === 'string' ? projectId.trim() : '';
    if (!pid && activeApplicationIds.length === 0) {
        if (/\bwhere\b/i.test(sql)) {
            return collapseDuplicateWhereAnd(sql.replace(/\bwhere\b/i, 'WHERE 1 = 0 AND'));
        }
        return sql.replace(/\b(GROUP BY|ORDER BY|LIMIT)\b/i, 'WHERE 1 = 0 $1');
    }
    const idCol = fromInfo.alias ? `${fromInfo.alias}.id` : `${CLICKHOUSE_APPLICATIONS_TABLE_NAME}.id`;
    const projectCol = fromInfo.alias
        ? `${fromInfo.alias}.project_id`
        : `${CLICKHOUSE_APPLICATIONS_TABLE_NAME}.project_id`;
    const predicates = [];
    if (pid) {
        predicates.push(`${projectCol} = '${escapeSqlStringLiteral(pid)}'`);
    }
    else {
        const quoted = activeApplicationIds
            .map((id) => `'${escapeSqlStringLiteral(String(id))}'`)
            .join(',');
        predicates.push(`${idCol} IN (${quoted})`);
    }
    const clause = predicates.join(' AND ');
    let next = sql;
    if (/\bwhere\b/i.test(next)) {
        next = next.replace(/\bwhere\b/i, `WHERE ${clause} AND`);
    }
    else {
        next = next.replace(/\b(GROUP BY|ORDER BY|LIMIT)\b/i, `WHERE ${clause} $1`);
    }
    return collapseDuplicateWhereAnd(next);
};
const injectAppFilter = (sql, ids) => {
    if (ids.length === 0 || hasExplicitApplicationFilter(sql)) {
        return sql;
    }
    const quoted = ids.map((id) => `'${id}'`).join(',');
    let next = sql.replace(/(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?application_id\s+IN\s*\([\s\S]*?\)\s*AND\s+/gi, '');
    next = next.replace(/(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?application_id\s+IN\s*\([\s\S]*?\)/gi, '');
    next = next.replace(/(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?ServiceName\s+IN\s*\([\s\S]*?\)\s*AND\s+/gi, '');
    next = next.replace(/(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?ServiceName\s+IN\s*\([\s\S]*?\)/gi, '');
    const fromInfo = extractFirstFromTableAndAlias(next);
    const appCol = fromInfo ? buildProjectScopedAppColumn(fromInfo.table, fromInfo.alias) : null;
    if (!appCol) {
        return collapseDuplicateWhereAnd(next);
    }
    const out = next.replace(/\bwhere\b/i, `WHERE ${appCol} IN (${quoted}) AND`);
    return collapseDuplicateWhereAnd(out);
};
const fixNullJsonIssue = (sql) => {
    if (/\bunion\b/i.test(sql)) {
        return sql;
    }
    const arrayJoinCast = sql.match(/JSONExtractArrayRaw\s*\(\s*CAST\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*\.)?grading_result\s+AS\s+String\s*\)/i);
    if (!arrayJoinCast) {
        return sql;
    }
    const prefix = arrayJoinCast[1] ?? '';
    const gradingCol = `${prefix}grading_result`;
    const escaped = gradingCol.replace(/\./g, '\\.');
    const alreadyHasNullCheck = new RegExp(`${escaped}\\s+IS\\s+NOT\\s+NULL`, 'i').test(sql);
    if (alreadyHasNullCheck) {
        return sql;
    }
    if (/\bwhere\b/i.test(sql)) {
        return sql.replace(/\bwhere\b/i, `WHERE ${gradingCol} IS NOT NULL AND`);
    }
    return sql.replace(/\b(GROUP BY|ORDER BY|LIMIT)\b/i, `WHERE ${gradingCol} IS NOT NULL $1`);
};
const rewriteInvalidEvalResultsMediaFilter = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    const hasOtelTracesFrom = new RegExp(`\\bfrom\\s+${pulseService_1.OTEL_TRACES_TABLE_NAME}\\b`, 'i').test(sql);
    if (hasOtelTracesFrom) {
        return sql;
    }
    return sql.replace(/(?:\bAND\s+)?SpanAttributes\['gen_ai\.output\.type'\]\s*=\s*'([^']+)'/gi, (_full, mediaType) => `AND trace_id IN (SELECT DISTINCT TraceId FROM ${pulseService_1.OTEL_TRACES_TABLE_NAME} WHERE SpanAttributes['gen_ai.output.type'] = '${mediaType}')`);
};
const fixReturnedRowsFloatCasts = (sql) => {
    let out = sql.replace(/toFloat64\(\s*SpanAttributes\['db\.response\.returned_rows']\s*\)/gi, "toFloat64OrZero(SpanAttributes['db.response.returned_rows'])");
    out = out.replace(/SpanAttributes\['db\.response\.returned_rows']\s*([<>]=?|=)\s*(\d+(?:\.\d+)?)/gi, "toFloat64OrZero(SpanAttributes['db.response.returned_rows']) $1 $2");
    return out;
};
/**
 * LLM sometimes emits groupArray((expr, count())) alongside other top-level aggregates.
 * ClickHouse rejects count() nested inside groupArray. Collect expr only (counts need a subquery).
 */
const fixNestedCountInsideGroupArrayTuple = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    let out = sql.replace(/groupArray\s*\(\s*\(\s*JSONExtractString\s*\(\s*j\s*,\s*'assertion'\s*,\s*'type'\s*\)\s*,\s*count\s*\(\s*\)\s*\)\s*\)/gi, "groupArray(JSONExtractString(j, 'assertion', 'type'))");
    out = out.replace(/groupArray\s*\(\s*\(\s*JSONExtractString\s*\(\s*j\s*,\s*"assertion"\s*,\s*"type"\s*\)\s*,\s*count\s*\(\s*\)\s*\)\s*\)/gi, 'groupArray(JSONExtractString(j, "assertion", "type"))');
    return out;
};
/**
 * Outer `countIf(...) AS fail_count` + inner `count() AS fail_count` + `groupArray((fail_metric, fail_count, ...))`
 * makes ClickHouse resolve `fail_count` in the tuple to the **outer** aggregate ("nested aggregate" error).
 * Rename the inner count alias and the tuple reference.
 */
const fixFailCountAliasShadowingInEvalSummaryGroupArray = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    if (!/\bcountif\s*\([^)]+\)\s+AS\s+fail_count\b/i.test(sql)) {
        return sql;
    }
    if (!/\bgrouparray\s*\(\s*\(\s*fail_metric\s*,\s*fail_count\b/i.test(sql)) {
        return sql;
    }
    if (!/\bcount\s*\(\s*\)\s+AS\s+fail_count\b/i.test(sql)) {
        return sql;
    }
    let out = sql.replace(/\bcount\s*\(\s*\)\s+AS\s+fail_count\b/gi, 'count() AS failed_component_count');
    out = out.replace(/\(\s*fail_metric\s*,\s*fail_count\s*,/gi, '(fail_metric, failed_component_count,');
    return out;
};
/**
 * Inner rollup uses `AS plugin_id_raw` but outer `groupArray((fail_metric, failed_row_count, common_plugin, common_reason))`
 * references a non-existent `common_plugin` — ClickHouse: "Unknown expression ... common_plugin".
 */
const fixGroupArrayTupleCommonPluginMatchesInnerPluginIdRaw = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    if (!/\bAS\s+plugin_id_raw\b/i.test(sql)) {
        return sql;
    }
    return sql.replace(/\bgroupArray\s*\(\s*\(\s*fail_metric\s*,\s*failed_row_count\s*,\s*common_plugin\s*,\s*common_reason\s*\)\s*\)/gi, 'groupArray((fail_metric, failed_row_count, plugin_id_raw, common_reason))');
};
const balancedParenRange = (s, openIdx) => {
    if (openIdx < 0 || openIdx >= s.length || s[openIdx] !== '(') {
        return null;
    }
    let depth = 0;
    for (let i = openIdx; i < s.length; i++) {
        const c = s[i];
        if (c === '(') {
            depth++;
        }
        else if (c === ')') {
            depth--;
            if (depth === 0) {
                return { inner: s.slice(openIdx + 1, i), closeIdx: i };
            }
        }
    }
    return null;
};
const sliceOuterSelectListRange = (sql) => {
    const sm = /\bselect\b/i.exec(sql);
    if (!sm || sm.index === undefined) {
        return null;
    }
    const start = sm.index + sm[0].length;
    let depth = 0;
    for (let i = start; i < sql.length; i++) {
        const c = sql[i];
        if (c === '(') {
            depth++;
        }
        else if (c === ')') {
            depth--;
        }
        else if (depth === 0) {
            const rest = sql.slice(i);
            if (/^\s*from\b/i.test(rest)) {
                return { start, end: i };
            }
        }
    }
    return null;
};
const stripEvalInnerWhereToRowLevelScope = (whereRaw) => {
    let w = whereRaw.replace(/\beval_results\./gi, '').trim();
    w = w.replace(/^\s*grading_result\s+IS\s+NOT\s+NULL\s+AND\s+/i, '');
    w = w.replace(/^\s*\(\s*(?:eval_results\.)?grading_result\s+IS\s+NOT\s+NULL\s+OR\s+(?:eval_results\.)?failure_reason\s*=\s*2\s*\)\s+AND\s+/i, '');
    w = w.replace(/\s+AND\s+JSONExtractBool\s*\(\s*j\s*,\s*'pass'\s*\)\s*=\s*false\b/gi, '');
    w = w.replace(/\s+AND\s+JSONExtractBool\s*\(\s*j\s*,\s*"pass"\s*\)\s*=\s*false\b/gi, '');
    w = w.replace(/\s+/g, ' ').trim();
    return w;
};
/**
 * Outer `countIf(success ...)` / `countIf(failure_reason ...)` over `FROM ( SELECT ... GROUP BY ... ) t`
 * where the subquery never SELECTs `success` or `failure_reason` — ClickHouse: "Unknown identifier `success`".
 * Rewrite those aggregates as scalar subqueries against eval_results using the inner WHERE (minus component-only predicates).
 */
const fixOuterEvalRowAggregatesOverGroupedSubquery = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    const sel = sliceOuterSelectListRange(sql);
    if (!sel) {
        return sql;
    }
    const outerList = sql.slice(sel.start, sel.end);
    if (/\(SELECT\s+countIf\s*\(\s*success\s*=\s*1\s+AND\s+failure_reason\s*!=\s*2\s*\)\s+FROM\s+eval_results\b/i.test(outerList)) {
        return sql;
    }
    if (!/\bcountIf\s*\(\s*success\s*=\s*1\b/i.test(outerList)) {
        return sql;
    }
    const afterList = sql.slice(sel.end);
    const fm = /^\s*FROM\s*\(\s*/i.exec(afterList);
    if (!fm || fm.index === undefined) {
        return sql;
    }
    const subqueryOpenParenIdx = sel.end + fm.index + fm[0].length - 1;
    const br = balancedParenRange(sql, subqueryOpenParenIdx);
    if (!br) {
        return sql;
    }
    const innerBody = br.inner.trim();
    if (!/\bFROM\s+eval_results\b/i.test(innerBody)) {
        return sql;
    }
    const innerHead = innerBody.split(/\bWHERE\b/i)[0] ?? '';
    if (/\beval_results\.success\b|\b,\s*success\s+AS\b|\bselect\s+[^\n]*\bsuccess\s*,/i.test(innerHead)) {
        return sql;
    }
    const whereMatch = innerBody.match(/\bwhere\s+([\s\S]+?)\bgroup\s+by\b/i);
    if (!whereMatch?.[1]) {
        return sql;
    }
    const wBase = stripEvalInnerWhereToRowLevelScope(whereMatch[1]);
    if (!wBase || !/\btest_run_id\b/i.test(wBase)) {
        return sql;
    }
    let list = outerList.trim();
    list = list.replace(/\bcountIf\s*\(\s*success\s*=\s*1\s+AND\s+failure_reason\s*!=\s*2\s*\)\s+AS\s+(\w+)\b/gi, `(SELECT countIf(success = 1 AND failure_reason != 2) FROM eval_results WHERE ${wBase}) AS $1`);
    list = list.replace(/\bcountIf\s*\(\s*success\s*=\s*0\s+AND\s+failure_reason\s*!=\s*2\s*\)\s+AS\s+(\w+)\b/gi, `(SELECT countIf(success = 0 AND failure_reason != 2) FROM eval_results WHERE ${wBase}) AS $1`);
    list = list.replace(/\bcountIf\s*\(\s*failure_reason\s*=\s*2\s*\)\s+AS\s+(\w+)\b/gi, `(SELECT countIf(failure_reason = 2) FROM eval_results WHERE ${wBase}) AS $1`);
    list = list.replace(/\bcount\s*\(\s*\*?\s*\)\s+AS\s+(\w+)\b/gi, `(SELECT count(*) FROM eval_results WHERE ${wBase}) AS $1`);
    let rest = sql.slice(sel.end);
    rest = rest.replace(/(\)\s*(?:AS\s+)?t)\s+WHERE\s+test_run_id\s*=\s*'[^']+'\s+AND\s+is_redteam\s*=\s*\d+/i, '$1');
    return `${sql.slice(0, sel.start)}${list}${rest}`;
};
/**
 * LLM sometimes emits `FROM ( ... failure rollup ... ) t , ( SELECT * FROM eval_results WHERE ... )`
 * without an alias — ClickHouse errors ("no alias for subquery"). That comma join also cross-joins
 * rollup rows with every eval row (wrong counts). Strip the redundant `SELECT *` arm and rewrite
 * outer aggregates as scalar subqueries + wrap `groupArray` over the rollup subquery only.
 */
const fixRedundantEvalResultsSelectStarCommaJoin = (sql) => {
    if (!/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    const needle = /,\s*\(\s*SELECT\s*\*\s+FROM\s+eval_results\b/gi;
    let last = null;
    let m = null;
    while ((m = needle.exec(sql)) !== null) {
        last = m;
    }
    if (!last || last.index === undefined) {
        return sql;
    }
    const openParenIdx = last.index + last[0].lastIndexOf('(');
    const br = balancedParenRange(sql, openParenIdx);
    if (!br) {
        return sql;
    }
    const removeStart = last.index;
    const removeEnd = br.closeIdx + 1;
    const withoutJoin = `${sql.slice(0, removeStart)}${sql.slice(removeEnd)}`;
    const fm = /\bfrom\s*\(\s*/i.exec(withoutJoin);
    if (!fm || fm.index === undefined) {
        return sql;
    }
    const innerOpen = fm.index + fm[0].length - 1;
    const innerBr = balancedParenRange(withoutJoin, innerOpen);
    if (!innerBr) {
        return sql;
    }
    const innerBody = innerBr.inner.trim();
    const whereMatch = innerBody.match(/\bwhere\s+([\s\S]+?)\bgroup\s+by\b/i);
    if (!whereMatch?.[1]) {
        return sql;
    }
    let wBase = whereMatch[1].replace(/\beval_results\./gi, '').trim();
    wBase = wBase.replace(/^\s*grading_result\s+IS\s+NOT\s+NULL\s+AND\s+/i, '');
    wBase = wBase.replace(/\s+AND\s+JSONExtractBool\s*\(\s*j\s*,\s*'pass'\s*\)\s*=\s*false\b/gi, '');
    wBase = wBase.replace(/\s+AND\s+JSONExtractBool\s*\(\s*j\s*,\s*"pass"\s*\)\s*=\s*false\b/gi, '');
    wBase = wBase.replace(/\s+/g, ' ').trim();
    if (!wBase) {
        return sql;
    }
    const sel = sliceOuterSelectListRange(withoutJoin);
    if (!sel) {
        return sql;
    }
    let list = withoutJoin.slice(sel.start, sel.end).trim();
    list = list.replace(/\beval_results\./gi, '');
    const gaMatch = list.match(/\bgroupArray\s*\(\s*\(\s*failure_metric\s*,\s*occurrences\s*,\s*common_reason\s*,\s*avg_score\s*,\s*notable_plugin\s*\)\s*\)\s+AS\s+(\w+)\b/i);
    if (!gaMatch?.[1]) {
        return sql;
    }
    const innerSub = `(${innerBody})`;
    list = list.replace(/\bgroupArray\s*\(\s*\(\s*failure_metric\s*,\s*occurrences\s*,\s*common_reason\s*,\s*avg_score\s*,\s*notable_plugin\s*\)\s*\)\s+AS\s+\w+\b/i, `(SELECT groupArray((failure_metric, occurrences, common_reason, avg_score, notable_plugin)) FROM ${innerSub} t) AS ${gaMatch[1]}`);
    list = list.replace(/\bcount\s*\(\s*\*?\s*\)\s+AS\s+total_tests\b/gi, `(SELECT count(*) FROM eval_results WHERE ${wBase}) AS total_tests`);
    list = list.replace(/\bcount\s*\(\s*\*?\s*\)\s+AS\s+total_evaluations\b/gi, `(SELECT count(*) FROM eval_results WHERE ${wBase}) AS total_evaluations`);
    list = list.replace(/\bcountIf\s*\(\s*\(\s*failure_reason\s*=\s*2\s*\)\s*\)\s+AS\s+error_count\b/gi, `(SELECT countIf((failure_reason = 2)) FROM eval_results WHERE ${wBase}) AS error_count`);
    list = list.replace(/\bcountIf\s*\(\s*failure_reason\s*=\s*2\s*\)\s+AS\s+error_count\b/gi, `(SELECT countIf(failure_reason = 2) FROM eval_results WHERE ${wBase}) AS error_count`);
    list = list.replace(/\bcountIf\s*\(\s*\(\s*success\s*=\s*1\s*\)\s+AND\s+\(\s*failure_reason\s*!=\s*2\s*\)\s*\)\s+AS\s+pass_count\b/gi, `(SELECT countIf((success = 1) AND (failure_reason != 2)) FROM eval_results WHERE ${wBase}) AS pass_count`);
    list = list.replace(/\bcountIf\s*\(\s*success\s*=\s*1\s+AND\s+failure_reason\s*!=\s*2\s*\)\s+AS\s+pass_count\b/gi, `(SELECT countIf(success = 1 AND failure_reason != 2) FROM eval_results WHERE ${wBase}) AS pass_count`);
    list = list.replace(/\bcountIf\s*\(\s*\(\s*success\s*=\s*0\s*\)\s+AND\s+\(\s*failure_reason\s*!=\s*2\s*\)\s*\)\s+AS\s+fail_count\b/gi, `(SELECT countIf((success = 0) AND (failure_reason != 2)) FROM eval_results WHERE ${wBase}) AS fail_count`);
    list = list.replace(/\bcountIf\s*\(\s*success\s*=\s*0\s+AND\s+failure_reason\s*!=\s*2\s*\)\s+AS\s+fail_count\b/gi, `(SELECT countIf(success = 0 AND failure_reason != 2) FROM eval_results WHERE ${wBase}) AS fail_count`);
    return `${withoutJoin.slice(0, sel.start)}${list}${withoutJoin.slice(sel.end)}`;
};
/**
 * ClickHouse rejects aggregates nested inside another aggregate, e.g.
 * `groupArray((a, b, c, groupArrayDistinct(reason)))` when `reason` is already an aggregate from an inner query.
 * Strip the redundant inner wrapper so the tuple lists column references only: `groupArray((a, b, c, reason))`.
 * Uses paren depth so commas inside `round(avg(...), 3)` do not break the scan.
 */
const stripNestedAggregatesInsideGroupArrayDoubleTuple = (sql) => {
    const head = /\bgroupArray\s*\(\s*\(/gi;
    let result = sql;
    let scanFrom = 0;
    let guard = 0;
    while (guard++ < 500) {
        head.lastIndex = scanFrom;
        const match = head.exec(result);
        if (!match) {
            break;
        }
        const innerExprStart = match.index + match[0].length;
        let depth = 1;
        let i = innerExprStart;
        for (; i < result.length; i++) {
            const ch = result[i];
            if (ch === '(') {
                depth++;
            }
            else if (ch === ')') {
                depth--;
                if (depth === 0) {
                    const inner = result.slice(innerExprStart, i);
                    const fixed = inner
                        .replace(/,\s*groupArrayDistinct\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)/gi, ', $1')
                        .replace(/,\s*groupUniqArray\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)/gi, ', $1')
                        .replace(/,\s*uniq\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)/gi, ', $1');
                    if (fixed === inner) {
                        scanFrom = match.index + match[0].length;
                    }
                    else {
                        result = result.slice(0, innerExprStart) + fixed + result.slice(i);
                        scanFrom = match.index;
                    }
                    break;
                }
            }
        }
        if (i >= result.length) {
            scanFrom = match.index + match[0].length;
        }
    }
    return result;
};
/**
 * multiIf(cond, (SELECT groupArray((...))), ['No failures detected']) mixes Array(Tuple(...)) with
 * Array(String) — ClickHouse: "no supertype for types Tuple..., String". Use a typed empty tuple array.
 */
const fixMultiIfElseStringArrayVsGroupArrayTuple = (sql) => {
    if (!/\bmultiif\b/i.test(sql)) {
        return sql;
    }
    if (!/\bgrouparray\s*\(\s*\(/i.test(sql)) {
        return sql;
    }
    let out = sql;
    out = out.replace(/\[\s*'No failures detected'\s*\]/gi, 'CAST([] AS Array(Tuple(String, UInt64, String, String)))');
    out = out.replace(/\[\s*"No failures detected"\s*\]/gi, 'CAST([] AS Array(Tuple(String, UInt64, String, String)))');
    out = out.replace(/\[\s*'No failure patterns? found'\s*\]/gi, 'CAST([] AS Array(Tuple(String, UInt64, String, String)))');
    return out;
};
const dropGroupByWithoutAggregates = (sql) => {
    const hasGroupBy = /\bGROUP BY\b/i.test(sql);
    const hasAggregateFn = /\b(SUM|AVG|COUNT|MIN|MAX|GROUP_CONCAT|ANY_VALUE)\s*\(/i.test(sql);
    if (hasGroupBy && !hasAggregateFn) {
        return sql.replace(/\s+GROUP BY[\s\S]*?(?=(ORDER BY|LIMIT|$))/i, ' ');
    }
    return sql;
};
const revertNumericTableFloat64OrZero = (sql) => {
    const touchesEvalOrOtel = /\bfrom\s+eval_results\b/i.test(sql) || /\bfrom\s+otel_traces\b/i.test(sql);
    if (!touchesEvalOrOtel) {
        return sql;
    }
    let out = sql.replace(/toFloat64OrZero\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)/g, 'toFloat64($1)');
    out = out.replace(/toFloat64OrZero\(\s*Sum\(([^)]+)\)\s*\)/gi, 'toFloat64(Sum($1))');
    return out;
};
const escapeRegexLiteral = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
/**
 * LLMs often filter eval_results.trace_id when the user pasted a test_run_id UUID.
 */
const fixMisscopedTraceIdAsTestRunId = (sql, scopedTestRunId) => {
    const id = typeof scopedTestRunId === 'string' ? scopedTestRunId.trim() : '';
    if (!id || !/\bfrom\s+eval_results\b/i.test(sql)) {
        return sql;
    }
    const esc = escapeRegexLiteral(id);
    return sql.replace(new RegExp(`(\\b(?:[a-zA-Z_][a-zA-Z0-9_]*\\.)?)trace_id\\s*=\\s*'${esc}'`, 'gi'), `$1test_run_id = '${id.replace(/'/g, "''")}'`);
};
/**
 * Applies the same hardening pipeline as before LLM SQL is sent to ClickHouse.
 * Order is significant; keep in sync with historical behavior.
 */
const postProcessGeneratedClickhouseSql = (sql, opts) => {
    const { activeApplicationIds, isEvalOnlyDomain, projectId, scopedTestRunId } = opts;
    let finalSql = fixReturnedRowsFloatCasts(sql);
    if (isEvalOnlyDomain && scopedTestRunId) {
        finalSql = fixMisscopedTraceIdAsTestRunId(finalSql, scopedTestRunId);
    }
    finalSql = normalizeApplicationAttributeFilter(finalSql);
    finalSql = fixCommaBeforeArrayJoin(finalSql);
    finalSql = fixCastCommaStringSyntax(finalSql);
    finalSql = wrapJsonExtractFirstArgCastStringForEvalJsonTypedColumns(finalSql);
    finalSql = fixEvalResultsArrayJoinSelectToClause(finalSql);
    finalSql = fixWrongComponentResultsJsonExtractsUseArrayJoinJ(finalSql);
    finalSql = fixGradingNotNullAllowsExecutionErrorsInWhere(finalSql);
    finalSql = fixArrayJoinSyntheticComponentWhenExecutionErrorAndGradingNull(finalSql);
    finalSql = stripGuardrailResultsHallucinatedStatus(finalSql);
    finalSql = stripHallucinatedEvalResultsEnvironmentColumns(finalSql);
    finalSql = disambiguateEvalResultsJsonReasonAlias(finalSql);
    finalSql = fixMisgroupedUnqualifiedFailureReasonGroupByAfterArrayJoinJ(finalSql);
    finalSql = fixArrayJoinJMetricStrategyPluginGroupByOrdinals(finalSql);
    finalSql = fixRowFailureReasonInGroupByAfterArrayJoinJ(finalSql);
    finalSql = fixNestedCountInsideGroupArrayTuple(finalSql);
    finalSql = fixRedundantEvalResultsSelectStarCommaJoin(finalSql);
    finalSql = fixFailCountAliasShadowingInEvalSummaryGroupArray(finalSql);
    finalSql = fixGroupArrayTupleCommonPluginMatchesInnerPluginIdRaw(finalSql);
    finalSql = fixOuterEvalRowAggregatesOverGroupedSubquery(finalSql);
    finalSql = stripNestedAggregatesInsideGroupArrayDoubleTuple(finalSql);
    finalSql = fixMultiIfElseStringArrayVsGroupArrayTuple(finalSql);
    finalSql = dropGroupByWithoutAggregates(finalSql);
    finalSql = revertNumericTableFloat64OrZero(finalSql);
    finalSql = rewriteGuardrailValidatorsUuidLookupToGuardrailResults(finalSql);
    finalSql = injectApplicationsProjectScope(finalSql, projectId, activeApplicationIds);
    finalSql = injectAppFilter(finalSql, activeApplicationIds);
    finalSql = removeRedundantAppFilter(finalSql);
    finalSql = fixNullJsonIssue(finalSql);
    if (!isEvalOnlyDomain) {
        finalSql = rewriteInvalidEvalResultsMediaFilter(finalSql);
    }
    return finalSql;
};
exports.postProcessGeneratedClickhouseSql = postProcessGeneratedClickhouseSql;
//# sourceMappingURL=clickhouseTextToSqlSqlFixes.js.map