import { clearFilterValue } from '../utils/urlHelper.js';
import ColumnUtils from '../components/_reports/report-heading/report-columns/report-columns-form/utils.js';
import { ReportType } from './UserReports.js';
import logger from '../utils/logger.js';

const DEFAULT_FILTERS_PREFIX = 'filters.';
class ReportQuery {
    selectedPage;
    pageSize;
    sortColumn;
    columns;
    sortedAsc;
    filters;
    filtersPrefix;
    dataProductDefinitionsPath;
    constructor({ fields, template, queryParams, definitionsPath, filtersPrefix = DEFAULT_FILTERS_PREFIX, reportType, }) {
        this.selectedPage = queryParams['selectedPage'] ? Number(queryParams['selectedPage']) : 1;
        this.pageSize = this.getPageSize(queryParams, template, reportType);
        this.sortColumn = queryParams['sortColumn']
            ? queryParams['sortColumn'].toString()
            : this.getDefaultSortColumn(fields);
        this.sortedAsc =
            queryParams['sortedAsc'] !== undefined
                ? queryParams['sortedAsc'] !== 'false'
                : this.getDefaultSortDirection(fields);
        this.dataProductDefinitionsPath =
            definitionsPath ??
                (queryParams['dataProductDefinitionsPath'] ? queryParams['dataProductDefinitionsPath'].toString() : undefined);
        this.filtersPrefix = filtersPrefix;
        this.columns = this.setColumns(queryParams, fields);
        this.filters = {};
        this.setAndTrimFiltersFromQuery(queryParams, fields);
        this.handleUnsetDateTypeDefaultsAndBounds(fields);
    }
    /**
     * Set the filers from the query params
     * - trims the start/end dates to min/max
     * - removes no-filter options
     *
     * @private
     * @param {ParsedQs} queryParams
     * @param {string} [min]
     * @param {string} [max]
     * @memberof ReportQuery
     */
    setAndTrimFiltersFromQuery(queryParams, fields) {
        Object.keys(queryParams)
            .filter((key) => key.startsWith(this.filtersPrefix))
            .filter((key) => queryParams[key])
            .forEach((key) => {
            const filterName = key.replace(this.filtersPrefix, '');
            const p = queryParams[key];
            let value = p ? p.toString() : '';
            // Extract base field name
            const baseFieldName = filterName.split('.')[0];
            const field = fields.find((f) => f.name === baseFieldName);
            if (!field?.filter)
                return;
            const { type, staticOptions } = field.filter;
            // Drop relative-duration for daterange types
            if ((type === 'daterange' || type === 'granulardaterange') && filterName.endsWith('relative-duration')) {
                return;
            }
            // Select / radio / multiselect validation
            if (type.toLowerCase() === 'radio' || type.toLowerCase() === 'select' || type === 'multiselect') {
                const validated = this.validateSelectFilterValue(p, type, staticOptions);
                if (!validated)
                    return;
                value = validated;
            }
            // Handle Date types
            if (field.type === 'date') {
                const { min, max } = field.filter;
                if (type === 'daterange' || type === 'granulardaterange') {
                    if (min && filterName.endsWith('.start')) {
                        if (new Date(value) < new Date(min))
                            value = min;
                    }
                    if (max && filterName.endsWith('.end')) {
                        if (new Date(value) > new Date(max))
                            value = max;
                    }
                }
                if (type === 'date') {
                    if (min && new Date(value) < new Date(min)) {
                        value = min;
                    }
                    if (max && new Date(value) > new Date(max)) {
                        value = max;
                    }
                }
            }
            // Handle no filter values (select, radio)
            if (value !== 'no-filter') {
                this.filters[filterName] = value;
            }
        });
    }
    /**
     * Validates that all staticOption values are correct
     *
     * - checks/validates value against the staticOptions in the filter definition
     * - if not present then it is dropped from the query,
     * - and logged
     *
     * @private
     * @param {unknown} rawValue
     * @param {string} type
     * @param {components['schemas']['FilterDefinition']['staticOptions']} [staticOptions]
     * @param {string} [fieldName]
     * @return {*}  {(string | undefined)}
     * @memberof ReportQuery
     */
    validateSelectFilterValue(rawValue, type, staticOptions, fieldName) {
        if (!staticOptions || !rawValue)
            return undefined;
        // Map lowercase -> original casing
        const valueMap = new Map(staticOptions.map((o) => [o.name.toLowerCase(), o.name]));
        // Normalize input to lowercase strings
        const values = Array.isArray(rawValue)
            ? rawValue.map((v) => v.toString().toLowerCase())
            : rawValue
                .toString()
                .split(',')
                .map((v) => v.trim().toLowerCase());
        if (type === 'multiselect') {
            const invalidValues = values.filter((v) => !valueMap.has(v));
            const filtered = [...new Set(values.map((v) => valueMap.get(v)).filter((v) => v !== undefined))];
            if (invalidValues.length > 0) {
                logger.warn(`Invalid filter values removed for multiselect: ${fieldName}: [${invalidValues.join(', ')}]`);
            }
            if (filtered.length === 0)
                return undefined;
            return filtered.join(',');
        }
        // Single value (radio/select)
        const value = values[0];
        const mappedValue = valueMap.get(value);
        if (!mappedValue) {
            logger.warn(`Invalid filter value: ${fieldName}: ${value}`);
            return undefined;
        }
        return mappedValue;
    }
    /**
     * Handles the setting of default values for date type filters
     * when no user input value has been applied
     *
     * - if min/max bounds in definition we ensure the date in within bounds
     * before being sent to the API
     *
     * @private
     * @param {components['schemas']['FieldDefinition'][]} fields
     * @memberof ReportQuery
     */
    handleUnsetDateTypeDefaultsAndBounds(fields) {
        const dateFields = fields.filter((f) => {
            return ((f.type === 'date' && f.filter && f.filter.type === 'daterange') ||
                (f.type === 'date' && f.filter && f.filter.type === 'date') ||
                (f.type === 'date' && f.filter && f.filter.type === 'granulardaterange'));
        });
        dateFields.forEach((df) => {
            if (df.filter) {
                const min = df.filter.min ?? undefined;
                const max = df.filter.max ?? undefined;
                this.setDefaultBoundsOnUnsetDatefields(df, df.filter, min, max);
            }
        });
    }
    /**
     * Set the default value for date filters where min/max is provided
     *
     * - If min / max exist and the user hasn’t already provided date bounds in the request,
     * - the code injects them as defaults so the filtering stays within those bounds.
     *
     * @private
     * @memberof ReportQuery
     */
    setDefaultBoundsOnUnsetDatefields(dateField, dateFieldFilter, min, max) {
        const existingKeys = Object.keys(this.filters);
        const baseKey = dateField.name;
        const startKey = `${baseKey}.start`;
        const endKey = `${baseKey}.end`;
        if (dateFieldFilter.type === 'daterange' || dateFieldFilter.type === 'granulardaterange') {
            const hasStart = existingKeys.some((key) => key === startKey);
            const hasEnd = existingKeys.some((key) => key === endKey);
            if (min && !hasStart) {
                this.filters[startKey] = min;
            }
            if (max && !hasEnd) {
                this.filters[endKey] = max;
            }
        }
        if (dateFieldFilter.type === 'date') {
            // Any value for this field counts (base or dotted)
            const hasAnyValueForField = existingKeys.some((key) => key === baseKey || key.startsWith(`${baseKey}.`));
            if (!hasAnyValueForField) {
                const value = min ?? max;
                if (value) {
                    this.filters[baseKey] = value;
                }
            }
        }
    }
    setColumns(queryParams, fields) {
        if (queryParams['columns']) {
            const columns = typeof queryParams['columns'] === 'string'
                ? queryParams['columns'].split(',')
                : queryParams['columns'];
            return ColumnUtils.ensureMandatoryColumns(fields, columns);
        }
        return fields.filter((f) => f.visible).map((f) => f.name);
    }
    /**
     * Sets the default sort column
     *
     * @private
     * @param {components['schemas']['FieldDefinition'][]} fields
     * @return {*}
     * @memberof ReportQuery
     */
    getDefaultSortColumn(fields) {
        const defaultSortColumn = fields.find((f) => f.defaultsort);
        return defaultSortColumn ? defaultSortColumn.name : fields.find((f) => f.sortable)?.name;
    }
    /**
     * Gets the default sort direction
     * - Default if not set in DPD: ASC
     *
     * @private
     * @param {components['schemas']['FieldDefinition'][]} fields
     * @return {*}
     * @memberof ReportQuery
     */
    getDefaultSortDirection(fields) {
        const field = fields.find((f) => f.defaultsort);
        if (field) {
            return field.sortDirection ? field.sortDirection === 'asc' : true;
        }
        return true;
    }
    /**
     * Get the pagesize for the request
     * - from query, otherwise set meaningful default
     *
     * @param {ParsedQs} queryParams
     * @param {Template} [template]
     * @param {ReportType} [reportType]
     * @return {*}  {number}
     * @memberof ReportQuery
     */
    getPageSize(queryParams, template, reportType) {
        let pageSize = 5000000;
        if (!reportType || reportType === ReportType.REPORT) {
            if (queryParams['pageSize']) {
                pageSize = Number(queryParams['pageSize']);
            }
            else if (template) {
                pageSize = this.getDefaultPageSize(template);
            }
        }
        return pageSize;
    }
    toRecordWithFilterPrefix(removeClearedFilters = false) {
        const record = {
            selectedPage: this.selectedPage.toString(),
            ...(this.pageSize && { pageSize: this.pageSize.toString() }),
            ...(this.sortColumn && { sortColumn: this.sortColumn }),
            sortedAsc: this.sortedAsc.toString(),
            columns: this.columns,
        };
        if (this.dataProductDefinitionsPath) {
            record['dataProductDefinitionsPath'] = this.dataProductDefinitionsPath;
        }
        Object.keys(this.filters).forEach((filterName) => {
            const value = this.filters[filterName];
            if ((value && !removeClearedFilters) || value !== clearFilterValue) {
                record[`${this.filtersPrefix}${filterName}`] = value;
            }
        });
        return record;
    }
    /**
     * Gets a meaningful default pagesize for a request
     *
     * @private
     * @param {Template} template
     * @return {*}
     * @memberof ReportQuery
     */
    getDefaultPageSize(template) {
        const maxResultsSize = 500000; // catch all rows
        const standardPage = 20; // smallest pagesize
        switch (template) {
            // These templates do no include pagination so return all rows
            case 'list-section':
            case 'summary-section':
            case 'row-section-child':
            case 'parent-child':
            case 'parent-child-section':
                return maxResultsSize;
            default:
                return standardPage;
        }
    }
}

export { DEFAULT_FILTERS_PREFIX, ReportQuery, ReportQuery as default };
//# sourceMappingURL=ReportQuery.js.map
