import { isArray } from '../../../utils/utils.js';
import { castUnknownObject, serialize as coreSerialize, delimiter, isFacetObject, isObject, isRangeFacetKey, isRangeFacetObject, preprocessObjectPairs, serializeFacets, serializeRangeFacets, serializeSpecialCharacters, splitOnFirstEqual, } from '../../search-parameters/search-parameter-serializer.js';
import { buildFieldsSortCriterion, buildRelevanceSortCriterion, SortBy, SortDirection, } from '../sort/sort.js';
const sortFieldAndDirectionSeparator = ' ';
const sortFieldsJoiner = ',';
export const commerceFacetsRegex = /^(f|fExcluded|cf|nf|nfExcluded|df|dfExcluded|mnf|mnfExcluded|lf)-(.+)$/;
export const searchSerializer = {
    serialize,
    deserialize,
};
export const productListingSerializer = {
    serialize,
    deserialize,
};
const supportedFacetParameters = {
    f: true,
    fExcluded: true,
    lf: true,
    cf: true,
    nf: true,
    nfExcluded: true,
    df: true,
    dfExcluded: true,
    mnf: true,
    mnfExcluded: true,
};
function serialize(parameters) {
    return sanitizeNumericFacetParams(coreSerialize(serializePair)(parameters));
}
function serializePair(pair) {
    const [key, val] = pair;
    if (!isValidKey(key)) {
        return '';
    }
    if (key === 'sortCriteria') {
        return isSortCriteriaObject(val) ? serializeSortCriteria(key, val) : '';
    }
    if (keyHasObjectValue(key) && !isRangeFacetKey(key)) {
        return isFacetObject(val) ? serializeFacets(key, val) : '';
    }
    if (key === 'nf' || key === 'df' || key === 'mnf') {
        return isRangeFacetObject(val) ? serializeRangeFacets(key, val) : '';
    }
    if (val !== undefined) {
        return serializeSpecialCharacters(key, val);
    }
    return '';
}
function serializeSortCriteria(key, val) {
    return serializeSpecialCharacters(key, buildCriterionExpression(val));
}
export function buildCriterionExpression(criterion) {
    if (!criterion) {
        return '';
    }
    if (criterion.by === SortBy.Relevance) {
        return 'relevance';
    }
    return criterion.fields
        .map((field) => `${field.name}${sortFieldAndDirectionSeparator}${field.direction}`)
        .join(sortFieldsJoiner);
}
function isValidKey(key) {
    return isValidBasicKey(key) || keyHasObjectValue(key);
}
function isSortCriteriaObject(obj) {
    if (!isObject(obj) || !('by' in obj)) {
        return false;
    }
    if (obj.by === 'relevance') {
        return true;
    }
    if (obj.by === 'fields' && 'fields' in obj && isArray(obj.fields)) {
        return obj.fields.every((field) => {
            return (isObject(field) &&
                'name' in field &&
                typeof field.name === 'string' &&
                (('direction' in field &&
                    (field.direction === SortDirection.Ascending ||
                        field.direction === SortDirection.Descending)) ||
                    !('direction' in field)));
        });
    }
    return false;
}
function isValidBasicKey(key) {
    const supportedBasicParameters = {
        q: true,
        sortCriteria: true,
        page: true,
        perPage: true,
    };
    return key in supportedBasicParameters;
}
function deserialize(fragment) {
    const parts = fragment.split(delimiter);
    const keyValuePairs = parts
        .map(splitOnFirstEqual)
        .map((parts) => preprocessObjectPairs(parts, commerceFacetsRegex))
        .filter(isValidPair)
        .map(cast);
    return keyValuePairs.reduce((acc, pair) => {
        const [key, val] = pair;
        if (keyHasObjectValue(key)) {
            const mergedValues = { ...acc[key], ...val };
            // oxlint-disable-next-line oxc/no-accumulating-spread -- <>
            return { ...acc, [key]: mergedValues };
        }
        if (key === 'sortCriteria') {
            const sortCriteria = deserializeSortCriteria(val);
            // oxlint-disable-next-line oxc/no-accumulating-spread -- <>
            return { ...acc, [key]: sortCriteria };
        }
        // oxlint-disable-next-line oxc/no-accumulating-spread -- <>
        return { ...acc, [key]: val };
    }, {});
}
export function keyHasObjectValue(key) {
    return key in supportedFacetParameters;
}
function isValidPair(pair) {
    const validKey = isValidKey(pair[0]);
    const lengthOfTwo = pair.length === 2;
    return validKey && lengthOfTwo;
}
function cast(pair) {
    const [key, value] = pair;
    if (key === 'page' || key === 'perPage') {
        return [key, parseInt(value, 10)];
    }
    if (keyHasObjectValue(key)) {
        return [key, castUnknownObject(value)];
    }
    return [key, decodeURIComponent(value)];
}
export function deserializeSortCriteria(value) {
    if (value === 'relevance') {
        return buildRelevanceSortCriterion();
    }
    const criteria = value.split(sortFieldsJoiner);
    if (!criteria.length) {
        return undefined;
    }
    return criteria.reduce((acc, joinedFieldAndDirection) => {
        const fieldAndDirection = joinedFieldAndDirection
            .trim()
            .split(sortFieldAndDirectionSeparator);
        if (fieldAndDirection.length !== 2) {
            return acc;
        }
        const field = fieldAndDirection[0].toLowerCase();
        const direction = fieldAndDirection[1].toLowerCase();
        acc.fields.push({ name: field, direction: direction });
        return acc;
    }, buildFieldsSortCriterion([]));
}
const isManualNumericFacet = (param) => param.startsWith('mnf-');
const isNumericFacet = (param) => param.startsWith('nf-') || isManualNumericFacet(param);
function sanitizeNumericFacetParams(params) {
    const seenNumericFacets = new Set();
    const dedupedNumericFacets = new Set();
    const sanitized = [];
    for (const param of params.split(delimiter)) {
        if (!isNumericFacet(param)) {
            sanitized.push(param);
            continue;
        }
        const normalizedParam = param.replace(/^mnf-/, 'nf-');
        if (!seenNumericFacets.has(normalizedParam)) {
            seenNumericFacets.add(normalizedParam);
            dedupedNumericFacets.add(param);
            continue;
        }
        if (isManualNumericFacet(param)) {
            // When equivalent mnf and nf parameters are present in the params, prioritize the mnf.
            dedupedNumericFacets.delete(normalizedParam);
            dedupedNumericFacets.add(param);
        }
    }
    sanitized.push(...dedupedNumericFacets);
    return sanitized.join(delimiter);
}
