_resolveSchema = (context, schema, stack = 0, resolveFor = CONVERSION, seenRef = {}, currentPath = '') => {
    if (!schema) {
      return new Error('Schema is empty');
    }

    const { stackLimit } = context.computedOptions;

    if (stack >= getRefStackLimit(stackLimit)) {
      return { value: ERR_TOO_MANY_LEVELS };
    }

    stack++;

    // eslint-disable-next-line one-var
    const compositeKeyword = schema.anyOf ? 'anyOf' : 'oneOf',
      { concreteUtils } = context;

    let compositeSchema = schema.anyOf || schema.oneOf;

    if (compositeSchema) {
      compositeSchema = _.map(compositeSchema, (schemaElement) => {
        const isSchemaFullyResolved = _.get(schemaElement, 'value') === ERR_TOO_MANY_LEVELS &&
          !_.startsWith(_.get(schemaElement, 'value', ''), '<Circular reference to ');

        /**
         * elements of composite schema may not have resolved fully,
         * we want to avoid assigning these properties to schema element in such cases
         */
        PROPERTIES_TO_ASSIGN_ON_CASCADE.forEach((prop) => {
          if (_.isNil(schemaElement[prop]) && !_.isNil(schema[prop]) && isSchemaFullyResolved) {
            schemaElement[prop] = schema[prop];
          }
        });
        return schemaElement;
      });

      if (resolveFor === CONVERSION) {
        return _resolveSchema(context, compositeSchema[0], stack, resolveFor, _.cloneDeep(seenRef), currentPath);
      }

      return { [compositeKeyword]: _.map(compositeSchema, (schemaElement, index) => {
        return _resolveSchema(context, schemaElement, stack, resolveFor, _.cloneDeep(seenRef),
          utils.addToJsonPath(currentPath, [compositeKeyword, index]));
      }) };
    }

    if (schema.allOf) {
      return resolveAllOfSchema(context, schema, stack, resolveFor, _.cloneDeep(seenRef), currentPath);
    }

    if (schema.$ref) {
      const schemaRef = schema.$ref;

      if (seenRef[schemaRef]) {
        return {
          value: '<Circular reference to ' + schemaRef + ' detected>'
        };
      }

      seenRef[schemaRef] = true;

      if (context.schemaCache[schemaRef]) {
        // Also merge readOnly and writeOnly prop cache from schemaCache to global context cache
        mergeReadWritePropCache(context, context.schemaCache[schemaRef].readOnlyPropCache,
          context.schemaCache[schemaRef].writeOnlyPropCache, currentPath);

        schema = context.schemaCache[schemaRef].schema;
      }
      else {
        const existingReadPropCache = context.readOnlyPropCache,
          existingWritePropCache = context.writeOnlyPropCache;

        schema = resolveRefFromSchema(context, schemaRef, stack, _.cloneDeep(seenRef));

        /**
         * Reset readOnly and writeOnly prop cache before resolving schema to make sure
         * we have fresh cache for $ref resolution which will be stored as part of schemaCache
         */
        resetReadWritePropCache(context);
        schema = _resolveSchema(context, schema, stack, resolveFor, _.cloneDeep(seenRef), '');

        // Add the resolved schema to the global schema cache
        context.schemaCache[schemaRef] = {
          schema,
          readOnlyPropCache: context.readOnlyPropCache,
          writeOnlyPropCache: context.writeOnlyPropCache
        };

        // eslint-disable-next-line one-var
        const newReadPropCache = context.readOnlyPropCache,
          newWritePropCache = context.writeOnlyPropCache;

        // Assign existing readOnly and writeOnly prop cache back to global context cache
        context.readOnlyPropCache = existingReadPropCache;
        context.writeOnlyPropCache = existingWritePropCache;

        // Merge existing and current cache to make sure we have all the properties in cache
        mergeReadWritePropCache(context, newReadPropCache, newWritePropCache, currentPath);
      }
      return schema;
    }

    // Discard format if not supported by both json-schema-faker and ajv or pattern is also defined
    if (!_.includes(SUPPORTED_FORMATS, schema.format) || (schema.pattern && schema.format)) {
      schema.format && (delete schema.format);
    }

    if (
      concreteUtils.compareTypes(schema.type, SCHEMA_TYPES.object) ||
      schema.hasOwnProperty('properties')
    ) {
      let resolvedSchemaProps = {},
        { includeDeprecated } = context.computedOptions;

      _.forOwn(schema.properties, (property, propertyName) => {
        // Skip property resolution if it's not schema object
        if (!_.isObject(property)) {
          return;
        }

        if (
          property.format === 'decimal' ||
          property.format === 'byte' ||
          property.format === 'password' ||
          property.format === 'unix-time'
        ) {
          delete property.format;
        }

        // Skip addition of deprecated properties based on provided options
        if (!includeDeprecated && property.deprecated) {
          return;
        }

        const currentPropPath = utils.addToJsonPath(currentPath, ['properties', propertyName]);

        resolvedSchemaProps[propertyName] = _resolveSchema(context, property, stack, resolveFor,
          _.cloneDeep(seenRef), currentPropPath);
      });

      schema.properties = resolvedSchemaProps;
      schema.type = schema.type || SCHEMA_TYPES.object;
    }
    // If schema is of type array
    else if (concreteUtils.compareTypes(schema.type, SCHEMA_TYPES.array) && schema.items) {
      schema.items = _resolveSchema(context, schema.items, stack, resolveFor, _.cloneDeep(seenRef),
        utils.addToJsonPath(currentPath, ['items']));
    }
    // Any properties to ignored should not be available in schema
    else if (_.every(SCHEMA_PROPERTIES_TO_EXCLUDE, (schemaKey) => { return !schema.hasOwnProperty(schemaKey); })) {
      if (schema.hasOwnProperty('type')) {
        let { parametersResolution } = context.computedOptions;

        // Override default value to schema for CONVERSION only for parmeter resolution set to schema
        if (resolveFor === CONVERSION && parametersResolution === 'schema') {
          if (!schema.hasOwnProperty('format')) {
            schema.default = '<' + schema.type + '>';
          }
          else if (typesMap.hasOwnProperty(schema.type)) {
            schema.default = typesMap[schema.type][schema.format];

            // in case the format is a custom format (email, hostname etc.)
            // https://swagger.io/docs/specification/data-models/data-types/#string
            // eslint-disable-next-line max-depth
            if (!schema.default && schema.format) {
              // Use non defined format only for schema of type string
              schema.default = '<' + (schema.type === SCHEMA_TYPES.string ? schema.format : schema.type) + '>';
            }
          }
          else {
            schema.default = '<' + schema.type + (schema.format ? ('-' + schema.format) : '') + '>';
          }
        }
      }
    }

    if (schema.hasOwnProperty('additionalProperties')) {
      schema.additionalProperties = _.isBoolean(schema.additionalProperties) ? schema.additionalProperties :
        _resolveSchema(context, schema.additionalProperties, stack, resolveFor, _.cloneDeep(seenRef),
          utils.addToJsonPath(currentPath, ['additionalProperties']));
      schema.type = schema.type || SCHEMA_TYPES.object;
    }

    // Resolve refs inside enums to value
    if (schema.hasOwnProperty('enum')) {
      _.forEach(schema.enum, (item, index) => {
        if (item && item.hasOwnProperty('$ref')) {
          schema.enum[index] = resolveRefFromSchema(
            context, item.$ref, stack, _.cloneDeep(seenRef)
          );
        }
      });
    }

    // Keep track of readOnly and writeOnly properties to resolve request and responses accordingly later.
    if (schema.readOnly) {
      context.readOnlyPropCache[currentPath] = true;
    }

    if (schema.writeOnly) {
      context.writeOnlyPropCache[currentPath] = true;
    }

    return schema;
  }
Resolve a given ref from the schema

@param {Object} context - Global context
@param {Object} schema - Schema that is to be resolved
@param {Number} [stack] - Current recursion depth
@param {String} resolveFor - For which action this resolution is to be done
@param {Object} seenRef - Map of all the references that have been resolved
@param {String} currentPath - Current path (json-pointer) being resolved relative to original schema
@todo: Explore using a directed graph/tree for maintaining seen ref

@returns {Object} Returns the object that satisfies the schema