import { assertUnreachable } from "./lib-utils-errors.js";
import { isTerminalRule, isInferredType, isAction, isParserRule, isInterface, isType, isRuleCall, isTerminalAlternatives, isTerminalGroup, isCharacterRange, isTerminalRuleCall, isNegatedToken, isUntilToken, isRegexToken, isWildcard, isAssignment, isSimpleType, isKeyword, isReturnType } from "./lib-languages-generated-ast.js";
import { isCompositeCstNode } from "./lib-syntax-tree.js";
import { streamAllContents, getContainerOfType } from "./lib-utils-ast-utils.js";
import { streamCst } from "./lib-utils-cst-utils.js";
import { escapeRegExp, isWhitespace } from "./lib-utils-regexp-utils.js";
function getEntryRule(grammar) {
  return grammar.rules.find((e) => isParserRule(e) && e.entry);
}
function getHiddenRules(grammar) {
  return grammar.rules.filter((e) => isTerminalRule(e) && e.hidden);
}
function getAllReachableRules(grammar, allTerminals) {
  const ruleNames = /* @__PURE__ */ new Set();
  const entryRule = getEntryRule(grammar);
  if (!entryRule) {
    return new Set(grammar.rules);
  }
  const topMostRules = [entryRule].concat(getHiddenRules(grammar));
  for (const rule of topMostRules) {
    ruleDfs(rule, ruleNames, allTerminals);
  }
  const rules = /* @__PURE__ */ new Set();
  for (const rule of grammar.rules) {
    if (ruleNames.has(rule.name) || isTerminalRule(rule) && rule.hidden) {
      rules.add(rule);
    }
  }
  return rules;
}
function ruleDfs(rule, visitedSet, allTerminals) {
  visitedSet.add(rule.name);
  streamAllContents(rule).forEach((node) => {
    if (isRuleCall(node) || allTerminals) {
      const refRule = node.rule.ref;
      if (refRule && !visitedSet.has(refRule.name)) {
        ruleDfs(refRule, visitedSet, allTerminals);
      }
    }
  });
}
function getCrossReferenceTerminal(crossRef) {
  if (crossRef.terminal) {
    return crossRef.terminal;
  } else if (crossRef.type.ref) {
    const nameAssigment = findNameAssignment(crossRef.type.ref);
    return nameAssigment === null || nameAssigment === void 0 ? void 0 : nameAssigment.terminal;
  }
  return void 0;
}
function isCommentTerminal(terminalRule) {
  return terminalRule.hidden && !isWhitespace(terminalRegex(terminalRule));
}
function findNodesForProperty(node, property) {
  if (!node || !property) {
    return [];
  }
  return findNodesForPropertyInternal(node, property, node.astNode, true);
}
function findNodeForProperty(node, property, index) {
  if (!node || !property) {
    return void 0;
  }
  const nodes = findNodesForPropertyInternal(node, property, node.astNode, true);
  if (nodes.length === 0) {
    return void 0;
  }
  if (index !== void 0) {
    index = Math.max(0, Math.min(index, nodes.length - 1));
  } else {
    index = 0;
  }
  return nodes[index];
}
function findNodesForPropertyInternal(node, property, element, first) {
  if (!first) {
    const nodeFeature = getContainerOfType(node.grammarSource, isAssignment);
    if (nodeFeature && nodeFeature.feature === property) {
      return [node];
    }
  }
  if (isCompositeCstNode(node) && node.astNode === element) {
    return node.content.flatMap((e) => findNodesForPropertyInternal(e, property, element, false));
  }
  return [];
}
function findNodeForKeyword(node, keyword, index) {
  if (!node) {
    return void 0;
  }
  const nodes = findNodesForKeywordInternal(node, keyword, node === null || node === void 0 ? void 0 : node.astNode);
  if (nodes.length === 0) {
    return void 0;
  }
  if (index !== void 0) {
    index = Math.max(0, Math.min(index, nodes.length - 1));
  } else {
    index = 0;
  }
  return nodes[index];
}
function findNodesForKeywordInternal(node, keyword, element) {
  if (node.astNode !== element) {
    return [];
  }
  if (isKeyword(node.grammarSource) && node.grammarSource.value === keyword) {
    return [node];
  }
  const treeIterator = streamCst(node).iterator();
  let result;
  const keywordNodes = [];
  do {
    result = treeIterator.next();
    if (!result.done) {
      const childNode = result.value;
      if (childNode.astNode === element) {
        if (isKeyword(childNode.grammarSource) && childNode.grammarSource.value === keyword) {
          keywordNodes.push(childNode);
        }
      } else {
        treeIterator.prune();
      }
    }
  } while (!result.done);
  return keywordNodes;
}
function findAssignment(cstNode) {
  var _a;
  const astNode = cstNode.astNode;
  while (astNode === ((_a = cstNode.container) === null || _a === void 0 ? void 0 : _a.astNode)) {
    const assignment = getContainerOfType(cstNode.grammarSource, isAssignment);
    if (assignment) {
      return assignment;
    }
    cstNode = cstNode.container;
  }
  return void 0;
}
function findNameAssignment(type) {
  let startNode = type;
  if (isInferredType(startNode)) {
    if (isAction(startNode.$container)) {
      startNode = startNode.$container.$container;
    } else if (isParserRule(startNode.$container)) {
      startNode = startNode.$container;
    } else {
      assertUnreachable(startNode.$container);
    }
  }
  return findNameAssignmentInternal(type, startNode, /* @__PURE__ */ new Map());
}
function findNameAssignmentInternal(type, startNode, cache) {
  var _a;
  function go(node, refType) {
    let childAssignment = void 0;
    const parentAssignment = getContainerOfType(node, isAssignment);
    if (!parentAssignment) {
      childAssignment = findNameAssignmentInternal(refType, refType, cache);
    }
    cache.set(type, childAssignment);
    return childAssignment;
  }
  if (cache.has(type)) {
    return cache.get(type);
  }
  cache.set(type, void 0);
  for (const node of streamAllContents(startNode)) {
    if (isAssignment(node) && node.feature.toLowerCase() === "name") {
      cache.set(type, node);
      return node;
    } else if (isRuleCall(node) && isParserRule(node.rule.ref)) {
      return go(node, node.rule.ref);
    } else if (isSimpleType(node) && ((_a = node.typeRef) === null || _a === void 0 ? void 0 : _a.ref)) {
      return go(node, node.typeRef.ref);
    }
  }
  return void 0;
}
function isDataTypeRule(rule) {
  return isDataTypeRuleInternal(rule, /* @__PURE__ */ new Set());
}
function isDataTypeRuleInternal(rule, visited) {
  if (visited.has(rule)) {
    return true;
  } else {
    visited.add(rule);
  }
  for (const node of streamAllContents(rule)) {
    if (isRuleCall(node)) {
      if (!node.rule.ref) {
        return false;
      }
      if (isParserRule(node.rule.ref) && !isDataTypeRuleInternal(node.rule.ref, visited)) {
        return false;
      }
    } else if (isAssignment(node)) {
      return false;
    } else if (isAction(node)) {
      return false;
    }
  }
  return Boolean(rule.definition);
}
function getExplicitRuleType(rule) {
  if (rule.inferredType) {
    return rule.inferredType.name;
  } else if (rule.dataType) {
    return rule.dataType;
  } else if (rule.returnType) {
    const refType = rule.returnType.ref;
    if (refType) {
      if (isParserRule(refType)) {
        return refType.name;
      } else if (isInterface(refType) || isType(refType)) {
        return refType.name;
      }
    }
  }
  return void 0;
}
function getTypeName(type) {
  var _a;
  if (isParserRule(type)) {
    return isDataTypeRule(type) ? type.name : (_a = getExplicitRuleType(type)) !== null && _a !== void 0 ? _a : type.name;
  } else if (isInterface(type) || isType(type) || isReturnType(type)) {
    return type.name;
  } else if (isAction(type)) {
    const actionType = getActionType(type);
    if (actionType) {
      return actionType;
    }
  } else if (isInferredType(type)) {
    return type.name;
  }
  throw new Error("Cannot get name of Unknown Type");
}
function getActionType(action) {
  var _a;
  if (action.inferredType) {
    return action.inferredType.name;
  } else if ((_a = action.type) === null || _a === void 0 ? void 0 : _a.ref) {
    return getTypeName(action.type.ref);
  }
  return void 0;
}
function getRuleType(rule) {
  var _a, _b, _c;
  if (isTerminalRule(rule)) {
    return (_b = (_a = rule.type) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : "string";
  } else {
    return (_c = getExplicitRuleType(rule)) !== null && _c !== void 0 ? _c : rule.name;
  }
}
function terminalRegex(terminalRule) {
  const flags = {
    s: false,
    i: false,
    u: false
  };
  const source = abstractElementToRegex(terminalRule.definition, flags);
  const flagText = Object.entries(flags).filter(([, value]) => value).map(([name]) => name).join("");
  return new RegExp(source, flagText);
}
const WILDCARD = /[\s\S]/.source;
function abstractElementToRegex(element, flags) {
  if (isTerminalAlternatives(element)) {
    return terminalAlternativesToRegex(element);
  } else if (isTerminalGroup(element)) {
    return terminalGroupToRegex(element);
  } else if (isCharacterRange(element)) {
    return characterRangeToRegex(element);
  } else if (isTerminalRuleCall(element)) {
    const rule = element.rule.ref;
    if (!rule) {
      throw new Error("Missing rule reference.");
    }
    return withCardinality(abstractElementToRegex(rule.definition), {
      cardinality: element.cardinality,
      lookahead: element.lookahead
    });
  } else if (isNegatedToken(element)) {
    return negateTokenToRegex(element);
  } else if (isUntilToken(element)) {
    return untilTokenToRegex(element);
  } else if (isRegexToken(element)) {
    const lastSlash = element.regex.lastIndexOf("/");
    const source = element.regex.substring(1, lastSlash);
    const regexFlags = element.regex.substring(lastSlash + 1);
    if (flags) {
      flags.i = regexFlags.includes("i");
      flags.s = regexFlags.includes("s");
      flags.u = regexFlags.includes("u");
    }
    return withCardinality(source, {
      cardinality: element.cardinality,
      lookahead: element.lookahead,
      wrap: false
    });
  } else if (isWildcard(element)) {
    return withCardinality(WILDCARD, {
      cardinality: element.cardinality,
      lookahead: element.lookahead
    });
  } else {
    throw new Error(`Invalid terminal element: ${element === null || element === void 0 ? void 0 : element.$type}`);
  }
}
function terminalAlternativesToRegex(alternatives) {
  return withCardinality(alternatives.elements.map((e) => abstractElementToRegex(e)).join("|"), {
    cardinality: alternatives.cardinality,
    lookahead: alternatives.lookahead
  });
}
function terminalGroupToRegex(group) {
  return withCardinality(group.elements.map((e) => abstractElementToRegex(e)).join(""), {
    cardinality: group.cardinality,
    lookahead: group.lookahead
  });
}
function untilTokenToRegex(until) {
  return withCardinality(`${WILDCARD}*?${abstractElementToRegex(until.terminal)}`, {
    cardinality: until.cardinality,
    lookahead: until.lookahead
  });
}
function negateTokenToRegex(negate) {
  return withCardinality(`(?!${abstractElementToRegex(negate.terminal)})${WILDCARD}*?`, {
    cardinality: negate.cardinality,
    lookahead: negate.lookahead
  });
}
function characterRangeToRegex(range) {
  if (range.right) {
    return withCardinality(`[${keywordToRegex(range.left)}-${keywordToRegex(range.right)}]`, {
      cardinality: range.cardinality,
      lookahead: range.lookahead,
      wrap: false
    });
  }
  return withCardinality(keywordToRegex(range.left), {
    cardinality: range.cardinality,
    lookahead: range.lookahead,
    wrap: false
  });
}
function keywordToRegex(keyword) {
  return escapeRegExp(keyword.value);
}
function withCardinality(regex, options) {
  var _a;
  if (options.wrap !== false || options.lookahead) {
    regex = `(${(_a = options.lookahead) !== null && _a !== void 0 ? _a : ""}${regex})`;
  }
  if (options.cardinality) {
    return `${regex}${options.cardinality}`;
  }
  return regex;
}
export {
  findAssignment,
  findNameAssignment,
  findNodeForKeyword,
  findNodeForProperty,
  findNodesForKeywordInternal,
  findNodesForProperty,
  getActionType,
  getAllReachableRules,
  getCrossReferenceTerminal,
  getEntryRule,
  getExplicitRuleType,
  getHiddenRules,
  getRuleType,
  getTypeName,
  isCommentTerminal,
  isDataTypeRule,
  terminalRegex
};
//# sourceMappingURL=lib-utils-grammar-utils.js.map
