import words from 'lodash/words';
import { CompileError } from './errors';
export var Compact;
(function (Compact) {
    Compact["AUTO"] = "auto";
    Compact["THOUSANDS"] = "thousands";
    Compact["MILLIONS"] = "millions";
    Compact["BILLIONS"] = "billions";
    Compact["TRILLIONS"] = "trillions";
    Compact["KILOBYTES"] = "kilobytes";
    Compact["MEGABYTES"] = "megabytes";
    Compact["GIGABYTES"] = "gigabytes";
    Compact["TERABYTES"] = "terabytes";
    Compact["PETABYTES"] = "petabytes";
    Compact["KIBIBYTES"] = "kibibytes";
    Compact["MEBIBYTES"] = "mebibytes";
    Compact["GIBIBYTES"] = "gibibytes";
    Compact["TEBIBYTES"] = "tebibytes";
    Compact["PEBIBYTES"] = "pebibytes";
})(Compact || (Compact = {}));
const CompactAlias = [
    'K',
    'thousand',
    'M',
    'million',
    'B',
    'billion',
    'T',
    'trillion',
    'KB',
    'kilobyte',
    'MB',
    'megabyte',
    'GB',
    'gigabyte',
    'TB',
    'terabyte',
    'PB',
    'petabyte',
    'KiB',
    'kibibyte',
    'MiB',
    'mebibyte',
    'GiB',
    'gibibyte',
    'TiB',
    'tebibyte',
    'PiB',
    'pebibyte',
];
export var NumberSeparator;
(function (NumberSeparator) {
    NumberSeparator["DEFAULT"] = "default";
    NumberSeparator["COMMA_PERIOD"] = "commaPeriod";
    NumberSeparator["SPACE_PERIOD"] = "spacePeriod";
    NumberSeparator["PERIOD_COMMA"] = "periodComma";
    NumberSeparator["NO_SEPARATOR_PERIOD"] = "noSeparatorPeriod";
    NumberSeparator["APOSTROPHE_PERIOD"] = "apostrophePeriod";
})(NumberSeparator || (NumberSeparator = {}));
export const CompactConfigMap = {
    [Compact.AUTO]: {
        compact: Compact.AUTO,
        alias: [],
        orderOfMagnitude: 0,
        convertFn: (value) => value,
        label: 'Auto (K, M, B, T)',
        suffix: '',
    },
    [Compact.THOUSANDS]: {
        compact: Compact.THOUSANDS,
        alias: ['K', 'thousand'],
        orderOfMagnitude: 3,
        convertFn: (value) => value / 1000,
        label: 'thousands (K)',
        suffix: 'K',
    },
    [Compact.MILLIONS]: {
        compact: Compact.MILLIONS,
        alias: ['M', 'million'],
        orderOfMagnitude: 6,
        convertFn: (value) => value / 1000000,
        label: 'millions (M)',
        suffix: 'M',
    },
    [Compact.BILLIONS]: {
        compact: Compact.BILLIONS,
        alias: ['B', 'billion'],
        orderOfMagnitude: 9,
        convertFn: (value) => value / 1000000000,
        label: 'billions (B)',
        suffix: 'B',
    },
    [Compact.TRILLIONS]: {
        compact: Compact.TRILLIONS,
        alias: ['T', 'trillion'],
        orderOfMagnitude: 12,
        convertFn: (value) => value / 1000000000000,
        label: 'trillions (T)',
        suffix: 'T',
    },
    [Compact.KILOBYTES]: {
        compact: Compact.KILOBYTES,
        alias: ['KB', 'kilobyte'],
        orderOfMagnitude: 3,
        convertFn: (value) => value / 1000,
        label: 'kilobytes (KB)',
        suffix: 'KB',
    },
    [Compact.MEGABYTES]: {
        compact: Compact.MEGABYTES,
        alias: ['MB', 'megabyte'],
        orderOfMagnitude: 6,
        convertFn: (value) => value / 1000000,
        label: 'megabytes (MB)',
        suffix: 'MB',
    },
    [Compact.GIGABYTES]: {
        compact: Compact.GIGABYTES,
        alias: ['GB', 'gigabyte'],
        orderOfMagnitude: 9,
        convertFn: (value) => value / 1000000000,
        label: 'gigabytes (GB)',
        suffix: 'GB',
    },
    [Compact.TERABYTES]: {
        compact: Compact.TERABYTES,
        alias: ['TB', 'terabyte'],
        orderOfMagnitude: 12,
        convertFn: (value) => value / 1000000000000,
        label: 'terabytes (TB)',
        suffix: 'TB',
    },
    [Compact.PETABYTES]: {
        compact: Compact.PETABYTES,
        alias: ['PB', 'petabyte'],
        orderOfMagnitude: 15,
        convertFn: (value) => value / 1000000000000000,
        label: 'petabytes (PB)',
        suffix: 'PB',
    },
    [Compact.KIBIBYTES]: {
        compact: Compact.KIBIBYTES,
        alias: ['KiB', 'kibibyte'],
        orderOfMagnitude: -1,
        convertFn: (value) => value / 1024,
        label: 'kibibytes (KiB)',
        suffix: 'KiB',
    },
    [Compact.MEBIBYTES]: {
        compact: Compact.MEBIBYTES,
        alias: ['MiB', 'mebibyte'],
        orderOfMagnitude: -1,
        convertFn: (value) => value / 1048576,
        label: 'mebibytes (MiB)',
        suffix: 'MiB',
    },
    [Compact.GIBIBYTES]: {
        compact: Compact.GIBIBYTES,
        alias: ['GiB', 'gibibyte'],
        orderOfMagnitude: -1,
        convertFn: (value) => value / 1073741824,
        label: 'gibibytes (GiB)',
        suffix: 'GiB',
    },
    [Compact.TEBIBYTES]: {
        compact: Compact.TEBIBYTES,
        alias: ['TiB', 'tebibyte'],
        orderOfMagnitude: -1,
        convertFn: (value) => value / 1099511627776,
        label: 'tebibytes (TiB)',
        suffix: 'TiB',
    },
    [Compact.PEBIBYTES]: {
        compact: Compact.PEBIBYTES,
        alias: ['PiB', 'pebibyte'],
        orderOfMagnitude: -1,
        convertFn: (value) => value / 1125899906842624,
        label: 'pebibytes (PiB)',
        suffix: 'PiB',
    },
};
export function findCompactConfig(compactOrAlias) {
    return Object.values(CompactConfigMap).find(({ compact, alias }) => compact === compactOrAlias ||
        alias.includes(compactOrAlias));
}
export var BinType;
(function (BinType) {
    BinType["FIXED_NUMBER"] = "fixed_number";
    BinType["FIXED_WIDTH"] = "fixed_width";
    BinType["CUSTOM_RANGE"] = "custom_range";
    BinType["CUSTOM_GROUP"] = "custom_group";
})(BinType || (BinType = {}));
export var GroupValueMatchType;
(function (GroupValueMatchType) {
    GroupValueMatchType["EXACT"] = "exact";
    GroupValueMatchType["STARTS_WITH"] = "startsWith";
    GroupValueMatchType["ENDS_WITH"] = "endsWith";
    GroupValueMatchType["INCLUDES"] = "includes";
})(GroupValueMatchType || (GroupValueMatchType = {}));
export var CustomDimensionType;
(function (CustomDimensionType) {
    CustomDimensionType["BIN"] = "bin";
    CustomDimensionType["SQL"] = "sql";
})(CustomDimensionType || (CustomDimensionType = {}));
export const isCustomDimension = (value) => value !== undefined &&
    Object.values(CustomDimensionType).includes(value.type);
