"use strict";
/**
 * Utility functions for handling SQL parameter references
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateParameterConfiguration = exports.validateParameterNames = exports.getAvailableParametersFromTables = exports.getAvailableParameterNames = exports.validateParameterReferences = exports.getParameterReferencesFromSqlAndFormat = exports.getParameterReferences = exports.LightdashParameters = exports.parameterRegex = void 0;
const tslib_1 = require("tslib");
const ajv_1 = tslib_1.__importDefault(require("ajv"));
const ajv_errors_1 = tslib_1.__importDefault(require("ajv-errors"));
const better_ajv_errors_1 = tslib_1.__importDefault(require("better-ajv-errors"));
const lightdash_dbt_2_0_json_1 = tslib_1.__importDefault(require("../schemas/json/lightdash-dbt-2.0.json"));
const errors_1 = require("../types/errors");
// Regex for SQL parameter replacement - requires full ${} syntax
exports.parameterRegex = /\$\{(?:lightdash|ld)\.parameters\.(\w+(?:\.\w+)?)\}/g;
// Regex for extracting parameter references - works with format strings, ternary expressions,
// and Liquid template blocks like {% if ld.parameters.grain == "day" %}
const parameterReferencePattern = /(?:lightdash|ld)\.parameters\.(\w+(?:\.\w+)?)/g;
var LightdashParameters;
(function (LightdashParameters) {
    LightdashParameters["PREFIX"] = "lightdash.parameters";
    LightdashParameters["PREFIX_SHORT"] = "ld.parameters";
})(LightdashParameters || (exports.LightdashParameters = LightdashParameters = {}));
/**
 * Extracts parameter references from SQL strings or format strings
 * @param sql - The SQL or format string to extract parameter references from
 * @returns An array of unique parameter names referenced in the string
 */
const getParameterReferences = (sql, regex = exports.parameterRegex) => {
    const matches = sql.match(regex);
    if (!matches) {
        return [];
    }
    // Extract parameter names using the regex capture group and remove duplicates
    const parameterNames = matches.map((match) => match.replace(regex, '$1'));
    // Return unique parameter names
    return [...new Set(parameterNames)];
};
exports.getParameterReferences = getParameterReferences;
/**
 * Extracts and combines parameter references from both SQL and format strings
 * @param compiledSql - The compiled SQL to extract parameters from
 * @param format - Optional format string to extract parameters from
 * @returns An array of unique parameter names from both sources
 */
const getParameterReferencesFromSqlAndFormat = (compiledSql, format) => {
    const sqlParameterReferences = (0, exports.getParameterReferences)(compiledSql);
    // Also extract parameter references from Liquid template blocks
    // e.g., {% if ld.parameters.grain == "day" %}
    const liquidParameterReferences = compiledSql.includes('{%')
        ? (0, exports.getParameterReferences)(compiledSql, parameterReferencePattern)
        : [];
    const formatParameterReferences = format && typeof format === 'string'
        ? (0, exports.getParameterReferences)(format, parameterReferencePattern)
        : [];
    // Combine and deduplicate parameter references from all sources
    return Array.from(new Set([
        ...sqlParameterReferences,
        ...liquidParameterReferences,
        ...formatParameterReferences,
    ]));
};
exports.getParameterReferencesFromSqlAndFormat = getParameterReferencesFromSqlAndFormat;
const validateParameterReferences = (tableName, parameterReferences, availableParameters) => {
    const missingParameters = parameterReferences.filter((p) => !availableParameters.includes(p));
    if (missingParameters.length > 0) {
        // When a short-form reference (e.g. `attribution_source`) doesn't match
        // but a model-prefixed parameter does (e.g. `model.attribution_source`),
        // surface the correct full reference so the user knows what to write.
        const hints = missingParameters.reduce((acc, missing) => {
            if (missing.includes('.'))
                return acc;
            const matches = availableParameters.filter((p) => p.endsWith(`.${missing}`));
            if (matches.length === 0)
                return acc;
            const suggestions = matches
                .map((m) => `\${lightdash.parameters.${m}}`)
                .join(' or ');
            acc.push(`"${missing}" is a model-level parameter — use the model name prefix: ${suggestions}`);
            return acc;
        }, []);
        const hintMsg = hints.length > 0 ? ` Hint: ${hints.join('; ')}` : '';
        throw new errors_1.CompileError(`Failed to compile explore "${tableName}". Missing parameters: ${missingParameters.join(', ')}.${hintMsg}`, {});
    }
};
exports.validateParameterReferences = validateParameterReferences;
/**
 * Get all available parameter names for a project and explore
 * @param projectParameters - The project parameters
 * @param exploreParameters - The explore parameters
 * @returns An array of available parameter names
 */
