'use strict';

var model = require('../../../../../../../@chevrotain_gast@11.0.3/node_modules/@chevrotain/gast/lib/src/model.js');
var helpers = require('../../../../../../../@chevrotain_gast@11.0.3/node_modules/@chevrotain/gast/lib/src/helpers.js');
var uniq = require('../../../../../../../lodash-es@4.17.21/node_modules/lodash-es/uniq.js');
var map = require('../../../../../../../lodash-es@4.17.21/node_modules/lodash-es/map.js');
var flatten = require('../../../../../../../lodash-es@4.17.21/node_modules/lodash-es/flatten.js');

function first(prod) {
    /* istanbul ignore else */
    if (prod instanceof model.NonTerminal) {
        // this could in theory cause infinite loops if
        // (1) prod A refs prod B.
        // (2) prod B refs prod A
        // (3) AB can match the empty set
        // in other words a cycle where everything is optional so the first will keep
        // looking ahead for the next optional part and will never exit
        // currently there is no safeguard for this unique edge case because
        // (1) not sure a grammar in which this can happen is useful for anything (productive)
        return first(prod.referencedRule);
    }
    else if (prod instanceof model.Terminal) {
        return firstForTerminal(prod);
    }
    else if (helpers.isSequenceProd(prod)) {
        return firstForSequence(prod);
    }
    else if (helpers.isBranchingProd(prod)) {
        return firstForBranching(prod);
    }
    else {
        throw Error("non exhaustive match");
    }
}
function firstForSequence(prod) {
    let firstSet = [];
    const seq = prod.definition;
    let nextSubProdIdx = 0;
    let hasInnerProdsRemaining = seq.length > nextSubProdIdx;
    let currSubProd;
    // so we enter the loop at least once (if the definition is not empty
    let isLastInnerProdOptional = true;
    // scan a sequence until it's end or until we have found a NONE optional production in it
    while (hasInnerProdsRemaining && isLastInnerProdOptional) {
        currSubProd = seq[nextSubProdIdx];
        isLastInnerProdOptional = helpers.isOptionalProd(currSubProd);
        firstSet = firstSet.concat(first(currSubProd));
        nextSubProdIdx = nextSubProdIdx + 1;
        hasInnerProdsRemaining = seq.length > nextSubProdIdx;
    }
    return uniq.default(firstSet);
}
function firstForBranching(prod) {
    const allAlternativesFirsts = map.default(prod.definition, (innerProd) => {
        return first(innerProd);
    });
    return uniq.default(flatten.default(allAlternativesFirsts));
}
function firstForTerminal(terminal) {
    return [terminal.terminalType];
}

exports.first = first;
exports.firstForBranching = firstForBranching;
exports.firstForSequence = firstForSequence;
exports.firstForTerminal = firstForTerminal;
