"use strict";

var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
exports.__esModule = true;
exports.mutationReplacer = exports.default = void 0;
var _ramda = require("ramda");
var _ramdaAdjunct = require("ramda-adjunct");
var _apidomCore = require("@swagger-api/apidom-core");
var _apidomError = require("@swagger-api/apidom-error");
var _modern = require("@swagger-api/apidom-json-pointer/modern");
var _apidomNsOpenapi = require("@swagger-api/apidom-ns-openapi-3-2");
var _$anchor = require("./selectors/$anchor.cjs");
var _uri = require("./selectors/uri.cjs");
var _MaximumDereferenceDepthError = _interopRequireDefault(require("../../../errors/MaximumDereferenceDepthError.cjs"));
var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs"));
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
var _index = _interopRequireDefault(require("../../../parse/index.cjs"));
var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
var _File = _interopRequireDefault(require("../../../File.cjs"));
var _util = require("./util.cjs");
var _util2 = require("../../util.cjs");
var _EvaluationJsonSchemaUriError = _interopRequireDefault(require("../../../errors/EvaluationJsonSchemaUriError.cjs"));
// @ts-ignore
const visitAsync = _apidomCore.visit[Symbol.for('nodejs.util.promisify.custom')];

// initialize element identity manager
const identityManager = new _apidomCore.IdentityManager();

/**
 * Checks if a URI is absolute (has a scheme).
 * @param uri - The URI to check
 * @returns true if the URI is absolute, false otherwise
 */
const isAbsoluteURI = uri => {
  try {
    const parsedUrl = new URL(uri);
    // URL constructor succeeds only for absolute URLs
    return !!parsedUrl.protocol;
  } catch {
    // If URL parsing fails, it's a relative URI
    return false;
  }
};

/**
 * Custom mutation replacer.
 * @public
 */
const mutationReplacer = (newElement, oldElement, key, parent) => {
  if ((0, _apidomCore.isMemberElement)(parent)) {
    parent.value = newElement; // eslint-disable-line no-param-reassign
  } else if (Array.isArray(parent)) {
    parent[key] = newElement; // eslint-disable-line no-param-reassign
  }
};

/**
 * @public
 */
exports.mutationReplacer = mutationReplacer;
/**
 * @public
 */