export const isCustomBinDimension = (value) => value !== undefined &&
    isCustomDimension(value) &&
    value.type === CustomDimensionType.BIN;
export const isCustomSqlDimension = (value) => value !== undefined &&
    isCustomDimension(value) &&
    value.type === CustomDimensionType.SQL;
export const isCompiledCustomSqlDimension = (value) => isCustomSqlDimension(value) && 'compiledSql' in value;
export var CustomFormatType;
(function (CustomFormatType) {
    CustomFormatType["DEFAULT"] = "default";
    CustomFormatType["PERCENT"] = "percent";
    CustomFormatType["CURRENCY"] = "currency";
    CustomFormatType["NUMBER"] = "number";
    CustomFormatType["ID"] = "id";
    CustomFormatType["DATE"] = "date";
    CustomFormatType["TIMESTAMP"] = "timestamp";
    CustomFormatType["BYTES_SI"] = "bytes_si";
    CustomFormatType["BYTES_IEC"] = "bytes_iec";
    CustomFormatType["CUSTOM"] = "custom";
})(CustomFormatType || (CustomFormatType = {}));
export var TableCalculationType;
(function (TableCalculationType) {
    TableCalculationType["NUMBER"] = "number";
    TableCalculationType["STRING"] = "string";
    TableCalculationType["DATE"] = "date";
    TableCalculationType["TIMESTAMP"] = "timestamp";
    TableCalculationType["BOOLEAN"] = "boolean";
})(TableCalculationType || (TableCalculationType = {}));
export var WindowFunctionType;
(function (WindowFunctionType) {
    WindowFunctionType["ROW_NUMBER"] = "row_number";
    WindowFunctionType["PERCENT_RANK"] = "percent_rank";
    WindowFunctionType["CUME_DIST"] = "cume_dist";
    WindowFunctionType["RANK"] = "rank";
    WindowFunctionType["SUM"] = "sum";
    WindowFunctionType["AVG"] = "avg";
    WindowFunctionType["COUNT"] = "count";
    WindowFunctionType["MIN"] = "min";
    WindowFunctionType["MAX"] = "max";
})(WindowFunctionType || (WindowFunctionType = {}));
export const nullaryWindowFunctions = [
    WindowFunctionType.ROW_NUMBER,
    WindowFunctionType.PERCENT_RANK,
    WindowFunctionType.CUME_DIST,
    WindowFunctionType.RANK,
];
export const unaryWindowFunctions = [
    WindowFunctionType.SUM,
    WindowFunctionType.AVG,
    WindowFunctionType.COUNT,
    WindowFunctionType.MIN,
    WindowFunctionType.MAX,
];
export var FrameType;
(function (FrameType) {
    FrameType["ROWS"] = "rows";
    FrameType["RANGE"] = "range";
})(FrameType || (FrameType = {}));
export var FrameBoundaryType;
(function (FrameBoundaryType) {
    FrameBoundaryType["UNBOUNDED_PRECEDING"] = "unbounded_preceding";
    FrameBoundaryType["PRECEDING"] = "preceding";
    FrameBoundaryType["CURRENT_ROW"] = "current_row";
    FrameBoundaryType["FOLLOWING"] = "following";
    FrameBoundaryType["UNBOUNDED_FOLLOWING"] = "unbounded_following";
})(FrameBoundaryType || (FrameBoundaryType = {}));
export var TableCalculationTemplateType;
(function (TableCalculationTemplateType) {
    TableCalculationTemplateType["PERCENT_CHANGE_FROM_PREVIOUS"] = "percent_change_from_previous";
    TableCalculationTemplateType["PERCENT_OF_PREVIOUS_VALUE"] = "percent_of_previous_value";
    TableCalculationTemplateType["PERCENT_OF_COLUMN_TOTAL"] = "percent_of_column_total";
    TableCalculationTemplateType["RANK_IN_COLUMN"] = "rank_in_column";
    TableCalculationTemplateType["RUNNING_TOTAL"] = "running_total";
    TableCalculationTemplateType["WINDOW_FUNCTION"] = "window_function";
})(TableCalculationTemplateType || (TableCalculationTemplateType = {}));
export var FieldType;
(function (FieldType) {
    FieldType["METRIC"] = "metric";
    FieldType["DIMENSION"] = "dimension";
})(FieldType || (FieldType = {}));
// This type check is a little fragile because it's based on
// 'displayName'. Ideally these would all have fieldTypes.
export const isTableCalculation = (item) => item
    ? !isCustomDimension(item) &&
        (!!('sql' in item && item.sql) ||
            !!('template' in item && item.template) ||
            !!('formula' in item && item.formula)) &&
        !('description' in item) &&
        !('tableName' in item) &&
        'displayName' in item
    : false;
