text: "use strict";
  
  module.exports = $Ref;
  
  const Pointer = require("./pointer");
  const { InvalidPointerError, isHandledError, normalizeError } = require("./util/errors");
  const { safePointerToPath, stripHash, getHash } = require("./util/url");
  
  /**
   * This class represents a single JSON reference and its resolved value.
   *
   * @constructor
   */
  function $Ref () {
    /**
     * The file path or URL of the referenced file.
     * This path is relative to the path of the main JSON schema file.
     *
     * This path does NOT contain document fragments (JSON pointers). It always references an ENTIRE file.
     * Use methods such as {@link $Ref#get}, {@link $Ref#resolve}, and {@link $Ref#exists} to get
     * specific JSON pointers within the file.
     *
     * @type {string}
     */
    this.path = undefined;
  
    /**
     * The resolved value of the JSON reference.
     * Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
     * @type {?*}
     */
    this.value = undefined;
  
    /**
     * The {@link $Refs} object that contains this {@link $Ref} object.
     * @type {$Refs}
     */
    this.$refs = undefined;
  
    /**
     * Indicates the type of {@link $Ref#path} (e.g. "file", "http", etc.)
     * @type {?string}
     */
    this.pathType = undefined;
  
    /**
     * List of all errors. Undefined if no errors.
     * @type {Array<JSONParserError | ResolverError | ParserError | MissingPointerError>}
     */
    this.errors = undefined;
  }
  
  /**
   * Pushes an error to errors array.
   *
   * @param {Array<JSONParserError | JSONParserErrorGroup>} error - The error to be pushed
   * @returns {void}
   */
  $Ref.prototype.addError = function (err) {
    if (this.errors === undefined) {
      this.errors = [];
    }
  
    // the path has been almost certainly set at this point,
    // but just in case something went wrong, let's inject path if necessary
    if (Array.isArray(err.errors)) {
      this.errors.push(...err.errors.map(normalizeError));
    }
    else {
      this.errors.push(normalizeError(err));
    }
  };
  
  
  /**
   * Determines whether the given JSON reference exists within this {@link $Ref#value}.
   *
   * @param {string} path - The full path being resolved, optionally with a JSON pointer in the hash
   * @param {$RefParserOptions} options
   * @returns {boolean}
   */
  $Ref.prototype.exists = function (path, options) {
    try {
      this.resolve(path, options);
      return true;
    }
    catch (e) {
      return false;
    }
  };
  
  /**
   * Resolves the given JSON reference within this {@link $Ref#value} and returns the resolved value.
   *
   * @param {string} path - The full path being resolved, optionally with a JSON pointer in the hash
   * @param {$RefParserOptions} options
   * @returns {*} - Returns the resolved value
   */
  $Ref.prototype.get = function (path, options) {
    return this.resolve(path, options).value;
  };
  
  /**
   * Resolves the given JSON reference within this {@link $Ref#value}.
   *
   * @param {string} path - The full path being resolved, optionally with a JSON pointer in the hash
   * @param {$RefParserOptions} options
   * @param {string} friendlyPath - The original user-specified path (used for error messages)
  *  @param {string} pathFromRoot - The path of `obj` from the schema root
   * @returns {Pointer}
   */
  $Ref.prototype.resolve = function (path, options, friendlyPath, pathFromRoot) {
    let pointer = new Pointer(this, path, friendlyPath);
    try {
      return pointer.resolve(this.value, options);
    }
    catch (err) {
      if (!options || !options.continueOnError || !isHandledError(err)) {
        throw err;
      }
  
      if (err.path === null) {
        err.path = safePointerToPath(getHash(pathFromRoot));
      }
  
      if (err instanceof InvalidPointerError) {
        // this is a special case - InvalidPointerError is thrown when dereferencing external file,
        // but the issue is caused by the source file that referenced the file that undergoes dereferencing
        err.source = stripHash(pathFromRoot);
      }
  
      this.addError(err);
      return null;
    }
  };
  
  /**
   * Sets the value of a nested property within this {@link $Ref#value}.
   * If the property, or any of its parents don't exist, they will be created.
   *
   * @param {string} path - The full path of the property to set, optionally with a JSON pointer in the hash
   * @param {*} value - The value to assign
   */
  $Ref.prototype.set = function (path, value) {
    let pointer = new Pointer(this, path);
    this.value = pointer.set(this.value, value);
  };
  
  /**
   * Determines whether the given value is a JSON reference.
   *
   * @param {*} value - The value to inspect
   * @returns {boolean}
   */
  $Ref.is$Ref = function (value) {
    return value && typeof value === "object" && typeof value.$ref === "string" && value.$ref.length > 0;
  };
  
  /**
   * Determines whether the given value is an external JSON reference.
   *
   * @param {*} value - The value to inspect
   * @returns {boolean}
   */
  $Ref.isExternal$Ref = function (value) {
    return $Ref.is$Ref(value) && value.$ref[0] !== "#";
  };
  
  /**
   * Determines whether the given value is a JSON reference, and whether it is allowed by the options.
   * For example, if it references an external file, then options.resolve.external must be true.
   *
   * @param {*} value - The value to inspect
   * @param {$RefParserOptions} options
   * @returns {boolean}
   */
  $Ref.isAllowed$Ref = function (value, options) {
    if ($Ref.is$Ref(value)) {
      if (value.$ref.substr(0, 2) === "#/" || value.$ref === "#") {
        // It's a JSON Pointer reference, which is always allowed
        return true;
      }
      else if (value.$ref[0] !== "#" && (!options || options.resolve.external)) {
        // It's an external reference, which is allowed by the options
        return true;
      }
    }
  };
  
  /**
   * Determines whether the given value is a JSON reference that "extends" its resolved value.
   * That is, it has extra properties (in addition to "$ref"), so rather than simply pointing to
   * an existing value, this $ref actually creates a NEW value that is a shallow copy of the resolved
   * value, plus the extra properties.
   *
   * @example:
   *  {
   *    person: {
   *      properties: {
   *        firstName: { type: string }
   *        lastName: { type: string }
   *      }
   *    }
   *    employee: {
   *      properties: {
   *        $ref: #/person/properties
   *        salary: { type: number }
   *      }
   *    }
   *  }
   *
   *  In this example, "employee" is an extended $ref, since it extends "person" with an additional
   *  property (salary).  The result is a NEW value that looks like this:
   *
   *  {
   *    properties: {
   *      firstName: { type: string }
   *      lastName: { type: string }
   *      salary: { type: number }
   *    }
   *  }
   *
   * @param {*} value - The value to inspect
   * @returns {boolean}
   */
  $Ref.isExtended$Ref = function (value) {
    return $Ref.is$Ref(value) && Object.keys(value).length > 1;
  };
  
  /**
   * Returns the resolved value of a JSON Reference.
   * If necessary, the resolved value is merged with the JSON Reference to create a new object
   *
   * @example:
   *  {
   *    person: {
   *      properties: {
   *        firstName: { type: string }
   *        lastName: { type: string }
   *      }
   *    }
   *    employee: {
   *      properties: {
   *        $ref: #/person/properties
   *        salary: { type: number }
   *      }
   *    }
   *  }
   *
   *  When "person" and "employee" are merged, you end up with the following object:
   *
   *  {
   *    properties: {
   *      firstName: { type: string }
   *      lastName: { type: string }
   *      salary: { type: number }
   *    }
   *  }
   *
   * @param {object} $ref - The JSON reference object (the one with the "$ref" property)
   * @param {*} resolvedValue - The resolved value, which can be any type
   * @returns {*} - Returns the dereferenced value
   */
  $Ref.dereference = function ($ref, resolvedValue) {
    if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) {
      let merged = {};
      for (let key of Object.keys($ref)) {
        if (key !== "$ref") {
          merged[key] = $ref[key];
        }
      }
  
      for (let key of Object.keys(resolvedValue)) {
        if (!(key in merged)) {
          merged[key] = resolvedValue[key];
        }
      }
  
      return merged;
    }
    else {
      // Completely replace the original reference with the resolved value
      return resolvedValue;
    }
  };
  