class OpenAPI3_2DereferenceVisitor {
  indirections;
  namespace;
  reference;
  options;
  ancestors;
  refractCache;
  allOfDiscriminatorMapping;
  $selfValue;
  constructor({
    reference,
    namespace,
    options,
    indirections = [],
    ancestors = new _util2.AncestorLineage(),
    refractCache = new Map(),
    allOfDiscriminatorMapping = new Map(),
    $selfValue = undefined
  }) {
    this.indirections = indirections;
    this.namespace = namespace;
    this.reference = reference;
    this.options = options;
    this.ancestors = new _util2.AncestorLineage(...ancestors);
    this.refractCache = refractCache;
    this.allOfDiscriminatorMapping = allOfDiscriminatorMapping;
    this.$selfValue = $selfValue;
  }
  handleDereferenceError(error, refEl, directAncestors) {
    var _this$options$derefer;
    if ((_this$options$derefer = this.options.dereference.dereferenceOpts) != null && _this$options$derefer.continueOnError) {
      var _this$options$derefer3;
      if (directAncestors) {
        const ancestorWithRef = [...directAncestors].find(ancestor => (0, _apidomCore.isObjectElement)(ancestor) && ancestor.getMetaProperty('ref-referencing-element'));
        if (ancestorWithRef) {
          var _this$options$derefer2;
          (_this$options$derefer2 = this.options.dereference.dereferenceOpts) == null || _this$options$derefer2.errors.push({
            error,
            refEl: ancestorWithRef.getMetaProperty('ref-referencing-element')
          });
        }
      }
      (_this$options$derefer3 = this.options.dereference.dereferenceOpts) == null || _this$options$derefer3.errors.push({
        error,
        refEl
      });
      return undefined;
    }
    throw error;
  }
  getNestedVisitorOptions(referencingElement) {
    var _this$options$derefer4, _this$options$derefer5;
    return {
      ...this.options,
      resolve: {
        ...this.options.resolve,
        external: (_this$options$derefer4 = this.options.dereference) != null && (_this$options$derefer4 = _this$options$derefer4.dereferenceOpts) != null && _this$options$derefer4.skipNestedExternal && (0, _apidomCore.toValue)(referencingElement.get('$ref')).startsWith('#') ? false : this.options.resolve.external
      },
      dereference: {
        ...this.options.dereference,
        dereferenceOpts: {
          ...((_this$options$derefer5 = this.options.dereference) == null ? void 0 : _this$options$derefer5.dereferenceOpts),
          continueOnError: false
        }
      }
    };
  }
  toBaseURI(uri) {
    return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
  }
  async toReference(uri) {
    // detect maximum depth of resolution
    if (this.reference.depth >= this.options.resolve.maxDepth) {
      throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`);
    }
    const baseURI = this.toBaseURI(uri);
    const {
      refSet
    } = this.reference;

    // we've already processed this Reference in past
    if (refSet.has(baseURI)) {
      return refSet.find((0, _ramda.propEq)(baseURI, 'uri'));
    }
    const parseResult = await (0, _index.default)(url.unsanitize(baseURI), {
      ...this.options,
      parse: {
        ...this.options.parse,
        mediaType: 'text/plain'
      }
    });

    // register new mutable reference with a refSet
    const mutableReference = new _Reference.default({
      uri: baseURI,
      value: (0, _apidomCore.cloneDeep)(parseResult),
      depth: this.reference.depth + 1
    });
    refSet.add(mutableReference);
    if (this.options.dereference.immutable) {
      // register new immutable reference with a refSet
      const immutableReference = new _Reference.default({
        uri: `immutable://${baseURI}`,
        value: parseResult,
        depth: this.reference.depth + 1
      });
      refSet.add(immutableReference);
    }
    return mutableReference;
  }
  toAncestorLineage(ancestors) {
    /**
     * Compute full ancestors lineage.
     * Ancestors are flatten to unwrap all Element instances.
     */
    const directAncestors = new Set(ancestors.filter(_apidomCore.isElement));
    const ancestorsLineage = new _util2.AncestorLineage(...this.ancestors, directAncestors);
    return [ancestorsLineage, directAncestors];
  }

  /**
   * Extract $self value from a Reference's root OpenAPI 3.2 document.
   * Returns the $self value if present and valid, otherwise undefined.
   */
  extractSelfFromReference(reference) {
    try {
      // Get the root element from the reference
      const rootElement = reference.value.result;

      // Find the OpenApi3_2Element in the parse result
      let openApiElement;
      if (rootElement instanceof _apidomCore.Element) {
        // Check if root is directly an OpenApi3_2Element
        if (rootElement.element === 'openapi-3-2') {
          openApiElement = rootElement;
        } else {
          // Try to find OpenApi3_2Element in the result (e.g., in ParseResult)
          const found = (0, _apidomCore.find)(element => element.element === 'openapi-3-2', rootElement);
          if (found) {
            openApiElement = found;
          }
        }
      }
      if (openApiElement && (0, _apidomNsOpenapi.isOpenApi3_2Element)(openApiElement) && (0, _apidomCore.isStringElement)(openApiElement.$self)) {
        const $self = (0, _apidomCore.toValue)(openApiElement.$self);
        if ($self) {
          // If $self is absolute, use it directly
          if (isAbsoluteURI($self)) {
            return url.sanitize(url.stripHash($self));
          }
          // If $self is relative, resolve it against the reference's URI
          return url.resolve(reference.uri, url.sanitize(url.stripHash($self)));
        }
      }
    } catch {
      // If extraction fails, return undefined to fall back to reference.uri
    }
    return undefined;
  }
  OpenApi3_2Element = {
    enter: openApi3_2Element => {
      // Extract $self field if present and use it as base URI for reference resolution
      if ((0, _apidomCore.isStringElement)(openApi3_2Element.$self)) {
        const $self = (0, _apidomCore.toValue)(openApi3_2Element.$self);
        if ($self) {
          // If $self is absolute, use it directly
          if (isAbsoluteURI($self)) {
            this.$selfValue = url.sanitize(url.stripHash($self));
          } else {
            // If $self is relative, resolve it against the retrieval URI first
            this.$selfValue = url.resolve(this.reference.uri, url.sanitize(url.stripHash($self)));
          }
        }
      }
      return undefined;
    },
    leave: (openApi3_2Element, key, parent, path, ancestors, link) => {
      var _this$options$derefer6;
      if (!((_this$options$derefer6 = this.options.dereference.strategyOpts['openapi-3-2']) != null && _this$options$derefer6.dereferenceDiscriminatorMapping)) {
        return undefined;
      }
      const openApi3_2ElementCopy = (0, _apidomCore.cloneShallow)(openApi3_2Element);
      openApi3_2ElementCopy.setMetaProperty('allOfDiscriminatorMapping', Object.fromEntries(this.allOfDiscriminatorMapping));
      link.replaceWith(openApi3_2ElementCopy, mutationReplacer);
      return !parent ? openApi3_2ElementCopy : undefined;
    }
  };
  async ReferenceElement(referencingElement, key, parent, path, ancestors, link) {
    var _this$options$derefer9, _this$options$derefer0;
    // skip current referencing element as it's already been access
    if (this.indirections.includes(referencingElement)) {
      return false;
    }
    const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
    const $refValue = (0, _apidomCore.toValue)(referencingElement.$ref);
    const retrievalURI = this.toBaseURI($refValue);
    const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
    const isExternalReference = !isInternalReference;

    // ignore resolving internal Reference Objects
    if (!this.options.resolve.internal && isInternalReference) {
      // skip traversing this reference element and all it's child elements
      return false;
    }
    // ignore resolving external Reference Objects
    if (!this.options.resolve.external && isExternalReference) {
      // skip traversing this reference element and all it's child elements
      return false;
    }
    let reference;
    try {
      reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
    } catch (error) {
      return this.handleDereferenceError(error, referencingElement, directAncestors);
    }
    const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref));
    this.indirections.push(referencingElement);
    const jsonPointer = _modern.URIFragmentIdentifier.fromURIReference($refBaseURI);
    let referencedElement;
    try {
      // possibly non-semantic fragment
      referencedElement = (0, _modern.evaluate)(reference.value.result, jsonPointer);
    } catch (error) {
      this.indirections.pop();
      return this.handleDereferenceError(error, referencingElement, directAncestors);
    }
    referencedElement.id = identityManager.identify(referencedElement);

    // applying semantics to a fragment
    if ((0, _apidomCore.isPrimitiveElement)(referencedElement)) {
      const referencedElementType = (0, _apidomCore.toValue)(referencingElement.meta.get('referenced-element'));
      const cacheKey = `${referencedElementType}-${(0, _apidomCore.toValue)(identityManager.identify(referencedElement))}`;
      if (this.refractCache.has(cacheKey)) {
        referencedElement = this.refractCache.get(cacheKey);
      } else if ((0, _apidomNsOpenapi.isReferenceLikeElement)(referencedElement)) {
        // handling indirect references
        referencedElement = _apidomNsOpenapi.ReferenceElement.refract(referencedElement);
        referencedElement.setMetaProperty('referenced-element', referencedElementType);
        this.refractCache.set(cacheKey, referencedElement);
      } else {
        // handling direct references
        const ElementClass = this.namespace.getElementClass(referencedElementType);
        referencedElement = ElementClass.refract(referencedElement);
        this.refractCache.set(cacheKey, referencedElement);
      }
    }

    // detect direct or indirect reference
    if (referencingElement === referencedElement) {
      const error = new _apidomError.ApiDOMError('Recursive Reference Object detected');
      this.indirections.pop();
      return this.handleDereferenceError(error, referencingElement, directAncestors);
    }

    // detect maximum depth of dereferencing
    if (this.indirections.length > this.options.dereference.maxDepth) {
      const error = new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
      this.indirections.pop();
      return this.handleDereferenceError(error, referencingElement, directAncestors);
    }

    // detect second deep dive into the same fragment and avoid it
    if (ancestorsLineage.includes(referencedElement)) {
      reference.refSet.circular = true;
      if (this.options.dereference.circular === 'error') {
        const error = new _apidomError.ApiDOMError('Circular reference detected');
        this.indirections.pop();
        return this.handleDereferenceError(error, referencingElement, directAncestors);
      }
      if (this.options.dereference.circular === 'replace') {
        var _this$options$derefer7, _this$options$derefer8;
        const refElement = new _apidomCore.RefElement(referencedElement.id, {
          type: 'reference',
          uri: reference.uri,
          $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
        });
        const replacer = (_this$options$derefer7 = (_this$options$derefer8 = this.options.dereference.strategyOpts['openapi-3-2']) == null ? void 0 : _this$options$derefer8.circularReplacer) != null ? _this$options$derefer7 : this.options.dereference.circularReplacer;
        const replacement = replacer(refElement);
        link.replaceWith(replacement, mutationReplacer);
        return !parent ? replacement : false;
      }
    }

    /**
     * Dive deep into the fragment.
     *
     * Cases to consider:
     *  1. We're crossing document boundary
     *  2. Fragment is from non-root document
     *  3. Fragment is a Reference Object. We need to follow it to get the eventual value
     *  4. We are dereferencing the fragment lazily/eagerly depending on circular mode
     */
    const isNonRootDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
    const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
    if ((isExternalReference || isNonRootDocument || (0, _apidomNsOpenapi.isReferenceElement)(referencedElement) || shouldDetectCircular || (_this$options$derefer9 = this.options.dereference.dereferenceOpts) != null && _this$options$derefer9.continueOnError) && !ancestorsLineage.includesCycle(referencedElement)) {
      // append referencing reference to ancestors lineage
      directAncestors.add(referencingElement);

      // Extract $self from target document for external references
      const targetSelfValue = isExternalReference || isNonRootDocument ? this.extractSelfFromReference(reference) : this.$selfValue;
      const visitor = new OpenAPI3_2DereferenceVisitor({
        reference,
        namespace: this.namespace,
        indirections: [...this.indirections],
        options: this.getNestedVisitorOptions(referencingElement),
        refractCache: this.refractCache,
        ancestors: ancestorsLineage,
        allOfDiscriminatorMapping: this.allOfDiscriminatorMapping,
        $selfValue: targetSelfValue
      });
      try {
        referencedElement = await visitAsync(referencedElement, visitor, {
          keyMap: _apidomNsOpenapi.keyMap,
          nodeTypeGetter: _apidomNsOpenapi.getNodeType
        });
      } catch (error) {
        this.indirections.pop();
        return this.handleDereferenceError(error, referencingElement, directAncestors);
      }

      // remove referencing reference from ancestors lineage
      directAncestors.delete(referencingElement);
    }
    this.indirections.pop();

    /**
     * Creating a new version of referenced element to avoid modifying the original one.
     */
    const mergedElement = (0, _apidomCore.cloneShallow)(referencedElement);
    // assign unique id to merged element
    mergedElement.setMetaProperty('id', identityManager.generateId());
    // annotate fragment with info about original Reference element
    mergedElement.setMetaProperty('ref-fields', {
      $ref: (0, _apidomCore.toValue)(referencingElement.$ref),
      // @ts-ignore
      description: (0, _apidomCore.toValue)(referencingElement.description),
      // @ts-ignore
      summary: (0, _apidomCore.toValue)(referencingElement.summary)
    });
    // annotate fragment with info about origin (use $self if available)
    const refOriginURI = isExternalReference || isNonRootDocument ? this.extractSelfFromReference(reference) || reference.uri : this.$selfValue || reference.uri;
    mergedElement.setMetaProperty('ref-origin', refOriginURI);
    // annotate fragment with info about referencing element
    mergedElement.setMetaProperty('ref-referencing-element-id', (0, _apidomCore.cloneDeep)(identityManager.identify(referencingElement)));
    if ((_this$options$derefer0 = this.options.dereference.dereferenceOpts) != null && _this$options$derefer0.continueOnError) {
      mergedElement.setMetaProperty('ref-referencing-element', referencingElement);
    }

    // override description and summary (outer has higher priority then inner)
    if ((0, _apidomCore.isObjectElement)(referencedElement) && (0, _apidomCore.isObjectElement)(mergedElement)) {
      if (referencingElement.hasKey('description') && 'description' in referencedElement) {
        mergedElement.remove('description');
        mergedElement.set('description', referencingElement.get('description'));
      }
      if (referencingElement.hasKey('summary') && 'summary' in referencedElement) {
        mergedElement.remove('summary');
        mergedElement.set('summary', referencingElement.get('summary'));
      }
    }

    /**
     * Transclude referencing element with merged referenced element.
     */
    link.replaceWith(mergedElement, mutationReplacer);

    /**
     * We're at the root of the tree, so we're just replacing the entire tree.
     */
    return !parent ? mergedElement : false;
  }
  async PathItemElement(referencingElement, key, parent, path, ancestors, link) {
    var _this$options$derefer11;
    // ignore PathItemElement without $ref field
    if (!(0, _apidomCore.isStringElement)(referencingElement.$ref)) {
      return undefined;
    }

    // skip current referencing element as it's already been access
    if (this.indirections.includes(referencingElement)) {
      return false;
    }
    const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
    const $refValue = (0, _apidomCore.toValue)(referencingElement.$ref);
    const retrievalURI = this.toBaseURI($refValue);
    const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
    const isExternalReference = !isInternalReference;

    // ignore resolving external Path Item Objects
    if (!this.options.resolve.internal && isInternalReference) {
      // skip traversing this Path Item element but traverse all it's child elements
      return undefined;
    }
    // ignore resolving external Path Item Objects
    if (!this.options.resolve.external && isExternalReference) {
      // skip traversing this Path Item element but traverse all it's child elements
      return undefined;
    }
    let reference;
    try {
      reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
    } catch (error) {
      return this.handleDereferenceError(error, referencingElement, directAncestors);
    }
    const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref));
    this.indirections.push(referencingElement);
    const jsonPointer = _modern.URIFragmentIdentifier.fromURIReference($refBaseURI);
    let referencedElement;
    try {
      // possibly non-semantic referenced element
      referencedElement = (0, _modern.evaluate)(reference.value.result, jsonPointer);
    } catch (error) {
      this.indirections.pop();
      return this.handleDereferenceError(error, referencingElement, directAncestors);
    }
    referencedElement.id = identityManager.identify(referencedElement);

    /**
     * Applying semantics to a referenced element if semantics are missing.
     */
    if ((0, _apidomCore.isPrimitiveElement)(referencedElement)) {
      const cacheKey = `path-item-${(0, _apidomCore.toValue)(identityManager.identify(referencedElement))}`;
      if (this.refractCache.has(cacheKey)) {
        referencedElement = this.refractCache.get(cacheKey);
      } else {
        referencedElement = _apidomNsOpenapi.PathItemElement.refract(referencedElement);
        this.refractCache.set(cacheKey, referencedElement);
      }
    }

    // detect direct or indirect reference
    if (referencingElement === referencedElement) {
      const error = new _apidomError.ApiDOMError('Recursive Path Item Object reference detected');
      this.indirections.pop();
      return this.handleDereferenceError(error, referencingElement, directAncestors);
    }

    // detect maximum depth of dereferencing
    if (this.indirections.length > this.options.dereference.maxDepth) {
      const error = new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
      this.indirections.pop();
      return this.handleDereferenceError(error, referencingElement, directAncestors);
    }

    // detect second deep dive into the same fragment and avoid it
    if (ancestorsLineage.includes(referencedElement)) {
      reference.refSet.circular = true;
      if (this.options.dereference.circular === 'error') {
        const error = new _apidomError.ApiDOMError('Circular reference detected');
        this.indirections.pop();
        return this.handleDereferenceError(error, referencingElement, directAncestors);
      }
      if (this.options.dereference.circular === 'replace') {
        var _this$options$derefer1, _this$options$derefer10;
        const refElement = new _apidomCore.RefElement(referencedElement.id, {
          type: 'path-item',
          uri: reference.uri,
          $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
        });
        const replacer = (_this$options$derefer1 = (_this$options$derefer10 = this.options.dereference.strategyOpts['openapi-3-2']) == null ? void 0 : _this$options$derefer10.circularReplacer) != null ? _this$options$derefer1 : this.options.dereference.circularReplacer;
        const replacement = replacer(refElement);
        link.replaceWith(replacement, mutationReplacer);
        return !parent ? replacement : false;
      }
    }

    /**
     * Dive deep into the fragment.
     *
     * Cases to consider:
     *  1. We're crossing document boundary
     *  2. Fragment is from non-root document
     *  3. Fragment is a Path Item Object with $ref field. We need to follow it to get the eventual value
     *  4. We are dereferencing the fragment lazily/eagerly depending on circular mode
     */
    const isNonRootDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
    const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
    if ((isExternalReference || isNonRootDocument || (0, _apidomNsOpenapi.isPathItemElement)(referencedElement) && (0, _apidomCore.isStringElement)(referencedElement.$ref) || shouldDetectCircular || (_this$options$derefer11 = this.options.dereference.dereferenceOpts) != null && _this$options$derefer11.continueOnError) && !ancestorsLineage.includesCycle(referencedElement)) {
      // append referencing reference to ancestors lineage
      directAncestors.add(referencingElement);

      // Extract $self from target document for external references
      const targetSelfValue = isExternalReference || isNonRootDocument ? this.extractSelfFromReference(reference) : this.$selfValue;
      const visitor = new OpenAPI3_2DereferenceVisitor({
        reference,
        namespace: this.namespace,
        indirections: [...this.indirections],
        options: this.getNestedVisitorOptions(referencingElement),
        refractCache: this.refractCache,
        ancestors: ancestorsLineage,
        allOfDiscriminatorMapping: this.allOfDiscriminatorMapping,
        $selfValue: targetSelfValue
      });
      try {
        referencedElement = await visitAsync(referencedElement, visitor, {
          keyMap: _apidomNsOpenapi.keyMap,
          nodeTypeGetter: _apidomNsOpenapi.getNodeType
        });
      } catch (error) {
        this.indirections.pop();
        return this.handleDereferenceError(error, referencingElement, directAncestors);
      }

      // remove referencing reference from ancestors lineage
      directAncestors.delete(referencingElement);
    }
    this.indirections.pop();

    /**
     * Creating a new version of Path Item by merging fields from referenced Path Item with referencing one.
     */
    if ((0, _apidomNsOpenapi.isPathItemElement)(referencedElement)) {
      var _this$options$derefer12;
      const mergedElement = new _apidomNsOpenapi.PathItemElement([...referencedElement.content], (0, _apidomCore.cloneDeep)(referencedElement.meta), (0, _apidomCore.cloneDeep)(referencedElement.attributes));
      // assign unique id to merged element
      mergedElement.setMetaProperty('id', identityManager.generateId());
      // existing keywords from referencing PathItemElement overrides ones from referenced element
      referencingElement.forEach((value, keyElement, item) => {
        mergedElement.remove((0, _apidomCore.toValue)(keyElement));
        mergedElement.content.push(item);
      });
      mergedElement.remove('$ref');

      // annotate referenced element with info about original referencing element
      mergedElement.setMetaProperty('ref-fields', {
        $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
      });
      // annotate referenced element with info about origin (use $self if available)
      const refOriginURI = isExternalReference || isNonRootDocument ? this.extractSelfFromReference(reference) || reference.uri : this.$selfValue || reference.uri;
      mergedElement.setMetaProperty('ref-origin', refOriginURI);
      // annotate fragment with info about referencing element
      mergedElement.setMetaProperty('ref-referencing-element-id', (0, _apidomCore.cloneDeep)(identityManager.identify(referencingElement)));
      if ((_this$options$derefer12 = this.options.dereference.dereferenceOpts) != null && _this$options$derefer12.continueOnError) {
        mergedElement.setMetaProperty('ref-referencing-element', referencingElement);
      }
      referencedElement = mergedElement;
    }

    /**
     * Transclude referencing element with merged referenced element.
     */
    link.replaceWith(referencedElement, mutationReplacer);

    /**
     * We're at the root of the tree, so we're just replacing the entire tree.
     */
    return !parent ? referencedElement : undefined;
  }
  async LinkElement(linkElement, key, parent, path, ancestors, link) {
    // ignore LinkElement without operationRef or operationId field
    if (!(0, _apidomCore.isStringElement)(linkElement.operationRef) && !(0, _apidomCore.isStringElement)(linkElement.operationId)) {
      return undefined;
    }

    // operationRef and operationId fields are mutually exclusive
    if ((0, _apidomCore.isStringElement)(linkElement.operationRef) && (0, _apidomCore.isStringElement)(linkElement.operationId)) {
      const error = new _apidomError.ApiDOMError('LinkElement operationRef and operationId fields are mutually exclusive.');
      return this.handleDereferenceError(error, linkElement);
    }
    let operationElement;
    if ((0, _apidomCore.isStringElement)(linkElement.operationRef)) {
      var _linkElementCopy$oper;
      // possibly non-semantic referenced element
      const operationRefValue = (0, _apidomCore.toValue)(linkElement.operationRef);
      const jsonPointer = _modern.URIFragmentIdentifier.fromURIReference(operationRefValue);
      const retrievalURI = this.toBaseURI(operationRefValue);
      const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
      const isExternalReference = !isInternalReference;

      // ignore resolving internal Operation Object reference
      if (!this.options.resolve.internal && isInternalReference) {
        // skip traversing this Link element but traverse all it's child elements
        return undefined;
      }
      // ignore resolving external Operation Object reference
      if (!this.options.resolve.external && isExternalReference) {
        // skip traversing this Link element but traverse all it's child elements
        return undefined;
      }
      let reference;
      try {
        reference = await this.toReference((0, _apidomCore.toValue)(linkElement.operationRef));
        operationElement = (0, _modern.evaluate)(reference.value.result, jsonPointer);
      } catch (error) {
        return this.handleDereferenceError(error, linkElement);
      }

      // Ensure operationElement was resolved
      if (!operationElement) {
        const error = new _apidomError.ApiDOMError('OperationElement could not be resolved.');
        return this.handleDereferenceError(error, linkElement);
      }

      // applying semantics to a referenced element
      let resolvedOperationElement = operationElement;
      if ((0, _apidomCore.isPrimitiveElement)(resolvedOperationElement)) {
        const cacheKey = `operation-${(0, _apidomCore.toValue)(identityManager.identify(resolvedOperationElement))}`;
        if (this.refractCache.has(cacheKey)) {
          resolvedOperationElement = this.refractCache.get(cacheKey);
        } else {
          resolvedOperationElement = _apidomNsOpenapi.OperationElement.refract(resolvedOperationElement);
          this.refractCache.set(cacheKey, resolvedOperationElement);
        }
      }
      // create shallow clone to be able to annotate with metadata
      resolvedOperationElement = (0, _apidomCore.cloneShallow)(resolvedOperationElement);
      // annotate operation element with info about origin (use $self if available)
      const refOriginURI = isExternalReference ? this.extractSelfFromReference(reference) || reference.uri : this.$selfValue || reference.uri;
      resolvedOperationElement.setMetaProperty('ref-origin', refOriginURI);
      const linkElementCopy = (0, _apidomCore.cloneShallow)(linkElement);
      (_linkElementCopy$oper = linkElementCopy.operationRef) == null || _linkElementCopy$oper.meta.set('operation', resolvedOperationElement);

      /**
       * Transclude Link Object containing Operation Object in its meta.
       */
      link.replaceWith(linkElementCopy, mutationReplacer);

      /**
       * We're at the root of the tree, so we're just replacing the entire tree.
       */
      return !parent ? linkElementCopy : undefined;
    }
    if ((0, _apidomCore.isStringElement)(linkElement.operationId)) {
      var _linkElementCopy$oper2;
      const operationId = (0, _apidomCore.toValue)(linkElement.operationId);
      let reference;
      try {
        reference = await this.toReference(url.unsanitize(this.reference.uri));
      } catch (error) {
        return this.handleDereferenceError(error, linkElement);
      }
      operationElement = (0, _apidomCore.find)(e => (0, _apidomNsOpenapi.isOperationElement)(e) && (0, _apidomCore.isElement)(e.operationId) && e.operationId.equals(operationId), reference.value.result);
      // OperationElement not found by its operationId
      if ((0, _ramdaAdjunct.isUndefined)(operationElement)) {
        const error = new _apidomError.ApiDOMError(`OperationElement(operationId=${operationId}) not found.`);
        return this.handleDereferenceError(error, linkElement);
      }
      const linkElementCopy = (0, _apidomCore.cloneShallow)(linkElement);
      (_linkElementCopy$oper2 = linkElementCopy.operationId) == null || _linkElementCopy$oper2.meta.set('operation', operationElement);

      /**
       * Transclude Link Object containing Operation Object in its meta.
       */
      link.replaceWith(linkElementCopy, mutationReplacer);

      /**
       * We're at the root of the tree, so we're just replacing the entire tree.
       */
      return !parent ? linkElementCopy : undefined;
    }
    return undefined;
  }
  async ExampleElement(exampleElement, key, parent, path, ancestors, link) {
    // ignore ExampleElement without externalValue field
    if (!(0, _apidomCore.isStringElement)(exampleElement.externalValue)) {
      return undefined;
    }

    // value and externalValue fields are mutually exclusive
    if (exampleElement.hasKey('value') && (0, _apidomCore.isStringElement)(exampleElement.externalValue)) {
      const error = new _apidomError.ApiDOMError('ExampleElement value and externalValue fields are mutually exclusive.');
      return this.handleDereferenceError(error, exampleElement);
    }
    const externalValueRef = (0, _apidomCore.toValue)(exampleElement.externalValue);
    const retrievalURI = this.toBaseURI(externalValueRef);
    const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
    const isExternalReference = !isInternalReference;

    // ignore resolving internal Example Objects
    if (!this.options.resolve.internal && isInternalReference) {
      // skip traversing this Example element but traverse all it's child elements
      return undefined;
    }
    // ignore resolving external Example Objects
    if (!this.options.resolve.external && isExternalReference) {
      // skip traversing this Example element but traverse all it's child elements
      return undefined;
    }
    let reference;
    try {
      reference = await this.toReference((0, _apidomCore.toValue)(exampleElement.externalValue));
    } catch (error) {
      return this.handleDereferenceError(error, exampleElement);
    }

    // shallow clone of the referenced element
    const valueElement = (0, _apidomCore.cloneShallow)(reference.value.result);
    // annotate operation element with info about origin (use $self if available)
    const refOriginURI = isExternalReference ? this.extractSelfFromReference(reference) || reference.uri : this.$selfValue || reference.uri;
    valueElement.setMetaProperty('ref-origin', refOriginURI);
    const exampleElementCopy = (0, _apidomCore.cloneShallow)(exampleElement);
    exampleElementCopy.value = valueElement;

    /**
     * Transclude Example Object containing external value.
     */
    link.replaceWith(exampleElementCopy, mutationReplacer);

    /**
     * We're at the root of the tree, so we're just replacing the entire tree.
     */
    return !parent ? exampleElementCopy : undefined;
  }
  async MemberElement(memberElement, key, parent, path, ancestors, link) {
    var _this$options$derefer13;
    const parentElement = ancestors[ancestors.length - 1];

    // skip current MemberElement if its parent is not a DiscriminatorElement
    if (!(0, _apidomCore.isObjectElement)(parentElement) || !parentElement.classes.contains('discriminator-mapping')) {
      return undefined;
    }

    // skip current MemberElement if discriminator mapping dereferencing option is not enabled
    if (!((_this$options$derefer13 = this.options.dereference.strategyOpts['openapi-3-2']) != null && _this$options$derefer13.dereferenceDiscriminatorMapping)) {
      return false;
    }

    // skip current MemberElement if its key or value is not a StringElement
    if (!(0, _apidomCore.isStringElement)(memberElement.key) || !(0, _apidomCore.isStringElement)(memberElement.value)) {
      return false;
    }

    // skip current referencing MemberElement as it's already been accessed
    if (this.indirections.includes(memberElement)) {
      return false;
    }
    this.indirections.push(memberElement);
    const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
    const parentSchemaElement = [...directAncestors].findLast(_apidomNsOpenapi.isSchemaElement);
    const ancestorsSchemaIdentifiers = (0, _apidomCore.cloneDeep)(parentSchemaElement.getMetaProperty('ancestorsSchemaIdentifiers'));

    // get the reference from the MemberElement value
    const memberElementValue = (0, _apidomCore.toValue)(memberElement.value);
    const namePattern = /^[a-zA-Z0-9\\.\\-_]+$/;
    const memberElementRef = namePattern.test(memberElementValue) ? `#/components/schemas/${memberElementValue}` : memberElementValue;

    // create SchemaElement with the reference from the MemberElement value
    const schemaElement = new _apidomNsOpenapi.SchemaElement({
      $ref: memberElementRef
    });
    schemaElement.setMetaProperty('ancestorsSchemaIdentifiers', ancestorsSchemaIdentifiers);

    // append referencing reference to ancestors lineage
    directAncestors.add(schemaElement);

    // For MemberElement (discriminator mapping), we stay in the same document context
    // so we keep using the current $selfValue
    const visitor = new OpenAPI3_2DereferenceVisitor({
      reference: this.reference,
      namespace: this.namespace,
      indirections: [...this.indirections],
      options: this.getNestedVisitorOptions(schemaElement),
      refractCache: this.refractCache,
      ancestors: ancestorsLineage,
      allOfDiscriminatorMapping: this.allOfDiscriminatorMapping,
      $selfValue: this.$selfValue
    });
    let referencedElement;
    try {
      referencedElement = await visitAsync(schemaElement, visitor, {
        keyMap: _apidomNsOpenapi.keyMap,
        nodeTypeGetter: _apidomNsOpenapi.getNodeType
      });
    } catch (error) {
      return this.handleDereferenceError(error, schemaElement, directAncestors);
    }

    // remove referencing reference from ancestors lineage
    directAncestors.delete(schemaElement);
    this.indirections.pop();

    // annotate MemberElement with referenced schema
    const memberElementCopy = (0, _apidomCore.cloneShallow)(memberElement);
    memberElementCopy.value.setMetaProperty('ref-schema', referencedElement);

    /**
     * Transclude MemberElement containing referenced schema in its meta.
     */
    link.replaceWith(memberElementCopy, mutationReplacer);

    /**
     * We're at the root of the tree, so we're just replacing the entire tree.
     */
    return !parent ? memberElementCopy : undefined;
  }
  async SchemaElement(referencingElement, key, parent, path, ancestors, link) {
    var _this$options$derefer16;
    // skip current referencing schema as $ref keyword was not defined
    if (!(0, _apidomCore.isStringElement)(referencingElement.$ref)) {
      return undefined;
    }

    // skip current referencing element as it's already been access
    if (this.indirections.includes(referencingElement)) {
      return false;
    }
    const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);

    // compute baseURI using rules around $id and $ref keywords
    let reference;
    try {
      reference = await this.toReference(url.unsanitize(this.reference.uri));
    } catch (error) {
      return this.handleDereferenceError(error, referencingElement, directAncestors);
    }
    let {
      uri: retrievalURI
    } = reference;
    const $refBaseURI = (0, _util.resolveSchema$refField)(retrievalURI, referencingElement);
    const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
    const file = new _File.default({
      uri: $refBaseURIStrippedHash
    });
    const isUnknownURI = (0, _ramda.none)(r => r.canRead(file), this.options.resolve.resolvers);
    const isURL = !isUnknownURI;
    let isInternalReference = url.stripHash(this.reference.uri) === $refBaseURI;
    let isExternalReference = !isInternalReference;
    this.indirections.push(referencingElement);

    // determining reference, proper evaluation and selection mechanism
    let referencedElement;
    try {
      if (isUnknownURI || isURL) {
        // we're dealing with canonical URI or URL with possible fragment
        retrievalURI = this.toBaseURI($refBaseURI);
        const selector = $refBaseURI;
        const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
        referencedElement = (0, _uri.evaluate)(selector, referenceAsSchema);
        referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
        referencedElement.id = identityManager.identify(referencedElement);

        // ignore resolving internal Schema Objects
        if (!this.options.resolve.internal && isInternalReference) {
          // skip traversing this schema element but traverse all it's child elements
          return undefined;
        }
        // ignore resolving external Schema Objects
        if (!this.options.resolve.external && isExternalReference) {
          // skip traversing this schema element but traverse all it's child elements
          return undefined;
        }
      } else {
        // we're assuming here that we're dealing with JSON Pointer here
        retrievalURI = this.toBaseURI($refBaseURI);
        isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
        isExternalReference = !isInternalReference;

        // ignore resolving internal Schema Objects
        if (!this.options.resolve.internal && isInternalReference) {
          // skip traversing this schema element but traverse all it's child elements
          return undefined;
        }
        // ignore resolving external Schema Objects
        if (!this.options.resolve.external && isExternalReference) {
          // skip traversing this schema element but traverse all it's child elements
          return undefined;
        }
        reference = await this.toReference(url.unsanitize($refBaseURI));
        const selector = _modern.URIFragmentIdentifier.fromURIReference($refBaseURI);
        const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
        referencedElement = (0, _modern.evaluate)(referenceAsSchema, selector);
        referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
        referencedElement.id = identityManager.identify(referencedElement);
      }
    } catch (error) {
      /**
       * No SchemaElement($id=URL) was not found, so we're going to try to resolve
       * the URL and assume the returned response is a JSON Schema.
       */
      if (isURL && error instanceof _EvaluationJsonSchemaUriError.default) {
        if ((0, _$anchor.isAnchor)((0, _$anchor.uriToAnchor)($refBaseURI))) {
          // we're dealing with JSON Schema $anchor here
          isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
          isExternalReference = !isInternalReference;

          // ignore resolving internal Schema Objects
          if (!this.options.resolve.internal && isInternalReference) {
            // skip traversing this schema element but traverse all it's child elements
            return undefined;
          }
          // ignore resolving external Schema Objects
          if (!this.options.resolve.external && isExternalReference) {
            // skip traversing this schema element but traverse all it's child elements
            return undefined;
          }
          try {
            reference = await this.toReference(url.unsanitize($refBaseURI));
          } catch (toReferenceError) {
            this.indirections.pop();
            return this.handleDereferenceError(toReferenceError, referencingElement, directAncestors);
          }
          const selector = (0, _$anchor.uriToAnchor)($refBaseURI);
          const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
          try {
            referencedElement = (0, _$anchor.evaluate)(selector, referenceAsSchema);
          } catch (evaluateError) {
            this.indirections.pop();
            return this.handleDereferenceError(evaluateError, referencingElement, directAncestors);
          }
          referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
          referencedElement.id = identityManager.identify(referencedElement);
        } else {
          // we're assuming here that we're dealing with JSON Pointer here
          retrievalURI = this.toBaseURI($refBaseURI);
          isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
          isExternalReference = !isInternalReference;

          // ignore resolving internal Schema Objects
          if (!this.options.resolve.internal && isInternalReference) {
            // skip traversing this schema element but traverse all it's child elements
            return undefined;
          }
          // ignore resolving external Schema Objects
          if (!this.options.resolve.external && isExternalReference) {
            // skip traversing this schema element but traverse all it's child elements
            return undefined;
          }
          try {
            reference = await this.toReference(url.unsanitize($refBaseURI));
          } catch (toReferenceError) {
            this.indirections.pop();
            return this.handleDereferenceError(toReferenceError, referencingElement, directAncestors);
          }
          const selector = _modern.URIFragmentIdentifier.fromURIReference($refBaseURI);
          const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
          try {
            referencedElement = (0, _modern.evaluate)(referenceAsSchema, selector);
          } catch (evaluateError) {
            this.indirections.pop();
            return this.handleDereferenceError(evaluateError, referencingElement, directAncestors);
          }
          referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
          referencedElement.id = identityManager.identify(referencedElement);
        }
      } else {
        this.indirections.pop();
        return this.handleDereferenceError(error, referencingElement, directAncestors);
      }
    }

    // detect direct or indirect reference
    if (referencingElement === referencedElement) {
      const error = new _apidomError.ApiDOMError('Recursive Schema Object reference detected');
      this.indirections.pop();
      return this.handleDereferenceError(error, referencingElement, directAncestors);
    }

    // detect maximum depth of dereferencing
    if (this.indirections.length > this.options.dereference.maxDepth) {
      const error = new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
      this.indirections.pop();
      return this.handleDereferenceError(error, referencingElement, directAncestors);
    }

    // detect second deep dive into the same fragment and avoid it
    if (ancestorsLineage.includes(referencedElement)) {
      reference.refSet.circular = true;
      if (this.options.dereference.circular === 'error') {
        const error = new _apidomError.ApiDOMError('Circular reference detected');
        this.indirections.pop();
        return this.handleDereferenceError(error, referencingElement, directAncestors);
      }
      if (this.options.dereference.circular === 'replace') {
        var _this$options$derefer14, _this$options$derefer15;
        const refElement = new _apidomCore.RefElement(referencedElement.id, {
          type: 'json-schema',
          uri: reference.uri,
          $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
        });
        const replacer = (_this$options$derefer14 = (_this$options$derefer15 = this.options.dereference.strategyOpts['openapi-3-2']) == null ? void 0 : _this$options$derefer15.circularReplacer) != null ? _this$options$derefer14 : this.options.dereference.circularReplacer;
        const replacement = replacer(refElement);
        link.replaceWith(replacement, mutationReplacer);
        return !parent ? replacement : false;
      }
    }

    /**
     * Dive deep into the fragment.
     *
     * Cases to consider:
     *  1. We're crossing document boundary
     *  2. Fragment is from non-root document
     *  3. Fragment is a Schema Object with $ref field. We need to follow it to get the eventual value
     *  4. We are dereferencing the fragment lazily/eagerly depending on circular mode
     */
    const isNonRootDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
    const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
    if ((isExternalReference || isNonRootDocument || (0, _apidomNsOpenapi.isSchemaElement)(referencedElement) && (0, _apidomCore.isStringElement)(referencedElement.$ref) || shouldDetectCircular || (_this$options$derefer16 = this.options.dereference.dereferenceOpts) != null && _this$options$derefer16.continueOnError) && !ancestorsLineage.includesCycle(referencedElement)) {
      // append referencing reference to ancestors lineage
      directAncestors.add(referencingElement);

      // Extract $self from target document for external references
      const targetSelfValue = isExternalReference || isNonRootDocument ? this.extractSelfFromReference(reference) : this.$selfValue;
      const visitor = new OpenAPI3_2DereferenceVisitor({
        reference,
        namespace: this.namespace,
        indirections: [...this.indirections],
        options: this.getNestedVisitorOptions(referencingElement),
        refractCache: this.refractCache,
        ancestors: ancestorsLineage,
        allOfDiscriminatorMapping: this.allOfDiscriminatorMapping,
        $selfValue: targetSelfValue
      });
      try {
        referencedElement = await visitAsync(referencedElement, visitor, {
          keyMap: _apidomNsOpenapi.keyMap,
          nodeTypeGetter: _apidomNsOpenapi.getNodeType
        });
      } catch (error) {
        this.indirections.pop();
        return this.handleDereferenceError(error, referencingElement, directAncestors);
      }

      // remove referencing reference from ancestors lineage
      directAncestors.delete(referencingElement);
    }
    this.indirections.pop();

    // Boolean JSON Schemas
    if ((0, _apidomNsOpenapi.isBooleanJsonSchemaElement)(referencedElement)) {
      const booleanJsonSchemaElement = (0, _apidomCore.cloneDeep)(referencedElement);
      // assign unique id to merged element
      booleanJsonSchemaElement.setMetaProperty('id', identityManager.generateId());
      // annotate referenced element with info about original referencing element
      booleanJsonSchemaElement.setMetaProperty('ref-fields', {
        $ref: (0, _apidomCore.toValue)(referencingElement.$ref),
        $refBaseURI
      });
      // annotate referenced element with info about origin (use $self if available)
      const refOriginURI = isExternalReference ? this.extractSelfFromReference(reference) || reference.uri : this.$selfValue || reference.uri;
      booleanJsonSchemaElement.setMetaProperty('ref-origin', refOriginURI);
      // annotate fragment with info about referencing element
      booleanJsonSchemaElement.setMetaProperty('ref-referencing-element-id', (0, _apidomCore.cloneDeep)(identityManager.identify(referencingElement)));
      link.replaceWith(booleanJsonSchemaElement, mutationReplacer);
      return !parent ? booleanJsonSchemaElement : false;
    }

    /**
     * Creating a new version of Schema Object by merging fields from referenced Schema Object with referencing one.
     */
    if ((0, _apidomNsOpenapi.isSchemaElement)(referencedElement)) {
      var _this$options$derefer17, _this$options$derefer18;
      const mergedElement = new _apidomNsOpenapi.SchemaElement([...referencedElement.content], (0, _apidomCore.cloneDeep)(referencedElement.meta), (0, _apidomCore.cloneDeep)(referencedElement.attributes));
      // assign unique id to merged element
      mergedElement.setMetaProperty('id', identityManager.generateId());
      // existing keywords from referencing schema overrides ones from referenced schema
      referencingElement.forEach((value, keyElement, item) => {
        mergedElement.remove((0, _apidomCore.toValue)(keyElement));
        mergedElement.content.push(item);
      });
      mergedElement.remove('$ref');
      // annotate referenced element with info about original referencing element
      mergedElement.setMetaProperty('ref-fields', {
        $ref: (0, _apidomCore.toValue)(referencingElement.$ref),
        $refBaseURI
      });
      // annotate fragment with info about origin (use $self if available)
      const refOriginURI = isExternalReference ? this.extractSelfFromReference(reference) || reference.uri : this.$selfValue || reference.uri;
      mergedElement.setMetaProperty('ref-origin', refOriginURI);
      // annotate fragment with info about referencing element
      mergedElement.setMetaProperty('ref-referencing-element-id', (0, _apidomCore.cloneDeep)(identityManager.identify(referencingElement)));
      if ((_this$options$derefer17 = this.options.dereference.dereferenceOpts) != null && _this$options$derefer17.continueOnError) {
        mergedElement.setMetaProperty('ref-referencing-element', referencingElement);
      }

      // creating mapping for allOf discriminator
      if ((_this$options$derefer18 = this.options.dereference.strategyOpts['openapi-3-2']) != null && _this$options$derefer18.dereferenceDiscriminatorMapping) {
        var _parentElement$classe;
        const parentElement = ancestors[ancestors.length - 1];
        const parentSchemaElement = [...directAncestors].findLast(_apidomNsOpenapi.isSchemaElement);
        const parentSchemaElementName = parentSchemaElement == null ? void 0 : parentSchemaElement.getMetaProperty('schemaName');
        const mergedElementName = (0, _apidomCore.toValue)(mergedElement.getMetaProperty('schemaName'));
        if (mergedElementName && parentSchemaElementName && // @ts-ignore
        parentElement != null && (_parentElement$classe = parentElement.classes) != null && _parentElement$classe.contains('json-schema-allOf')) {
          var _this$allOfDiscrimina;
          const currentMapping = (_this$allOfDiscrimina = this.allOfDiscriminatorMapping.get(mergedElementName)) != null ? _this$allOfDiscrimina : [];
          currentMapping.push(parentSchemaElement);
          this.allOfDiscriminatorMapping.set(mergedElementName, currentMapping);
        }
      }
      referencedElement = mergedElement;
    }
    /**
     * Transclude referencing element with merged referenced element.
     */
    link.replaceWith(referencedElement, mutationReplacer);

    /**
     * We're at the root of the tree, so we're just replacing the entire tree.
     */
    return !parent ? referencedElement : undefined;
  }
}
var _default = exports.default = OpenAPI3_2DereferenceVisitor;