const getAvailableParameterNames = (projectParameters, exploreParameters) => Object.keys(projectParameters || {}).concat(Object.keys(exploreParameters || {}));
exports.getAvailableParameterNames = getAvailableParameterNames;
/**
 * Get all available parameter names for a project and explore
 * @param exploreParameters - The explore parameters
 * @param includedTables - The included tables
 * @returns An array of available parameter names
 */
const getAvailableParametersFromTables = (includedTables) => includedTables.reduce((acc, table) => {
    const tableParameters = Object.keys(table.parameters || {}).reduce((acc2, p) => {
        const parameter = table.parameters?.[p];
        if (!parameter) {
            return acc2;
        }
        // Use the original table name for parameters if available, otherwise use the current name
        const tableName = table.originalName ?? table.name;
        const parameterKey = `${tableName}.${p}`;
        return {
            ...acc2,
            [parameterKey]: {
                ...parameter,
                type: parameter.type || 'string',
            },
        };
    }, {});
    return {
        ...acc,
        ...tableParameters,
    };
}, {});
exports.getAvailableParametersFromTables = getAvailableParametersFromTables;
/**
 * Validate parameter names
 * @param parameters - The parameters to validate
 * @returns True if any parameter name doesn't match the valid pattern, false otherwise
 */
const validateParameterNames = (parameters) => {
    const validNamePattern = /^[a-zA-Z0-9_-]+$/;
    const invalidParameters = Object.keys(parameters || {}).filter((paramName) => !validNamePattern.test(paramName));
    return {
        isInvalid: invalidParameters.length > 0,
        invalidParameters,
    };
};
exports.validateParameterNames = validateParameterNames;
// Lazy-initialized AJV validator to avoid module-level side effects
let cached = null;
const getParametersSchemaAndValidator = () => {
    if (!cached) {
        const parametersSchema = lightdash_dbt_2_0_json_1.default.$defs?.modelMeta?.properties?.parameters;
        if (!parametersSchema) {
            console.warn('Parameters schema not found in lightdash-dbt-2.0.json — expected $defs.modelMeta.properties.parameters. Skipping parameter config validation.');
            return undefined;
        }
        const ajv = new ajv_1.default({
            coerceTypes: true,
            allowUnionTypes: true,
            allErrors: true,
        });
        (0, ajv_errors_1.default)(ajv);
        cached = {
            schema: parametersSchema,
            validator: ajv.compile(parametersSchema),
        };
    }
    return cached;
};
/**
 * Validate parameter configuration using AJV against the JSON schema.
 */
const validateParameterConfiguration = (parameters) => {
    if (!parameters || Object.keys(parameters).length === 0) {
        return { isValid: true, error: null };
    }
    const schemaAndValidator = getParametersSchemaAndValidator();
    if (!schemaAndValidator) {
        return { isValid: true, error: null };
    }
    const { schema, validator } = schemaAndValidator;
    if (!validator(parameters)) {
        const error = (0, better_ajv_errors_1.default)(schema, parameters, validator.errors || [], { indent: 2 });
        return {
            isValid: false,
            error: error || 'Invalid parameter configuration',
        };
    }
    return { isValid: true, error: null };
};
exports.validateParameterConfiguration = validateParameterConfiguration;
//# sourceMappingURL=parameters.js.map