export const isSqlTableCalculation = (calc) => !!calc && 'sql' in calc && !!calc.sql && calc.sql.length > 0;
export const isTemplateTableCalculation = (calc) => !!calc && 'template' in calc && !!calc.template;
export const isFormulaTableCalculation = (calc) => !!calc && 'formula' in calc && !!calc.formula && calc.formula.length > 0;
export const isField = (field) => field ? !!field.fieldType : false;
export const convertFieldRefToFieldId = (fieldRef, fallbackTableName) => {
    const parts = fieldRef.split('.');
    if (parts.length !== 2) {
        if (fallbackTableName) {
            return `${fallbackTableName}_${fieldRef}`;
        }
        throw new CompileError(`Table calculation contains an invalid reference: ${fieldRef}. References must be of the format "table.field"`, {});
    }
    const [tableName, fieldName] = parts;
    return `${tableName}_${fieldName}`;
};
export var DimensionType;
(function (DimensionType) {
    DimensionType["STRING"] = "string";
    DimensionType["NUMBER"] = "number";
    DimensionType["TIMESTAMP"] = "timestamp";
    DimensionType["DATE"] = "date";
    DimensionType["BOOLEAN"] = "boolean";
})(DimensionType || (DimensionType = {}));
/**
 * Whether a dimension's curated `filter_autocomplete` values can answer a value
 * search without querying the warehouse. Mirrors the Explore filter UI
 * (`useFieldValues`): always use curated values when the warehouse fetch is
 * turned off; otherwise only as the fast path for an empty ("list all") search.
 */
