// @ts-nocheck
/**
 * Mapping unit.
 *
 * @typedef {Object} module:@citation-js/core.util.Translator~statement
 * @property {String|Array<String>} [source] - properties to source value from
 * @property {String|Array<String>} [target] - properties the value should go to
 * @property {Object} [convert] - convert serialized or nested values
 * @property {module:@citation-js/core.util.Translator~convertProp} [convert.toTarget] - function to convert source prop to target
 * @property {module:@citation-js/core.util.Translator~convertProp} [convert.toSource] - function to convert target prop to source
 * @property {Object} [when] - conditions as to when this statement should apply
 * @property {module:@citation-js/core.util.Translator~condition} [when.source]
 * @property {module:@citation-js/core.util.Translator~condition} [when.target]
 */
/**
 * In the case of toTarget, source is input and target is output. In the case of
 * toSource, source is output and target is input.
 *
 * @callback module:@citation-js/core.util.Translator~convertProp
 * @param {...*} input - input values
 * @return {Array|*} If output is an array and multiple output properties are
 *                   specified, the output is divided over those properties.
 */
/**
 * A top-level Boolean enables or disables a mapping unit in a given direction.
 * Otherwise, individual properties are checked with an object specifying the
 * property name and one of four things:
 *
 *  - A boolean, checking for presence of the property
 *  - An array of values for checking whether the property value is in them
 *  - A value that should match with the property value
 *  - A predicate function taking in the value and returning a boolean
 *
 * All conditions have to be fulfilled for the mapping unit to be enabled.
 *
 * @typedef {Boolean|Object<String,Boolean|Array<*>|module:@citation-js/core.util.Translator~conditionPropPredicate|*>} module:@citation-js/core.util.Translator~condition
 */
/**
 * Return, based on a property, whether a mapping should apply.
 *
 * @callback module:@citation-js/core.util.Translator~conditionPropPredicate
 * @param {*} input - input value
 * @return {Boolean}
 */
/**
 * Return, whether a mapping should apply.
 *
 * @callback module:@citation-js/core.util.Translator~conditionPredicate
 * @param {Object} input - input
 * @return {Boolean}
 */
/**
 * @access private
 * @memberof module:@citation-js/core.util.Translator
 * @param {module:@citation-js/core.util.Translator~condition} condition
 * @return {module:@citation-js/core.util.Translator~conditionPredicate}
 */
function createConditionEval(condition) {
    return function conditionEval(input) {
        if (typeof condition === 'boolean') {
            return condition;
        }
        return Object.keys(condition).every((prop) => {
            const value = condition[prop];
            if (value === true) {
                return prop in input;
            }
            else if (value === false) {
                return !(prop in input);
            }
            else if (typeof value === 'function') {
                return value(input[prop]);
            }
            else if (Array.isArray(value)) {
                return value.includes(input[prop]);
            }
            else {
                return input[prop] === value;
            }
        });
    };
}
/**
 * @access private
 * @typedef {Object} module:@citation-js/core.util.Translator~normalizedStatement
 * @property {Array<String>} inputProp
 * @property {Array<String>} outputProp
 * @property {module:@citation-js/core.util.Translator~convertProp} convert
 * @property {module:@citation-js/core.util.Translator~conditionPredicate} condition
 */
/**
 * @access private
 * @memberof module:@citation-js/core.util.Translator
 * @param {module:@citation-js/core.util.Translator~statement} prop
 * @param {Boolean} toSource
 * @return {module:@citation-js/core.util.Translator~normalizedStatement} normalized one-directional object
 */
function parsePropStatement(prop, toSource) {
    let inputProp;
    let outputProp;
    let convert;
    let condition;
    if (typeof prop === 'string') {
        inputProp = outputProp = prop;
    }
    else if (prop) {
        inputProp = toSource ? prop.target : prop.source;
        outputProp = toSource ? prop.source : prop.target;
        if (prop.convert) {
            convert = toSource ? prop.convert.toSource : prop.convert.toTarget;
        }
        if (prop.when) {
            condition = toSource ? prop.when.target : prop.when.source;
            if (condition != null) {
                condition = createConditionEval(condition);
            }
        }
    }
    else {
        return null;
    }
    inputProp = [].concat(inputProp).filter(Boolean);
    outputProp = [].concat(outputProp).filter(Boolean);
    return { inputProp, outputProp, convert, condition };
}
/**
 * Return, whether a mapping should apply.
 *
 * @callback module:@citation-js/core.util.Translator~convert
 * @param {Object} input - input
 * @return {Object} output
 */
/**
 * @access private
 * @memberof module:@citation-js/core.util.Translator
 * @param {Array<module:@citation-js/core.util.Translator~statement>} props
 * @param {Boolean} toSource
 * @return {module:@citation-js/core.util.Translator~convert} converter
 */
function createConverter(props, toSource) {
    toSource = toSource === Translator.CONVERT_TO_SOURCE;
    props = props.map((prop) => parsePropStatement(prop, toSource)).filter(Boolean);
    return function converter(input) {
        const output = {};
        for (const { inputProp, outputProp, convert, condition } of props) {
            // Skip when no output will be assigned
            if (outputProp.length === 0) {
                continue;
                // Skip when requested by the requirements of the prop converter
            }
            else if (condition && !condition(input)) {
                continue;
                // Skip when none of the required props are in the input data
                // NOTE: if no input is required, do not skip
            }
            else if (inputProp.length !== 0 && inputProp.every((prop) => !(prop in input))) {
                continue;
            }
            let outputData = inputProp.map((prop) => input[prop]);
            if (convert) {
                try {
                    const converted = convert.apply(input, outputData);
                    outputData = outputProp.length === 1 ? [converted] : converted;
                }
                catch (cause) {
                    throw new Error(`Failed to convert ${inputProp} to ${outputProp}`, { cause });
                }
            }
            outputProp.forEach((prop, index) => {
                const value = outputData[index];
                if (value !== undefined) {
                    output[prop] = value;
                }
            });
        }
        return output;
    };
}
/**
 * @memberof module:@citation-js/core.util
 *
 * @param {Array<module:@citation-js/core.util.Translator~statement>} props
 *
 * @todo proper merging (?)
 * @todo 'else' conditions
 */
class Translator {
    constructor(props) {
        /**
         * @type {module:@citation-js/core.util.Translator~convert}
         */
        this.convertToSource = createConverter(props, Translator.CONVERT_TO_SOURCE);
        /**
         * @type {module:@citation-js/core.util.Translator~convert}
         */
        this.convertToTarget = createConverter(props, Translator.CONVERT_TO_TARGET);
    }
}
/**
 * @memberof module:@citation-js/core.util.Translator
 * @property {Symbol} CONVERT_TO_SOURCE
 */
Translator.CONVERT_TO_SOURCE = Symbol('convert to source');
/**
 * @memberof module:@citation-js/core.util.Translator
 * @property {Symbol} CONVERT_TO_TARGET
 */
Translator.CONVERT_TO_TARGET = Symbol('convert to target');
export { Translator };
