// @ts-nocheck
import { deepCopy, upgradeCsl } from '../../util/index.js';
import logger from '../../logger.js';
import { get as getTypeInfo } from './register.js';
import { type as parseType } from './type.js';
import { data as parseData, dataAsync as parseDataAsync } from './data.js';
import { applyGraph, removeGraph } from './graph.js';
/**
 * @access private
 * @param {Array<Object>} graph
 * @return {String}
 */
function prepareParseGraph(graph) {
    return (graph
        // collapse continuous iterations of the same type
        .reduce((array, next) => {
        const last = array[array.length - 1];
        if (last && last.type === next.type) {
            last.count = last.count + 1 || 2;
        }
        else {
            array.push(next);
        }
        return array;
    }, [])
        // presentation
        .map((element) => (element.count > 1 ? element.count + 'x ' : '') + element.type)
        .join(' -> '));
}
/**
 * @access private
 * @memberof module:@citation-js/core.plugins.input
 * @constructor ChainParser
 *
 * @param {module:@citation-js/core~InputData} input
 * @param {module:@citation-js/core~InputOptions} options
 */
class ChainParser {
    constructor(input, options = {}) {
        this.options = Object.assign({
            generateGraph: true,
            forceType: parseType(input),
            maxChainLength: 10,
            strict: true,
            target: '@csl/list+object',
        }, options);
        this.type = this.options.forceType;
        this.data = typeof input === 'object' ? deepCopy(input) : input;
        this.graph = [{ type: this.type, data: input }];
        this.iteration = 0;
    }
    /**
     * After a round of data parsing, update type information and check targets.
     *
     * @access public
     * @return {Boolean} Whether this is the last iteration or not
     */
    iterate() {
        if (this.iteration !== 0) {
            const typeInfo = getTypeInfo(this.type);
            if (typeInfo && typeInfo.outputs) {
                this.type = typeInfo.outputs;
            }
            else {
                this.type = parseType(this.data);
            }
            this.graph.push({ type: this.type });
        }
        if (this.error || this.type === this.options.target) {
            return false;
        }
        else if (this.iteration >= this.options.maxChainLength) {
            this.error = new RangeError(`Max. number of parsing iterations reached (${prepareParseGraph(this.graph)})`);
            return false;
        }
        else {
            this.iteration++;
            return true;
        }
    }
    /**
     * Finish the iteration and return parsed data.
     *
     * @access public
     * @return Array<module:@citation-js/core~CSL>
     */
    end() {
        if (this.error) {
            logger.error('[core]', this.error.message);
            if (this.options.strict !== false) {
                throw this.error;
            }
            else {
                return [];
            }
        }
        else if (this.options.target === '@csl/list+object') {
            return upgradeCsl(this.data).map(this.options.generateGraph ? (entry) => applyGraph(entry, this.graph) : removeGraph);
        }
        else {
            return this.data;
        }
    }
}
/**
 * Parse input until success.
 *
 * @access protected
 * @method chain
 * @memberof module:@citation-js/core.plugins.input
 *
 * @param {module:@citation-js/core~InputData} input - input data
 * @param {module:@citation-js/core~InputOptions} [options] - options
 *
 * @return {Array<module:@citation-js/core~CSL>} The parsed input
 */
export const chain = (...args) => {
    const chain = new ChainParser(...args);
    while (chain.iterate()) {
        try {
            chain.data = parseData(chain.data, chain.type);
        }
        catch (e) {
            chain.error = e;
        }
    }
    return chain.end();
};
/**
 * Parse input once.
 *
 * @access protected
 * @method chainLink
 * @memberof module:@citation-js/core.plugins.input
 *
 * @param {module:@citation-js/core~InputData} input - input data
 *
 * @return {module:@citation-js/core~InputData} The parsed input
 */
export const chainLink = (input) => {
    const type = parseType(input);
    const output = type.match(/array|object/) ? deepCopy(input) : input;
    return parseData(output, type);
};
/**
 * Parse input until success. (async)
 *
 * @access protected
 * @method chainAsync
 * @memberof module:@citation-js/core.plugins.input
 *
 * @param {module:@citation-js/core~InputData} input - input data
 * @param {module:@citation-js/core~InputOptions} [options] - options
 *
 * @return {Promise<Array<module:@citation-js/core~CSL>>} The parsed input
 */
export const chainAsync = async (...args) => {
    const chain = new ChainParser(...args);
    while (chain.iterate()) {
        chain.data = await parseDataAsync(chain.data, chain.type).catch((e) => {
            chain.error = e;
        });
    }
    return chain.end();
};
/**
 * Parse input once. (async)
 *
 * @access protected
 * @method chainLinkAsync
 * @memberof module:@citation-js/core.plugins.input
 *
 * @param {module:@citation-js/core~InputData} input - The input data
 *
 * @return {Promise<module:@citation-js/core~InputData>} The parsed input
 */
export const chainLinkAsync = async (input) => {
    const type = parseType(input);
    const output = type.match(/array|object/) ? deepCopy(input) : input;
    return parseDataAsync(output, type);
};