export const shouldUseStaticFilterAutocomplete = (filterAutocomplete, search) => {
    if (!filterAutocomplete)
        return false;
    const hasValues = (filterAutocomplete.values?.length ?? 0) > 0;
    return (!filterAutocomplete.fetchFromWarehouse ||
        (hasValues && search.trim().length === 0));
};
export const filterStaticFilterAutocompleteValues = (values, search) => {
    const normalizedSearch = search.trim().toLowerCase();
    const matched = normalizedSearch.length === 0
        ? values
        : values.filter(({ value, label }) => value.toLowerCase().includes(normalizedSearch) ||
            (label?.toLowerCase().includes(normalizedSearch) ??
                false));
    const seen = new Set();
    return matched
        .map(({ value }) => value)
        .filter((value) => {
        if (seen.has(value))
            return false;
        seen.add(value);
        return true;
    })
        .sort((a, b) => a.localeCompare(b));
};
/**
 * Type guard to check if a compiled field has a compilation error.
 */
export const hasFieldCompilationError = (field) => field.compilationError !== undefined;
export const isDimension = (field) => isField(field) && field.fieldType === FieldType.DIMENSION;
export const isTimeBasedDimension = (item) => isDimension(item) &&
    (item.type === DimensionType.DATE || item.type === DimensionType.TIMESTAMP);
