"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.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 substitution - requires the full ${...} syntax.
// Used by `replaceLightdashValues` to find substitution sites.
exports.parameterRegex = /\$\{(?:lightdash|ld)\.parameters\.(\w+(?:\.\w+)?)\}/g;
// Broader regex that also matches bare `ld.parameters.X` references — used to catch
// references inside Liquid template tags (e.g. `{% if ld.parameters.grain == "day" %}`),
// format strings, and ternary expressions. The lookbehind requires a non-word,
// non-dot boundary before `ld`/`lightdash` so identifiers like `myld.parameters.x`
// don't false-match.
const parameterReferencePattern = /(?<![\w.])(?:lightdash|ld)\.parameters\.(\w+(?:\.\w+)?)/g;
var LightdashParameters;
(function (LightdashParameters) {
    LightdashParameters["PREFIX"] = "lightdash.parameters";
    LightdashParameters["PREFIX_SHORT"] = "ld.parameters";
})(LightdashParameters || (exports.LightdashParameters = LightdashParameters = {}));
const matchAll = (input, regex) => {
    const matches = input.match(regex);
    if (!matches)
        return [];
    return matches.map((match) => match.replace(regex, '$1'));
};
/**
 * Single source of truth for extracting parameter references from a templated SQL or
 * format string. Detects both `${ld.parameters.X}` substitution sites and bare
 * `ld.parameters.X` references inside Liquid template blocks (only when `{%` is present,
 * to avoid false positives in plain SQL).
 *
 * @param sources - One or more strings to scan (e.g. compiled SQL, sql_from, format string).
 * @returns A deduplicated array of parameter names.
 */
const getParameterReferences = (...sources) => {
    const references = new Set();
    sources.forEach((source) => {
        if (!source || typeof source !== 'string')
            return;
        // Always match ${...} substitution sites.
        matchAll(source, exports.parameterRegex).forEach((name) => references.add(name));
        // If the source contains Liquid tags, also match bare references inside them.
        if (source.includes('{%')) {
            matchAll(source, parameterReferencePattern).forEach((name) => references.add(name));
        }
    });
    return [...references];
};
exports.getParameterReferences = getParameterReferences;
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