"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const syntaxTree = require("./lib-syntax-tree.cjs");
const astUtils = require("./lib-utils-ast-utils.cjs");
const promiseUtils = require("./lib-utils-promise-utils.cjs");
const documents = require("./lib-workspace-documents.cjs");
const cancellation = require("../../_virtual/cancellation.cjs");
const ref_resolving = Symbol("ref_resolving");
class DefaultLinker {
  constructor(services) {
    this.reflection = services.shared.AstReflection;
    this.langiumDocuments = () => services.shared.workspace.LangiumDocuments;
    this.scopeProvider = services.references.ScopeProvider;
    this.astNodeLocator = services.workspace.AstNodeLocator;
  }
  async link(document, cancelToken = cancellation.cancellationExports.CancellationToken.None) {
    for (const node of astUtils.streamAst(document.parseResult.value)) {
      await promiseUtils.interruptAndCheck(cancelToken);
      astUtils.streamReferences(node).forEach((ref) => this.doLink(ref, document));
    }
  }
  doLink(refInfo, document) {
    var _a;
    const ref = refInfo.reference;
    if (ref._ref === void 0) {
      ref._ref = ref_resolving;
      try {
        const description = this.getCandidate(refInfo);
        if (syntaxTree.isLinkingError(description)) {
          ref._ref = description;
        } else {
          ref._nodeDescription = description;
          if (this.langiumDocuments().hasDocument(description.documentUri)) {
            const linkedNode = this.loadAstNode(description);
            ref._ref = linkedNode !== null && linkedNode !== void 0 ? linkedNode : this.createLinkingError(refInfo, description);
          } else {
            ref._ref = void 0;
          }
        }
      } catch (err) {
        console.error(`An error occurred while resolving reference to '${ref.$refText}':`, err);
        const errorMessage = (_a = err.message) !== null && _a !== void 0 ? _a : String(err);
        ref._ref = Object.assign(Object.assign({}, refInfo), { message: `An error occurred while resolving reference to '${ref.$refText}': ${errorMessage}` });
      }
      document.references.push(ref);
    }
  }
  unlink(document) {
    for (const ref of document.references) {
      delete ref._ref;
      delete ref._nodeDescription;
    }
    document.references = [];
  }
  getCandidate(refInfo) {
    const scope = this.scopeProvider.getScope(refInfo);
    const description = scope.getElement(refInfo.reference.$refText);
    return description !== null && description !== void 0 ? description : this.createLinkingError(refInfo);
  }
  buildReference(node, property, refNode, refText) {
    const linker = this;
    const reference = {
      $refNode: refNode,
      $refText: refText,
      get ref() {
        var _a;
        if (syntaxTree.isAstNode(this._ref)) {
          return this._ref;
        } else if (syntaxTree.isAstNodeDescription(this._nodeDescription)) {
          const linkedNode = linker.loadAstNode(this._nodeDescription);
          this._ref = linkedNode !== null && linkedNode !== void 0 ? linkedNode : linker.createLinkingError({ reference, container: node, property }, this._nodeDescription);
        } else if (this._ref === void 0) {
          this._ref = ref_resolving;
          const document = astUtils.findRootNode(node).$document;
          const refData = linker.getLinkedNode({ reference, container: node, property });
          if (refData.error && document && document.state < documents.DocumentState.ComputedScopes) {
            return this._ref = void 0;
          }
          this._ref = (_a = refData.node) !== null && _a !== void 0 ? _a : refData.error;
          this._nodeDescription = refData.descr;
          document === null || document === void 0 ? void 0 : document.references.push(this);
        } else if (this._ref === ref_resolving) {
          throw new Error(`Cyclic reference resolution detected: ${linker.astNodeLocator.getAstNodePath(node)}/${property} (symbol '${refText}')`);
        }
        return syntaxTree.isAstNode(this._ref) ? this._ref : void 0;
      },
      get $nodeDescription() {
        return this._nodeDescription;
      },
      get error() {
        return syntaxTree.isLinkingError(this._ref) ? this._ref : void 0;
      }
    };
    return reference;
  }
  getLinkedNode(refInfo) {
    var _a;
    try {
      const description = this.getCandidate(refInfo);
      if (syntaxTree.isLinkingError(description)) {
        return { error: description };
      }
      const linkedNode = this.loadAstNode(description);
      if (linkedNode) {
        return { node: linkedNode, descr: description };
      } else {
        return {
          descr: description,
          error: this.createLinkingError(refInfo, description)
        };
      }
    } catch (err) {
      console.error(`An error occurred while resolving reference to '${refInfo.reference.$refText}':`, err);
      const errorMessage = (_a = err.message) !== null && _a !== void 0 ? _a : String(err);
      return {
        error: Object.assign(Object.assign({}, refInfo), { message: `An error occurred while resolving reference to '${refInfo.reference.$refText}': ${errorMessage}` })
      };
    }
  }
  loadAstNode(nodeDescription) {
    if (nodeDescription.node) {
      return nodeDescription.node;
    }
    const doc = this.langiumDocuments().getDocument(nodeDescription.documentUri);
    if (!doc) {
      return void 0;
    }
    return this.astNodeLocator.getAstNode(doc.parseResult.value, nodeDescription.path);
  }
  createLinkingError(refInfo, targetDescription) {
    const document = astUtils.findRootNode(refInfo.container).$document;
    if (document && document.state < documents.DocumentState.ComputedScopes) {
      console.warn(`Attempted reference resolution before document reached ComputedScopes state (${document.uri}).`);
    }
    const referenceType = this.reflection.getReferenceType(refInfo);
    return Object.assign(Object.assign({}, refInfo), { message: `Could not resolve reference to ${referenceType} named '${refInfo.reference.$refText}'.`, targetDescription });
  }
}
exports.DefaultLinker = DefaultLinker;
//# sourceMappingURL=lib-references-linker.cjs.map