export const getFieldRef = (field) => `${field.table}.${field.name}`;
export const getFieldLabel = (field) => `${field.tableLabel} ${field.label}`;
export var MetricType;
(function (MetricType) {
    MetricType["PERCENTILE"] = "percentile";
    MetricType["AVERAGE"] = "average";
    MetricType["COUNT"] = "count";
    MetricType["COUNT_DISTINCT"] = "count_distinct";
    MetricType["SUM"] = "sum";
    MetricType["SUM_DISTINCT"] = "sum_distinct";
    MetricType["AVERAGE_DISTINCT"] = "average_distinct";
    MetricType["MIN"] = "min";
    MetricType["MAX"] = "max";
    MetricType["PERCENT_OF_PREVIOUS"] = "percent_of_previous";
    MetricType["PERCENT_OF_TOTAL"] = "percent_of_total";
    MetricType["RUNNING_TOTAL"] = "running_total";
    MetricType["NUMBER"] = "number";
    MetricType["MEDIAN"] = "median";
    MetricType["STRING"] = "string";
    MetricType["DATE"] = "date";
    MetricType["TIMESTAMP"] = "timestamp";
    MetricType["BOOLEAN"] = "boolean";
})(MetricType || (MetricType = {}));
export var Format;
(function (Format) {
    Format["KM"] = "km";
    Format["MI"] = "mi";
    Format["SI"] = "si";
    Format["USD"] = "usd";
    Format["GBP"] = "gbp";
    Format["EUR"] = "eur";
    Format["JPY"] = "jpy";
    Format["DKK"] = "dkk";
    Format["ID"] = "id";
    Format["PERCENT"] = "percent";
})(Format || (Format = {}));
export const isFormat = (value) => !!value && Object.values(Format).includes(value);
export const parseMetricType = (metricType) => {
    switch (metricType) {
        case 'percentile':
            return MetricType.PERCENTILE;
        case 'median':
            return MetricType.MEDIAN;
        case 'average':
            return MetricType.AVERAGE;
        case 'count':
            return MetricType.COUNT;
        case 'count_distinct':
            return MetricType.COUNT_DISTINCT;
        case 'sum':
            return MetricType.SUM;
        case 'sum_distinct':
            return MetricType.SUM_DISTINCT;
        case 'average_distinct':
            return MetricType.AVERAGE_DISTINCT;
        case 'min':
            return MetricType.MIN;
        case 'max':
            return MetricType.MAX;
        case 'number':
            return MetricType.NUMBER;
        case 'string':
            return MetricType.STRING;
        case 'date':
            return MetricType.DATE;
        case 'timestamp':
            return MetricType.TIMESTAMP;
        case 'boolean':
            return MetricType.BOOLEAN;
        case 'percent_of_previous':
            return MetricType.PERCENT_OF_PREVIOUS;
        case 'percent_of_total':
            return MetricType.PERCENT_OF_TOTAL;
        case 'running_total':
            return MetricType.RUNNING_TOTAL;
        default:
            throw new Error(`Cannot parse dbt metric with type '${metricType}'`);
    }
};
const NonAggregateMetricTypes = [
    MetricType.STRING,
    MetricType.NUMBER,
    MetricType.DATE,
    MetricType.TIMESTAMP,
    MetricType.BOOLEAN,
];
export const PostCalculationMetricTypes = [
    MetricType.PERCENT_OF_PREVIOUS,
    MetricType.PERCENT_OF_TOTAL,
    MetricType.RUNNING_TOTAL,
];
export const isMetric = (field) => field
    ? 'fieldType' in field && field.fieldType === FieldType.METRIC
    : false;
export const isNonAggregateMetric = (field) => isMetric(field) && NonAggregateMetricTypes.includes(field.type);
export const AggregateMetricTypes = [
    MetricType.SUM,
    MetricType.COUNT,
    MetricType.COUNT_DISTINCT,
    MetricType.AVERAGE,
    MetricType.MIN,
    MetricType.MAX,
    MetricType.MEDIAN,
    MetricType.PERCENTILE,
];
export const isAggregateMetricType = (type) => AggregateMetricTypes.includes(type);
export const isNonAggregateMetricType = (type) => NonAggregateMetricTypes.includes(type);
export const isPostCalculationMetricType = (type) => PostCalculationMetricTypes.includes(type);
export const isPostCalculationMetric = (field) => isMetric(field) && isPostCalculationMetricType(field.type);
export const isCompiledMetric = (field) => isMetric(field) && 'compiledSql' in field;
// The base-type metadata a MIN/MAX metric carries when it aggregates a single
// DATE/TIMESTAMP dimension. Returns an empty object for any other metric type or
// a non-temporal base, so callers can spread it unconditionally. See the
// baseDimensionType field on Metric for why this is denormalized at compile time.
export const getMinMaxBaseDimensionMetadata = (metricType, base) => {
    const isMinMax = metricType === MetricType.MIN || metricType === MetricType.MAX;
    const isTemporalBase = base?.type === DimensionType.DATE ||
        base?.type === DimensionType.TIMESTAMP;
    if (!isMinMax || !base || !isTemporalBase)
        return {};
    return {
        baseDimensionType: base.type,
        baseDimensionTimeInterval: base.timeInterval,
    };
};
export const isFilterableDimension = (dimension) => !!dimension &&
    [
        DimensionType.STRING,
        DimensionType.NUMBER,
        DimensionType.DATE,
        DimensionType.TIMESTAMP,
        DimensionType.BOOLEAN,
    ].includes(dimension.type);
