'use strict';

/**
 * @ignore
 * @exports MetadatableFn
 * @param {*} parentClass
 * @return {module:MetadatableFn~Metadatable}
 */
const Metadatable = parentClass =>

/**
 * Mixin for objects which have a `metadata` property
 * @mixin
 * @alias module:MetadatableFn~Metadatable
 */
class extends parentClass {
  /**
   * Set a metadata value.
   * Warning: Does _not_ autovivify, you will need to create the parent objects if they don't exist
   * @param {string} key   Key to set. Supports dot notation e.g., `"foo.bar"`
   * @param {*}      value Value must be JSON.stringify-able
   * @throws Error
   * @throws RangeError
   * @fires Metadatable#metadataUpdate
   */
  setMeta(key, value) {
    if (!this.metadata) {
      throw new Error('Class does not have metadata property');
    }

    let parts = key.split('.');
    const property = parts.pop();
    let base = this.metadata;

    while (parts.length) {
      let part = parts.shift();
      if (!(part in base)) {
        throw new RangeError(`Metadata path invalid: ${key}`);
      }
      base = base[part];
    }

    const oldValue = base[property];
    base[property] = value;
    
    /**
    * @event Metadatable#metadataUpdate
    * @param {string} key
    * @param {*} newValue
    * @param {*} oldValue
    */
    this.emit('metadataUpdated', key, value, oldValue);
  }

  /**
   * Get metadata by dot notation
   * Warning: This method is _very_ permissive and will not error on a non-existent key. Rather, it will return false.
   * @param {string} key Key to fetch. Supports dot notation e.g., `"foo.bar"`
   * @return {*}
   * @throws Error
   */
  getMeta(key) {
    if (!this.metadata) {
      throw new Error('Class does not have metadata property');
    }

    const base = this.metadata;
    return key.split('.').reduce((obj, index) => obj && obj[index], base);
  }
};

module.exports = Metadatable;