"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createDimensionWithGranularity = exports.ExploreCompiler = exports.parseAllReferences = exports.getAllReferences = exports.getParsedReference = exports.lightdashVariablePattern = void 0;
const errors_1 = require("../types/errors");
const explore_1 = require("../types/explore");
const field_1 = require("../types/field");
const timeFrames_1 = require("../types/timeFrames");
const timeFrames_2 = require("../utils/timeFrames");
const fieldSetExpander_1 = require("./fieldSetExpander");
const filtersCompiler_1 = require("./filtersCompiler");
const lightdashProjectConfig_1 = require("./lightdashProjectConfig");
const parameters_1 = require("./parameters");
// exclude lightdash prefix from variable pattern
exports.lightdashVariablePattern = /\$\{((?!(lightdash|ld)\.)[a-zA-Z0-9_.]+)\}/g;
const getParsedReference = (ref, currentTable) => {
    // Reference to another dimension
    const split = ref.split('.');
    if (split.length > 2) {
        throw new errors_1.CompileError(`Model "${currentTable}" cannot resolve dimension reference: \${${ref}}`, {});
    }
    const refTable = split.length === 1 ? currentTable : split[0];
    const refName = split.length === 1 ? split[0] : split[1];
    return { refTable, refName };
};
exports.getParsedReference = getParsedReference;
const getAllReferences = (raw) => (raw.match(exports.lightdashVariablePattern) || []).map((value) => value.slice(2, value.length - 1));
exports.getAllReferences = getAllReferences;
const parseAllReferences = (raw, currentTable) => (0, exports.getAllReferences)(raw).map((ref) => (0, exports.getParsedReference)(ref, currentTable));
exports.parseAllReferences = parseAllReferences;
const getReferencedTable = (refTable, tables) => {
    if (tables[refTable]) {
        return tables[refTable];
    }
    return Object.values(tables).find((table) => table.name === refTable || table.originalName === refTable);
};
class ExploreCompiler {
    constructor(warehouseClient, options = {}) {
        this.warehouseClient = warehouseClient;
        this.options = options;
    }
    compileExplore({ name, label, tags, baseTable, joinedTables, tables, targetDatabase, groupLabel, warehouse, ymlPath, sqlPath, spotlightConfig, meta, databricksCompute, aiHint, projectParameters, }) {
        // Check that base table exists (always required)
        if (!tables[baseTable]) {
            throw new errors_1.CompileError(`Failed to compile explore "${name}". Tried to find base table but cannot find table with name "${baseTable}"`, {});
        }
        // Collect warnings for partial compilation
        const exploreWarnings = [];
        // Filter joined tables - skip missing tables when partial compilation is enabled
        const validJoinedTables = this.options.allowPartialCompilation
            ? joinedTables.filter((join) => {
                if (tables[join.table] === undefined) {
                    exploreWarnings.push({
                        type: explore_1.InlineErrorType.MISSING_TABLE,
                        message: `Join to table "${join.table}" was skipped because the table does not exist`,
                    });
                    return false;
                }
                return true;
            })
            : joinedTables;
        // Validate all joined tables exist (only when partial compilation is disabled)
        if (!this.options.allowPartialCompilation) {
            joinedTables.forEach((join) => {
                if (!tables[join.table]) {
                    throw new errors_1.CompileError(`Failed to compile explore "${name}". Tried to join table "${join.table}" to "${baseTable}" but cannot find table with name "${join.table}"`, {});
                }
            });
        }
        const aliases = [
            baseTable,
            ...validJoinedTables.map((join) => join.alias || join.table),
        ];
        if (aliases.length !== new Set(aliases).size) {
            throw new errors_1.CompileError(`Failed to compile explore "${name}". Cannot join to the same table multiple times table in an explore. Use an 'alias'`, {});
        }
        const { isInvalid: hasInvalidParameterNames, invalidParameters } = (0, parameters_1.validateParameterNames)(meta.parameters);
        if (hasInvalidParameterNames) {
            throw new errors_1.CompileError(`Failed to compile explore "${name}". Invalid parameter names: ${invalidParameters.join(', ')}`, {
                invalidParameters,
            });
        }
        const includedTables = validJoinedTables.reduce((prev, join) => {
            const joinTableName = join.alias || tables[join.table].name;
            const joinTableLabel = join.label ||
                (join.alias && (0, field_1.friendlyName)(join.alias)) ||
                tables[join.table].label;
            const joinDescription = join.description !== undefined
                ? join.description
                : tables[join.table].description;
            // Expand field sets if join.fields contains set references
            let expandedFields;
            if (join.fields) {
                expandedFields = (0, fieldSetExpander_1.expandFieldsWithSets)(join.fields, tables[join.table]);
            }
            const requiredDimensionsForJoin = (0, exports.parseAllReferences)(join.sqlOn, join.table).reduce((acc, reference) => {
                if (reference.refTable === joinTableName) {
                    acc.push(reference.refName);
                }
                return acc;
            }, []);
            const tableDimensions = tables[join.table].dimensions;
            return {
                ...prev,
                [join.alias || join.table]: {
                    ...tables[join.table],
                    originalName: tables[join.table].name,
                    name: joinTableName,
                    label: joinTableLabel,
                    ...(joinDescription !== undefined && {
                        description: joinDescription,
                    }),
                    hidden: join.hidden,
                    dimensions: Object.keys(tableDimensions).reduce((acc, dimensionKey) => {
                        const dimension = tableDimensions[dimensionKey];
                        const isRequired = requiredDimensionsForJoin.includes(dimensionKey);
                        const isTimeIntervalBaseDimensionVisible = dimension.timeInterval &&
                            dimension.timeIntervalBaseDimensionName &&
                            expandedFields
                            ? expandedFields.includes(dimension.timeIntervalBaseDimensionName)
                            : false;
                        const isVisible = expandedFields === undefined ||
                            expandedFields.includes(dimensionKey) ||
                            (dimension.group !== undefined &&
                                expandedFields.includes(dimension.group)) ||
                            isTimeIntervalBaseDimensionVisible;
                        if (isRequired || isVisible) {
                            acc[dimensionKey] = {
                                ...dimension,
                                hidden: join.hidden ||
                                    dimension.hidden ||
                                    !isVisible,
                                table: joinTableName,
                                tableLabel: joinTableLabel,
                            };
                        }
                        return acc;
                    }, {}),
                    metrics: Object.keys(tables[join.table].metrics)
                        .filter((d) => expandedFields === undefined ||
                        expandedFields.includes(d))
                        .reduce((prevMetrics, metricKey) => {
                        const metric = tables[join.table].metrics[metricKey];
                        return {
                            ...prevMetrics,
                            [metricKey]: {
                                ...metric,
                                hidden: !!join.hidden || metric.hidden,
                                table: joinTableName,
                                tableLabel: joinTableLabel,
                            },
                        };
                    }, {}),
                },
            };
        }, { [baseTable]: tables[baseTable] });
        // get all available parameters from the included tables
        const exploreAvailableParameters = (0, parameters_1.getAvailableParametersFromTables)(Object.values(includedTables));
        // available parameters are the combination of project parameters and model parameters
        // used for validating parameter references
        const availableParametersNames = (0, parameters_1.getAvailableParameterNames)(projectParameters, exploreAvailableParameters);
        // Compile joins first to determine which ones succeed
        // Failed joins will be skipped along with their tables when partial compilation is enabled
        const joinResults = validJoinedTables.map((j) => {
            const tableName = j.alias || j.table;
            if (this.options.allowPartialCompilation) {
                try {
                    return {
                        join: j,
                        compiled: this.compileJoin(j, includedTables, availableParametersNames),
                        tableName,
                    };
                }
                catch (e) {
                    // Join failed to compile - skip it and add error
                    const errorMessage = e instanceof Error
                        ? e.message
                        : `Failed to compile join to "${j.table}"`;
                    exploreWarnings.push({
                        type: explore_1.InlineErrorType.SKIPPED_JOIN,
                        message: errorMessage,
                    });
                    return { join: j, compiled: null, tableName };
                }
            }
            return {
                join: j,
                compiled: this.compileJoin(j, includedTables, availableParametersNames),
                tableName,
            };
        });
        // Filter to only successful joins
        const compiledJoins = joinResults
            .filter((r) => r.compiled !== null)
            .map((r) => r.compiled);
        // Get table names that should be included (base table + successful joins)
        const successfulTableNames = new Set([
            baseTable,
            ...joinResults
                .filter((r) => r.compiled !== null)
                .map((r) => r.tableName),
        ]);
        const compiledTables = aliases
            .filter((tableName) => successfulTableNames.has(tableName))
            .reduce((prev, tableName) => ({
            ...prev,
            [tableName]: this.compileTable(includedTables[tableName], includedTables, availableParametersNames),
        }), {});
        // Collect field-level compilation errors
        if (this.options.allowPartialCompilation) {
            Object.entries(compiledTables).forEach(([, table]) => {
                Object.entries(table.dimensions).forEach(([, dimension]) => {
                    if (dimension.compilationError) {
                        exploreWarnings.push({
                            type: explore_1.InlineErrorType.FIELD_ERROR,
                            message: dimension.compilationError.message,
                        });
                    }
                });
                Object.entries(table.metrics).forEach(([, metric]) => {
                    if (metric.compilationError) {
                        exploreWarnings.push({
                            type: explore_1.InlineErrorType.FIELD_ERROR,
                            message: metric.compilationError.message,
                        });
                    }
                });
            });
        }
        const spotlightVisibility = meta.spotlight?.visibility ?? spotlightConfig?.default_visibility;
        const spotlightCategories = (0, lightdashProjectConfig_1.getCategoriesFromResource)('explore', name, spotlightConfig, meta.spotlight?.categories);
        return {
            name,
            label,
            tags,
            baseTable,
            joinedTables: compiledJoins,
            tables: compiledTables,
            targetDatabase,
            groupLabel,
            warehouse,
            ymlPath,
            sqlPath,
            databricksCompute,
            ...(aiHint ? { aiHint } : {}),
            ...(0, lightdashProjectConfig_1.getSpotlightConfigurationForResource)({
                visibility: spotlightVisibility,
                categories: spotlightCategories,
                owner: meta.spotlight?.owner,
            }),
            ...(meta.parameters && Object.keys(meta.parameters).length > 0
                ? { parameters: meta.parameters }
                : {}),
            ...(exploreWarnings.length > 0
                ? { warnings: exploreWarnings }
                : {}),
        };
    }
    /**
     * Creates a dimension with a compilation error marker.
     * Used when partial compilation is enabled and a dimension fails to compile.
     */
    static createDimensionWithError(dimension, error) {
        return {
            ...dimension,
            compiledSql: 'NULL', // Placeholder SQL that won't cause query failures
            tablesReferences: undefined,
            compilationError: error,
        };
    }
    /**
     * Creates a metric with a compilation error marker.
     * Used when partial compilation is enabled and a metric fails to compile.
     */
    static createMetricWithError(metric, error) {
        return {
            ...metric,
            compiledSql: 'NULL', // Placeholder SQL that won't cause query failures
            tablesReferences: undefined,
            compilationError: error,
        };
    }
    compileTable(table, tables, availableParameters) {
        const dimensions = Object.keys(table.dimensions).reduce((prev, dimensionKey) => {
            const dimension = table.dimensions[dimensionKey];
            if (this.options.allowPartialCompilation) {
                try {
                    return {
                        ...prev,
                        [dimensionKey]: this.compileDimension(dimension, tables, availableParameters),
                    };
                }
                catch (e) {
                    const errorMessage = e instanceof Error
                        ? e.message
                        : `Failed to compile dimension "${dimensionKey}"`;
                    return {
                        ...prev,
                        [dimensionKey]: ExploreCompiler.createDimensionWithError(dimension, { message: errorMessage }),
                    };
                }
            }
            return {
                ...prev,
                [dimensionKey]: this.compileDimension(dimension, tables, availableParameters),
            };
        }, {});
        const metrics = Object.keys(table.metrics).reduce((prev, metricKey) => {
            const metric = table.metrics[metricKey];
            if (this.options.allowPartialCompilation) {
                try {
                    return {
                        ...prev,
                        [metricKey]: this.compileMetric(metric, tables, availableParameters),
                    };
                }
                catch (e) {
                    const errorMessage = e instanceof Error
                        ? e.message
                        : `Failed to compile metric "${metricKey}"`;
                    return {
                        ...prev,
                        [metricKey]: ExploreCompiler.createMetricWithError(metric, { message: errorMessage }),
                    };
                }
            }
            return {
                ...prev,
                [metricKey]: this.compileMetric(metric, tables, availableParameters),
            };
        }, {});
        const compiledSqlWhere = table.sqlWhere?.replace(exports.lightdashVariablePattern, (_, p1) => {
            const compiledReference = this.compileDimensionReference(p1, tables, table.name, { fieldType: 'sql_where', fieldName: table.name });
            return compiledReference.sql;
        });
        // Extract parameter references from sqlWhere
        const parameterReferences = compiledSqlWhere
            ? (0, parameters_1.getParameterReferences)(compiledSqlWhere)
            : [];
        (0, parameters_1.validateParameterReferences)(table.name, parameterReferences, availableParameters);
        return {
            ...table,
            uncompiledSqlWhere: table.sqlWhere,
            sqlWhere: compiledSqlWhere,
            dimensions,
            metrics,
            ...(parameterReferences.length > 0 ? { parameterReferences } : {}),
            ...(table.parameters && Object.keys(table.parameters).length > 0
                ? { parameters: table.parameters }
                : {}),
        };
    }
    static expandShowUnderlyingValueSets(metric, tables) {
        if (!Array.isArray(metric.showUnderlyingValues)) {
            return undefined;
        }
        const currentTable = tables[metric.table];
        const containsSetFields = metric.showUnderlyingValues.some((field) => field.endsWith('*'));
        if (!containsSetFields || !currentTable) {
            return metric.showUnderlyingValues;
        }
        const expandedValues = (0, fieldSetExpander_1.expandFieldsWithSets)([...metric.showUnderlyingValues], currentTable);
        expandedValues.forEach((fieldRef) => {
            const { refTable, refName } = (0, exports.getParsedReference)(fieldRef, metric.table);
            const referencedTable = getReferencedTable(refTable, tables);
            const isValidReference = !!(referencedTable?.dimensions[refName] ||
                referencedTable?.metrics[refName]);
            if (!isValidReference) {
                throw new errors_1.CompileError(`"show_underlying_values" for metric "${metric.name}" has a reference to an unknown field: ${fieldRef} in table "${metric.table}"`);
            }
        });
        return expandedValues;
    }
    compileMetric(metric, tables, availableParameters) {
        const compiledMetric = this.compileMetricSql(metric, tables, availableParameters);
        const showUnderlyingValues = ExploreCompiler.expandShowUnderlyingValueSets(metric, tables);
        const tablesRequiredAttributes = Array.from(compiledMetric.tablesReferences).reduce((acc, tableReference) => {
            const table = tables[tableReference];
            if (table?.requiredAttributes) {
                acc[tableReference] = table.requiredAttributes;
            }
            return acc;
        }, {});
        const compiledSql = compiledMetric.sql;
        // Extract parameter references from both SQL and format string
        const parameterReferences = (0, parameters_1.getParameterReferencesFromSqlAndFormat)(compiledSql, typeof metric.format === 'string' ? metric.format : undefined);
        (0, parameters_1.validateParameterReferences)(metric.table, parameterReferences, availableParameters);
        return {
            ...metric,
            compiledSql,
            showUnderlyingValues,
            tablesReferences: Array.from(compiledMetric.tablesReferences),
            ...(Object.keys(tablesRequiredAttributes).length
                ? { tablesRequiredAttributes }
                : {}),
            ...(parameterReferences.length > 0 ? { parameterReferences } : {}),
        };
    }
    compileMetricSql(metric, tables, availableParameters) {
        // Metric might have references to other dimensions
        if (!tables[metric.table]) {
            throw new errors_1.CompileError(`Metric "${metric.name}" references a table "${metric.table}" which matches no model`, {});
        }
        const currentRef = `${metric.table}.${metric.name}`;
        const currentShortRef = metric.name;
        let tablesReferences = new Set([metric.table]);
        if (metric.sql === undefined || metric.sql === null) {
            throw new errors_1.CompileError(`Metric "${metric.name}" in table "${metric.table}" is missing a sql definition`, {});
        }
        let renderedSql = metric.sql.replace(exports.lightdashVariablePattern, (_, p1) => {
            if ([currentShortRef, currentRef].includes(p1)) {
                throw new errors_1.CompileError(`Metric "${metric.name}" in table "${metric.table}" has a sql string referencing itself: "${metric.sql}"`, {});
            }
            const fieldContext = {
                fieldType: 'metric',
                fieldName: metric.name,
            };
            const compiledReference = (0, field_1.isNonAggregateMetric)(metric) ||
                (0, field_1.isPostCalculationMetric)(metric)
                ? this.compileMetricReference(p1, tables, metric.table, availableParameters, fieldContext)
                : this.compileDimensionReference(p1, tables, metric.table, fieldContext);
            tablesReferences = new Set([
                ...tablesReferences,
                ...compiledReference.tablesReferences,
            ]);
            return compiledReference.sql;
        });
        if (metric.filters !== undefined && metric.filters.length > 0) {
            if ((0, field_1.isNonAggregateMetric)(metric) ||
                (0, field_1.isPostCalculationMetric)(metric)) {
                throw new errors_1.CompileError(`Error: ${metric.name} - metric filters cannot be used with non-aggregate or post-calculation metrics`);
            }
            const conditions = metric.filters.map((filter) => {
                const fieldRef = 
                // @ts-expect-error This fallback is to support old metric filters in yml. We can delete this after a few months since we can assume all projects have been redeployed
                filter.target.fieldRef || filter.target.fieldId;
                const { refTable, refName } = (0, exports.getParsedReference)(fieldRef, metric.table);
                const table = tables[refTable];
                if (!table) {
                    throw new errors_1.CompileError(`Filter for metric "${metric.name}" has a reference to an unknown table`);
                }
                // NOTE: date dimensions from explores have their time format uppercased (e.g. order_date_DAY) - see ticket: https://github.com/lightdash/lightdash/issues/5998
                const dimensionRefName = Object.keys(table.dimensions).find((key) => key.toLowerCase() === refName.toLowerCase());
                const dimensionField = dimensionRefName
                    ? table.dimensions[dimensionRefName]
                    : undefined;
                if (!dimensionField) {
                    throw new errors_1.CompileError(`Filter for metric "${metric.name}" has a reference to an unknown dimension: ${fieldRef}`);
                }
                const compiledDimension = this.compileDimension(dimensionField, tables, availableParameters);
                if (compiledDimension.tablesReferences) {
                    tablesReferences = new Set([
                        ...tablesReferences,
                        ...compiledDimension.tablesReferences,
                    ]);
                }
                return (0, filtersCompiler_1.renderFilterRuleSqlFromField)(filter, compiledDimension, this.warehouseClient.getFieldQuoteChar(), this.warehouseClient.getStringQuoteChar(), this.warehouseClient.escapeString.bind(this.warehouseClient), this.warehouseClient.getStartOfWeek(), this.warehouseClient.getAdapterType());
            });
            renderedSql = `CASE WHEN (${conditions.join(' AND ')}) THEN (${renderedSql}) ELSE NULL END`;
        }
        const compiledSql = this.warehouseClient.getMetricSql(renderedSql, metric);
        return { sql: compiledSql, tablesReferences };
    }
    compileDimension(dimension, tables, availableParameters) {
        const compiledDimension = this.compileDimensionSql(dimension, tables);
        const tablesRequiredAttributes = Array.from(compiledDimension.tablesReferences).reduce((acc, tableReference) => {
            const table = tables[tableReference];
            if (table?.requiredAttributes) {
                acc[tableReference] = table.requiredAttributes;
            }
            return acc;
        }, {});
        const compiledSql = compiledDimension.sql;
        // Extract parameter references from both SQL and format string
        const parameterReferences = (0, parameters_1.getParameterReferencesFromSqlAndFormat)(compiledSql, typeof dimension.format === 'string' ? dimension.format : undefined);
        (0, parameters_1.validateParameterReferences)(dimension.table, parameterReferences, availableParameters);
        return {
            ...dimension,
            compiledSql,
            tablesReferences: Array.from(compiledDimension.tablesReferences),
            ...(Object.keys(tablesRequiredAttributes).length
                ? { tablesRequiredAttributes }
                : {}),
            ...(parameterReferences.length > 0 ? { parameterReferences } : {}),
        };
    }
    compileDimensionSql(dimension, tables) {
        // Dimension might have references to other dimensions
        // Check we don't reference yourself
        const currentRef = `${dimension.table}.${dimension.name}`;
        const currentShortRef = dimension.name;
        let tablesReferences = new Set([dimension.table]);
        const sql = dimension.sql.replace(exports.lightdashVariablePattern, (_, p1) => {
            if ([currentShortRef, currentRef].includes(p1)) {
                throw new errors_1.CompileError(`Dimension "${dimension.name}" in table "${dimension.table}" has a sql string referencing itself: "${dimension.sql}"`, {});
            }
            // eslint-disable-next-line @typescript-eslint/no-use-before-define
            const compiledReference = this.compileDimensionReference(p1, tables, dimension.table, { fieldType: 'dimension', fieldName: dimension.name });
            tablesReferences = new Set([
                ...tablesReferences,
                ...compiledReference.tablesReferences,
            ]);
            return compiledReference.sql;
        });
        return { sql, tablesReferences };
    }
    compileCustomDimensionSql(dimension, tables, availableParameters) {
        const currentRef = dimension.id;
        let tablesReferences = new Set([]);
        const compiledSql = dimension.sql.replace(exports.lightdashVariablePattern, (_, p1) => {
            if (currentRef === p1) {
                throw new errors_1.CompileError(`Dimension "${dimension.name}" in table "${dimension.table}" has a sql string referencing itself: "${dimension.sql}"`, {});
            }
            const compiledReference = this.compileDimensionReference(p1, tables, dimension.table, {
                fieldType: 'custom_dimension',
                fieldName: dimension.name,
            });
            tablesReferences = new Set([
                ...tablesReferences,
                ...compiledReference.tablesReferences,
            ]);
            return compiledReference.sql;
        });
        // Extract parameter references from custom dimension sql
        const parameterReferences = (0, parameters_1.getParameterReferences)(compiledSql);
        (0, parameters_1.validateParameterReferences)(dimension.table, parameterReferences, availableParameters);
        return {
            compiledSql,
            tablesReferences: Array.from(tablesReferences),
            ...(parameterReferences.length > 0 ? { parameterReferences } : {}),
        };
    }
    compileCustomDimension(customDimension, tables, availableParameters) {
        if ((0, field_1.isCustomBinDimension)(customDimension)) {
            return customDimension;
        }
        const compiledCustomDimensionSql = this.compileCustomDimensionSql(customDimension, tables, availableParameters);
        return {
            ...customDimension,
            ...compiledCustomDimensionSql,
        };
    }
    compileDimensionReference(ref, tables, currentTable, fieldContext) {
        // Reference to current table
        if (ref === 'TABLE') {
            const fieldQuoteChar = this.warehouseClient.getFieldQuoteChar();
            return {
                sql: `${fieldQuoteChar}${currentTable}${fieldQuoteChar}`,
                tablesReferences: new Set([currentTable]),
            };
        }
        const { refTable, refName } = (0, exports.getParsedReference)(ref, currentTable);
        /** Resolve the table reference through its original name, or via an alias: */
        const referencedTable = getReferencedTable(refTable, tables);
        const referencedDimension = referencedTable?.dimensions[refName];
        if (referencedDimension === undefined) {
            const fieldInfo = fieldContext
                ? ` in ${fieldContext.fieldType} "${fieldContext.fieldName}"`
                : '';
            throw new errors_1.CompileError(`Model "${currentTable}"${fieldInfo} has a dimension reference: \${${ref}} which matches no dimension`, {});
        }
        const compiledDimension = this.compileDimensionSql(referencedDimension, tables);
        return {
            sql: `(${compiledDimension.sql})`,
            tablesReferences: new Set([
                referencedTable?.name || refTable,
                ...compiledDimension.tablesReferences,
            ]),
        };
    }
    compileMetricReference(ref, tables, currentTable, availableParameters, fieldContext) {
        // Reference to current table
        if (ref === 'TABLE') {
            const fieldQuoteChar = this.warehouseClient.getFieldQuoteChar();
            return {
                sql: `${fieldQuoteChar}${currentTable}${fieldQuoteChar}`,
                tablesReferences: new Set([currentTable]),
            };
        }
        const { refTable: refTableName, refName } = (0, exports.getParsedReference)(ref, currentTable);
        const referencedTable = getReferencedTable(refTableName, tables);
        const referencedMetric = referencedTable?.metrics[refName];
        if (referencedMetric === undefined) {
            const fieldInfo = fieldContext
                ? ` in ${fieldContext.fieldType} "${fieldContext.fieldName}"`
                : '';
            throw new errors_1.CompileError(`Model "${currentTable}"${fieldInfo} has a metric reference: \${${ref}} which matches no metric`, {});
        }
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        const compiledMetric = this.compileMetricSql(referencedMetric, tables, availableParameters);
        return {
            sql: `(${compiledMetric.sql})`,
            tablesReferences: new Set([
                referencedTable?.name || refTableName,
                ...compiledMetric.tablesReferences,
            ]),
        };
    }
    compileExploreJoinSql(join, tables) {
        const tablesReferences = new Set([]);
        // Sql join contains references to dimensions
        const sql = join.sqlOn.replace(exports.lightdashVariablePattern, (_, p1) => {
            const compiledReference = this.compileDimensionReference(p1, tables, join.table, { fieldType: 'join', fieldName: join.table });
            // Update table references
            compiledReference.tablesReferences.forEach((value) => tablesReferences.add(value));
            return compiledReference.sql;
        });
        return { sql, tablesReferences };
    }
    compileJoin(join, tables, availableParameters) {
        const { sql, tablesReferences } = this.compileExploreJoinSql({
            table: join.alias || join.table,
            sqlOn: join.sqlOn,
        }, tables);
        // Extract parameter references from sqlOn
        const parameterReferences = (0, parameters_1.getParameterReferences)(sql);
        (0, parameters_1.validateParameterReferences)(join.table, parameterReferences, availableParameters);
        return {
            table: join.alias || join.table,
            sqlOn: join.sqlOn,
            type: join.type,
            compiledSqlOn: sql,
            tablesReferences: Array.from(tablesReferences),
            hidden: join.hidden,
            always: join.always,
            relationship: join.relationship,
            ...(parameterReferences.length > 0 ? { parameterReferences } : {}),
        };
    }
}
exports.ExploreCompiler = ExploreCompiler;
const createDimensionWithGranularity = (dimensionName, baseTimeDimension, explore, warehouseSqlBuilder, granularity, availableParameters) => {
    const newTimeInterval = timeFrames_1.dateGranularityToTimeFrameMap[granularity];
    const exploreCompiler = new ExploreCompiler(warehouseSqlBuilder);
    return exploreCompiler.compileDimension({
        ...baseTimeDimension,
        name: dimensionName,
        type: timeFrames_2.timeFrameConfigs[newTimeInterval].getDimensionType(baseTimeDimension.type),
        timeInterval: newTimeInterval,
        label: `${baseTimeDimension.label} ${timeFrames_2.timeFrameConfigs[newTimeInterval]
            .getLabel()
            .toLowerCase()}`,
        sql: timeFrames_2.timeFrameConfigs[newTimeInterval].getSql(warehouseSqlBuilder.getAdapterType(), newTimeInterval, baseTimeDimension.sql, timeFrames_2.timeFrameConfigs[newTimeInterval].getDimensionType(baseTimeDimension.type), warehouseSqlBuilder.getStartOfWeek()),
    }, explore.tables, availableParameters);
};
exports.createDimensionWithGranularity = createDimensionWithGranularity;
//# sourceMappingURL=exploreCompiler.js.map