export const isFilterableField = (field) => (isDimension(field) && isFilterableDimension(field)) ||
    isCustomSqlDimension(field) ||
    isMetric(field) ||
    isTableCalculation(field);
export const isFilterableItem = (item) => isDimension(item) ? isFilterableDimension(item) : true;
export const defaultSql = (columnName) => 
// eslint-disable-next-line no-useless-escape
`\$\{TABLE\}.${columnName}`;
export const capitalize = (word) => word ? `${word.charAt(0).toUpperCase()}${word.slice(1).toLowerCase()}` : '';
export const friendlyName = (text) => {
    if (text === '') {
        return '';
    }
    // Use lodash's words function with a safe custom pattern
    // Pattern explanation (avoids backtracking):
    // - [A-Z][a-z]+: Uppercase letter followed by 1+ lowercase (avoids * quantifier)
    // - [a-z]+: One or more lowercase letters
    // - [0-9]+[a-z]+: Digits followed by letters (keeps them together like "1field")
    // - [0-9]+: Just digits
    // - [A-Z]+: Multiple uppercase letters
    const safePattern = /[A-Z][a-z]+|[a-z]+|[0-9]+[a-z]+|[0-9]+|[A-Z]+/g;
    const extractedWords = words(text, safePattern);
    if (extractedWords.length === 0) {
        return '';
    }
    // Join words with spaces and convert to sentence case
    // (only first letter capitalized, rest lowercase)
    const joined = extractedWords.join(' ').toLowerCase();
    return joined.charAt(0).toUpperCase() + joined.slice(1);
};
export const isSummable = (item) => {
    if (!item) {
        return false;
    }
    if (isTableCalculation(item)) {
        return false;
    }
    if (isCustomDimension(item)) {
        return false;
    }
    const numericTypes = [MetricType.COUNT, MetricType.SUM];
    const isNumberDimension = isDimension(item) && item.type === DimensionType.NUMBER;
    const isNumbericType = numericTypes.includes(item.type) || isNumberDimension;
    const isPercent = item.format === 'percent';
    const isDatePart = isDimension(item) && item.timeInterval;
    return isNumbericType && !isPercent && !isDatePart;
};
const SIByteCompacts = [
    Compact.KILOBYTES,
    Compact.MEGABYTES,
    Compact.GIGABYTES,
    Compact.TERABYTES,
    Compact.PETABYTES,
];
export const IECByteCompacts = [
    Compact.KIBIBYTES,
    Compact.MEBIBYTES,
    Compact.GIBIBYTES,
    Compact.TEBIBYTES,
    Compact.PEBIBYTES,
];
export function getCompactOptionsForFormatType(type) {
    if (type === CustomFormatType.BYTES_IEC)
        return IECByteCompacts;
    if (type === CustomFormatType.BYTES_SI)
        return SIByteCompacts;
    const numericCompacts = [
        Compact.THOUSANDS,
        Compact.MILLIONS,
        Compact.BILLIONS,
        Compact.TRILLIONS,
    ];
    if (type === CustomFormatType.NUMBER ||
        type === CustomFormatType.CURRENCY) {
        return [Compact.AUTO, ...numericCompacts];
    }
    return numericCompacts;
}
//# sourceMappingURL=field.js.map