import { isLinkingError as a, isAstNode as u, isAstNodeDescription as g } from "../syntax-tree.js";
import { streamAst as m, streamReferences as p, findRootNode as f } from "../utils/ast-utils.js";
import { interruptAndCheck as _ } from "../utils/promise-utils.js";
import { DocumentState as h } from "../workspace/documents.js";
import { c as $ } from "../../../../../../../_virtual/cancellation.js";
const l = Symbol("ref_resolving");
class b {
  constructor(e) {
    this.reflection = e.shared.AstReflection, this.langiumDocuments = () => e.shared.workspace.LangiumDocuments, this.scopeProvider = e.references.ScopeProvider, this.astNodeLocator = e.workspace.AstNodeLocator;
  }
  async link(e, o = $.CancellationToken.None) {
    for (const r of m(e.parseResult.value))
      await _(o), p(r).forEach((t) => this.doLink(t, e));
  }
  doLink(e, o) {
    var r;
    const t = e.reference;
    if (t._ref === void 0) {
      t._ref = l;
      try {
        const n = this.getCandidate(e);
        if (a(n))
          t._ref = n;
        else if (t._nodeDescription = n, this.langiumDocuments().hasDocument(n.documentUri)) {
          const s = this.loadAstNode(n);
          t._ref = s ?? this.createLinkingError(e, n);
        } else
          t._ref = void 0;
      } catch (n) {
        console.error(`An error occurred while resolving reference to '${t.$refText}':`, n);
        const s = (r = n.message) !== null && r !== void 0 ? r : String(n);
        t._ref = Object.assign(Object.assign({}, e), { message: `An error occurred while resolving reference to '${t.$refText}': ${s}` });
      }
      o.references.push(t);
    }
  }
  unlink(e) {
    for (const o of e.references)
      delete o._ref, delete o._nodeDescription;
    e.references = [];
  }
  getCandidate(e) {
    const r = this.scopeProvider.getScope(e).getElement(e.reference.$refText);
    return r ?? this.createLinkingError(e);
  }
  buildReference(e, o, r, t) {
    const n = this, s = {
      $refNode: r,
      $refText: t,
      get ref() {
        var d;
        if (u(this._ref))
          return this._ref;
        if (g(this._nodeDescription)) {
          const i = n.loadAstNode(this._nodeDescription);
          this._ref = i ?? n.createLinkingError({ reference: s, container: e, property: o }, this._nodeDescription);
        } else if (this._ref === void 0) {
          this._ref = l;
          const i = f(e).$document, c = n.getLinkedNode({ reference: s, container: e, property: o });
          if (c.error && i && i.state < h.ComputedScopes)
            return this._ref = void 0;
          this._ref = (d = c.node) !== null && d !== void 0 ? d : c.error, this._nodeDescription = c.descr, i == null || i.references.push(this);
        } else if (this._ref === l)
          throw new Error(`Cyclic reference resolution detected: ${n.astNodeLocator.getAstNodePath(e)}/${o} (symbol '${t}')`);
        return u(this._ref) ? this._ref : void 0;
      },
      get $nodeDescription() {
        return this._nodeDescription;
      },
      get error() {
        return a(this._ref) ? this._ref : void 0;
      }
    };
    return s;
  }
  getLinkedNode(e) {
    var o;
    try {
      const r = this.getCandidate(e);
      if (a(r))
        return { error: r };
      const t = this.loadAstNode(r);
      return t ? { node: t, descr: r } : {
        descr: r,
        error: this.createLinkingError(e, r)
      };
    } catch (r) {
      console.error(`An error occurred while resolving reference to '${e.reference.$refText}':`, r);
      const t = (o = r.message) !== null && o !== void 0 ? o : String(r);
      return {
        error: Object.assign(Object.assign({}, e), { message: `An error occurred while resolving reference to '${e.reference.$refText}': ${t}` })
      };
    }
  }
  loadAstNode(e) {
    if (e.node)
      return e.node;
    const o = this.langiumDocuments().getDocument(e.documentUri);
    if (o)
      return this.astNodeLocator.getAstNode(o.parseResult.value, e.path);
  }
  createLinkingError(e, o) {
    const r = f(e.container).$document;
    r && r.state < h.ComputedScopes && console.warn(`Attempted reference resolution before document reached ComputedScopes state (${r.uri}).`);
    const t = this.reflection.getReferenceType(e);
    return Object.assign(Object.assign({}, e), { message: `Could not resolve reference to ${t} named '${e.reference.$refText}'.`, targetDescription: o });
  }
}
export {
  b as DefaultLinker
};
