text: import * as ImmutableUtils from './ImmutableUtils';
  
  const getDefaultGetId = (idAttribute) => (input) =>
    ImmutableUtils.isImmutable(input) ? input.get(idAttribute) : input[idAttribute];
  
  export default class EntitySchema {
    constructor(key, definition = {}, options = {}) {
      if (!key || typeof key !== 'string') {
        throw new Error(`Expected a string key for Entity, but found ${key}.`);
      }
  
      const {
        idAttribute = 'id',
        mergeStrategy = (entityA, entityB) => {
          return { ...entityA, ...entityB };
        },
        processStrategy = (input) => ({ ...input }),
        fallbackStrategy = (key, schema) => undefined,
        keyNamingStrategy,
      } = options;
  
      this._key = key;
      this._getId = typeof idAttribute === 'function' ? idAttribute : getDefaultGetId(idAttribute);
      this._idAttribute = idAttribute;
      this._mergeStrategy = mergeStrategy;
      this._processStrategy = processStrategy;
      this._fallbackStrategy = fallbackStrategy;
  
      this._keyNameAffix = '';
      if (keyNamingStrategy === 'camelCase') {
        this._keyNameAffix = 'Id';
      } else if (keyNamingStrategy === 'snakeCase') {
        this._keyNameAffix = '_id';
      }
  
      this.define(definition);
    }
  
    get key() {
      return this._key;
    }
  
    get idAttribute() {
      return this._idAttribute;
    }
  
    define(definition) {
      this.schema = Object.keys(definition).reduce((entitySchema, key) => {
        const schema = definition[key];
        return { ...entitySchema, [key]: schema };
      }, this.schema || {});
    }
  
    getId(input, parent, key) {
      return this._getId(input, parent, key);
    }
  
    merge(entityA, entityB) {
      return this._mergeStrategy(entityA, entityB);
    }
  
    fallback(id, schema) {
      return this._fallbackStrategy(id, schema);
    }
  
    getKeyName(key, schema) {
      if (!this._keyNameAffix) {
        return key;
      }
      if (Array.isArray(schema)) {
        if (key[key.length - 1] !== 's') {
          return `${key}${this._keyNameAffix}s`;
        }
        return `${key.slice(0, key.length - 1)}${this._keyNameAffix}s`;
      } else {
        return `${key}${this._keyNameAffix}`;
      }
    }
  
    normalize(input, parent, key, visit, addEntity, visitedEntities) {
      const id = this.getId(input, parent, key);
      const entityType = this.key;
  
      if (!(entityType in visitedEntities)) {
        visitedEntities[entityType] = {};
      }
      if (!(id in visitedEntities[entityType])) {
        visitedEntities[entityType][id] = [];
      }
      if (visitedEntities[entityType][id].some((entity) => entity === input)) {
        return id;
      }
      visitedEntities[entityType][id].push(input);
  
      const processedEntity = this._processStrategy(input, parent, key);
      Object.keys(this.schema).forEach((key) => {
        if (processedEntity.hasOwnProperty(key) && typeof processedEntity[key] === 'object') {
          const schema = this.schema[key];
          const resolvedSchema = typeof schema === 'function' ? schema(input) : schema;
  
          const idKey = this.getKeyName(key, resolvedSchema);
          processedEntity[idKey] = visit(
            processedEntity[key],
            processedEntity,
            idKey,
            resolvedSchema,
            addEntity,
            visitedEntities
          );
  
          // Delete old key if we're transforming name.
          if (this._keyNameAffix) {
            delete processedEntity[key];
          }
        }
      });
  
      addEntity(this, processedEntity, input, parent, key);
      return id;
    }
  
    denormalize(entity, unvisit) {
      if (ImmutableUtils.isImmutable(entity)) {
        return ImmutableUtils.denormalizeImmutable(this.schema, entity, unvisit);
      }
  
      Object.keys(this.schema).forEach((key) => {
        const schema = this.schema[key];
        const idKey = this.getKeyName(key, schema);
        if (entity.hasOwnProperty(idKey)) {
          entity[key] = unvisit(entity[idKey], schema);
        }
      });
      return entity;
    }
  }
  