import { every, includes, some } from "lodash-es";
import {
  AbstractProduction,
  Alternation,
  Alternative,
  NonTerminal,
  Option,
  Repetition,
  RepetitionMandatory,
  RepetitionMandatoryWithSeparator,
  RepetitionWithSeparator,
  Rule,
  Terminal,
} from "./model.js";
import type { IProduction, IProductionWithOccurrence } from "@chevrotain/types";

export function isSequenceProd(
  prod: IProduction,
): prod is { definition: IProduction[] } & IProduction {
  return (
    prod instanceof Alternative ||
    prod instanceof Option ||
    prod instanceof Repetition ||
    prod instanceof RepetitionMandatory ||
    prod instanceof RepetitionMandatoryWithSeparator ||
    prod instanceof RepetitionWithSeparator ||
    prod instanceof Terminal ||
    prod instanceof Rule
  );
}

export function isOptionalProd(
  prod: IProduction,
  alreadyVisited: NonTerminal[] = [],
): boolean {
  const isDirectlyOptional =
    prod instanceof Option ||
    prod instanceof Repetition ||
    prod instanceof RepetitionWithSeparator;
  if (isDirectlyOptional) {
    return true;
  }

  // note that this can cause infinite loop if one optional empty TOP production has a cyclic dependency with another
  // empty optional top rule
  // may be indirectly optional ((A?B?C?) | (D?E?F?))
  if (prod instanceof Alternation) {
    // for OR its enough for just one of the alternatives to be optional
    return some((<Alternation>prod).definition, (subProd: IProduction) => {
      return isOptionalProd(subProd, alreadyVisited);
    });
  } else if (prod instanceof NonTerminal && includes(alreadyVisited, prod)) {
    // avoiding stack overflow due to infinite recursion
    return false;
  } else if (prod instanceof AbstractProduction) {
    if (prod instanceof NonTerminal) {
      alreadyVisited.push(prod);
    }
    return every(
      (<AbstractProduction>prod).definition,
      (subProd: IProduction) => {
        return isOptionalProd(subProd, alreadyVisited);
      },
    );
  } else {
    return false;
  }
}

export function isBranchingProd(
  prod: IProduction,
): prod is { definition: IProduction[] } & IProduction {
  return prod instanceof Alternation;
}

export function getProductionDslName(prod: IProductionWithOccurrence): string {
  /* istanbul ignore else */
  if (prod instanceof NonTerminal) {
    return "SUBRULE";
  } else if (prod instanceof Option) {
    return "OPTION";
  } else if (prod instanceof Alternation) {
    return "OR";
  } else if (prod instanceof RepetitionMandatory) {
    return "AT_LEAST_ONE";
  } else if (prod instanceof RepetitionMandatoryWithSeparator) {
    return "AT_LEAST_ONE_SEP";
  } else if (prod instanceof RepetitionWithSeparator) {
    return "MANY_SEP";
  } else if (prod instanceof Repetition) {
    return "MANY";
  } else if (prod instanceof Terminal) {
    return "CONSUME";
    /* c8 ignore next 3 */
  } else {
    throw Error("non exhaustive match");
  }
}
