"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateEvent = validateEvent;
function deepCopyConstraintMapping(mapping) {
    const result = {};
    for (const [key, arr] of Object.entries(mapping)) {
        result[key] = [...arr];
    }
    return result;
}
function deepCopyChildren(children) {
    const result = {};
    for (const [propName, constraintsUnknown] of Object.entries(children)) {
        const constraints = constraintsUnknown;
        result[propName] = {
            type: constraints.type,
            required: constraints.required,
            isList: constraints.isList,
            pinnedValues: constraints.pinnedValues ? deepCopyConstraintMapping(constraints.pinnedValues) : undefined,
            allowedValues: constraints.allowedValues ? deepCopyConstraintMapping(constraints.allowedValues) : undefined,
            minMaxRanges: constraints.minMaxRanges ? deepCopyConstraintMapping(constraints.minMaxRanges) : undefined,
            children: constraints.children ? deepCopyChildren(constraints.children) : undefined
        };
    }
    return result;
}
function mergeChildren(target, source) {
    for (const [propName, sourceConstraintsUnknown] of Object.entries(source)) {
        const sourceConstraints = sourceConstraintsUnknown;
        if (!target[propName]) {
            target[propName] = {
                type: sourceConstraints.type,
                required: sourceConstraints.required,
                isList: sourceConstraints.isList,
                pinnedValues: sourceConstraints.pinnedValues
                    ? deepCopyConstraintMapping(sourceConstraints.pinnedValues)
                    : undefined,
                allowedValues: sourceConstraints.allowedValues
                    ? deepCopyConstraintMapping(sourceConstraints.allowedValues)
                    : undefined,
                minMaxRanges: sourceConstraints.minMaxRanges
                    ? deepCopyConstraintMapping(sourceConstraints.minMaxRanges)
                    : undefined,
                children: sourceConstraints.children ? deepCopyChildren(sourceConstraints.children) : undefined
            };
        }
        else {
            const targetConstraints = target[propName];
            mergeConstraintMappings(targetConstraints, sourceConstraints);
            if (sourceConstraints.isList !== undefined) {
                targetConstraints.isList = sourceConstraints.isList;
            }
            if (sourceConstraints.children) {
                if (!targetConstraints.children) {
                    targetConstraints.children = deepCopyChildren(sourceConstraints.children);
                }
                else {
                    mergeChildren(targetConstraints.children, sourceConstraints.children);
                }
            }
        }
    }
}
function mergeConstraintMappings(target, source) {
    if (source.pinnedValues) {
        if (!target.pinnedValues) {
            target.pinnedValues = {};
        }
        for (const key of Object.keys(source.pinnedValues)) {
            if (target.pinnedValues[key]) {
                const merged = new Set(target.pinnedValues[key]);
                for (const id of source.pinnedValues[key]) {
                    merged.add(id);
                }
                target.pinnedValues[key] = Array.from(merged);
            }
            else {
                target.pinnedValues[key] = [...source.pinnedValues[key]];
            }
        }
    }
    if (source.allowedValues) {
        if (!target.allowedValues) {
            target.allowedValues = {};
        }
        for (const key of Object.keys(source.allowedValues)) {
            if (target.allowedValues[key]) {
                const merged = new Set(target.allowedValues[key]);
                for (const id of source.allowedValues[key]) {
                    merged.add(id);
                }
                target.allowedValues[key] = Array.from(merged);
            }
            else {
                target.allowedValues[key] = [...source.allowedValues[key]];
            }
        }
    }
    if (source.minMaxRanges) {
        if (!target.minMaxRanges) {
            target.minMaxRanges = {};
        }
        for (const key of Object.keys(source.minMaxRanges)) {
            if (target.minMaxRanges[key]) {
                const merged = new Set(target.minMaxRanges[key]);
                for (const id of source.minMaxRanges[key]) {
                    merged.add(id);
                }
                target.minMaxRanges[key] = Array.from(merged);
            }
            else {
                target.minMaxRanges[key] = [...source.minMaxRanges[key]];
            }
        }
    }
}
function validateEvent(properties, specResponse) {
    const caches = {
        allowedValuesCache: new Map()
    };
    const allEventIds = collectAllEventIds(specResponse.events);
    const constraintsByProperty = collectConstraintsByPropertyName(specResponse.events);
    const propertyResults = {};
    for (const propName of Object.keys(properties)) {
        const value = properties[propName];
        const constraints = constraintsByProperty[propName];
        if (!constraints) {
            propertyResults[propName] = {};
        }
        else {
            const result = validatePropertyConstraints(value, constraints, allEventIds, caches);
            propertyResults[propName] = result;
        }
    }
    return {
        metadata: specResponse.metadata,
        propertyResults
    };
}
function collectAllEventIds(events) {
    const ids = [];
    for (const event of events) {
        ids.push(event.baseEventId);
        for (let j = 0; j < event.variantIds.length; j++) {
            ids.push(event.variantIds[j]);
        }
    }
    return ids;
}
function collectConstraintsByPropertyName(events) {
    if (events.length === 0) {
        return {};
    }
    if (events.length === 1) {
        return events[0].props;
    }
    const result = {};
    for (const event of events) {
        for (const [propName, constraintsUnknown] of Object.entries(event.props)) {
            const constraints = constraintsUnknown;
            if (!result[propName]) {
                result[propName] = {
                    type: constraints.type,
                    required: constraints.required,
                    isList: constraints.isList,
                    pinnedValues: constraints.pinnedValues ? { ...constraints.pinnedValues } : undefined,
                    allowedValues: constraints.allowedValues ? { ...constraints.allowedValues } : undefined,
                    minMaxRanges: constraints.minMaxRanges ? { ...constraints.minMaxRanges } : undefined,
                    children: constraints.children ? deepCopyChildren(constraints.children) : undefined
                };
            }
            else {
                const existing = result[propName];
                mergeConstraintMappings(existing, constraints);
                if (constraints.isList !== undefined) {
                    existing.isList = constraints.isList;
                }
                if (constraints.children) {
                    if (!existing.children) {
                        existing.children = deepCopyChildren(constraints.children);
                    }
                    else {
                        mergeChildren(existing.children, constraints.children);
                    }
                }
            }
        }
    }
    return result;
}
const MAX_CHILD_DEPTH = 2;
function validatePropertyConstraints(value, constraints, allEventIds, caches, depth = 0) {
    const result = {};
    if (!constraints.required && (value === null || value === undefined)) {
        return result;
    }
    if (depth >= MAX_CHILD_DEPTH) {
        return result;
    }
    if (constraints.isList) {
        return validateListProperty(value, constraints, allEventIds, caches, depth);
    }
    if (constraints.children) {
        return validateObjectProperty(value, constraints, allEventIds, caches, depth);
    }
    return validatePrimitiveProperty(value, constraints, allEventIds, caches);
}
function validateListProperty(value, constraints, allEventIds, caches, depth) {
    if (!Array.isArray(value)) {
        return {};
    }
    const listValue = value;
    if (constraints.children && Array.isArray(listValue) && listValue.length > 0) {
        const aggregatedChildrenResults = {};
        for (const item of listValue) {
            const itemResult = validateObjectProperty(item, constraints, allEventIds, caches, depth);
            if (itemResult.children) {
                for (const [childName, childResult] of Object.entries(itemResult.children)) {
                    if (!aggregatedChildrenResults[childName]) {
                        aggregatedChildrenResults[childName] = {
                            failed: new Set(),
                            passed: new Set(),
                            children: {}
                        };
                    }
                    if (childResult.failedEventIds) {
                        addIdsToSet(childResult.failedEventIds, aggregatedChildrenResults[childName].failed);
                    }
                }
            }
        }
        const resultChildren = {};
        for (const [childName, agg] of Object.entries(aggregatedChildrenResults)) {
            if (agg.failed.size > 0) {
                resultChildren[childName] = buildValidationResult(agg.failed, allEventIds);
            }
        }
        if (Object.keys(resultChildren).length > 0) {
            return { children: resultChildren };
        }
        return {};
    }
    const failedIds = new Set();
    for (const item of listValue) {
        const itemFailedIds = new Set();
        if (constraints.pinnedValues) {
            checkPinnedValues(item, constraints.pinnedValues, itemFailedIds);
        }
        if (constraints.allowedValues) {
            checkAllowedValues(item, constraints.allowedValues, itemFailedIds, caches.allowedValuesCache);
        }
        if (constraints.minMaxRanges) {
            checkMinMaxRanges(item, constraints.minMaxRanges, itemFailedIds);
        }
        addIdsToSet(Array.from(itemFailedIds), failedIds);
    }
    return buildValidationResult(failedIds, allEventIds);
}
function validateObjectProperty(value, constraints, allEventIds, caches, depth) {
    const result = {};
    const childrenResults = {};
    const valueObj = typeof value === 'object' && value !== null && !Array.isArray(value)
        ? value
        : {};
    if (constraints.children) {
        for (const [childName, childConstraints] of Object.entries(constraints.children)) {
            if (!Object.prototype.hasOwnProperty.call(valueObj, childName)) {
                continue;
            }
            const childValue = valueObj[childName];
            const childResult = validatePropertyConstraints(childValue, childConstraints, allEventIds, caches, depth + 1);
            if (childResult.failedEventIds || childResult.passedEventIds || childResult.children) {
                childrenResults[childName] = childResult;
            }
        }
    }
    if (Object.keys(childrenResults).length > 0) {
        result.children = childrenResults;
    }
    return result;
}
function validatePrimitiveProperty(value, constraints, allEventIds, caches) {
    const failedIds = new Set();
    if (constraints.pinnedValues) {
        checkPinnedValues(value, constraints.pinnedValues, failedIds);
    }
    if (constraints.allowedValues) {
        checkAllowedValues(value, constraints.allowedValues, failedIds, caches.allowedValuesCache);
    }
    if (constraints.minMaxRanges) {
        checkMinMaxRanges(value, constraints.minMaxRanges, failedIds);
    }
    return buildValidationResult(failedIds, allEventIds);
}
function buildValidationResult(failedIds, allEventIds) {
    const failedArray = Array.from(failedIds);
    if (failedArray.length === 0) {
        return {};
    }
    const passedIds = allEventIds.filter((id) => !failedIds.has(id));
    if (passedIds.length < failedArray.length && passedIds.length > 0) {
        return { passedEventIds: passedIds };
    }
    else {
        return { failedEventIds: failedArray };
    }
}
function addIdsToSet(ids, set) {
    for (const id of ids) {
        set.add(id);
    }
}
function convertValueToString(value) {
    if (value === null ||
        value === undefined ||
        typeof value === 'boolean' ||
        typeof value === 'number' ||
        typeof value === 'string') {
        return String(value);
    }
    if (Array.isArray(value) || typeof value === 'object') {
        try {
            return JSON.stringify(value);
        }
        catch (e) {
            return String(value);
        }
    }
    return String(value);
}
function checkPinnedValues(value, pinnedValues, failedIds) {
    const stringValue = convertValueToString(value);
    for (const [pinnedValue, eventIds] of Object.entries(pinnedValues)) {
        if (stringValue !== pinnedValue) {
            addIdsToSet(eventIds, failedIds);
        }
    }
}
function checkAllowedValues(value, allowedValues, failedIds, allowedValuesCache) {
    const stringValue = convertValueToString(value);
    for (const [allowedArrayJson, eventIds] of Object.entries(allowedValues)) {
        let allowedSet = allowedValuesCache.get(allowedArrayJson);
        if (!allowedSet) {
            try {
                const allowedArray = JSON.parse(allowedArrayJson);
                allowedSet = new Set(allowedArray);
                allowedValuesCache.set(allowedArrayJson, allowedSet);
            }
            catch (e) {
                continue;
            }
        }
        if (!allowedSet.has(stringValue)) {
            addIdsToSet(eventIds, failedIds);
        }
    }
}
function checkMinMaxRanges(value, minMaxRanges, failedIds) {
    if (typeof value !== 'number') {
        for (const eventIds of Object.values(minMaxRanges)) {
            addIdsToSet(eventIds, failedIds);
        }
        return;
    }
    if (Number.isNaN(value)) {
        for (const eventIds of Object.values(minMaxRanges)) {
            addIdsToSet(eventIds, failedIds);
        }
        return;
    }
    for (const [rangeStr, eventIds] of Object.entries(minMaxRanges)) {
        const [minStr, maxStr] = rangeStr.split(',');
        const min = minStr === '' ? -Infinity : parseFloat(minStr);
        const max = maxStr === '' ? Infinity : parseFloat(maxStr);
        if (isNaN(min) || isNaN(max)) {
            continue;
        }
        if (value < min || value > max) {
            addIdsToSet(eventIds, failedIds);
        }
    }
}
//# sourceMappingURL=event-validator-functions.js.map