/**
 * @flow
 */
import Reference from './reference.js';
import { isObject, deepGet, deepExists } from './../../utils';

/**
 * @class DataSnapshot
 * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot
 */
export default class Snapshot {
  ref: Object;
  key: string;

  _value: any;
  _priority: any;
  _childKeys: Array<string>;

  constructor(ref: Reference, snapshot: Object) {
    this.key = snapshot.key;

    if (ref.key !== snapshot.key) {
      this.ref = ref.child(snapshot.key);
    } else {
      this.ref = ref;
    }

    // internal use only
    this._value = snapshot.value;
    this._priority = snapshot.priority === undefined ? null : snapshot.priority;
    this._childKeys = snapshot.childKeys || [];
  }

  /**
   * Extracts a JavaScript value from a DataSnapshot.
   * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#val
   * @returns {any}
   */
  val(): any {
    // clone via JSON stringify/parse - prevent modification of this._value
    if (isObject(this._value) || Array.isArray(this._value)) return JSON.parse(JSON.stringify(this._value));
    return this._value;
  }

  /**
   * Gets another DataSnapshot for the location at the specified relative path.
   * @param path
   * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#forEach
   * @returns {Snapshot}
   */
  child(path: string): Snapshot {
    const value = deepGet(this._value, path);
    const childRef = this.ref.child(path);
    return new Snapshot(childRef, {
      value,
      key: childRef.key,
      exists: value !== null,

      // todo this is wrong - child keys needs to be the ordered keys, from FB
      // todo potential solution is build up a tree/map of a snapshot and its children
      // todo natively and send that back to JS to be use in this class.
      childKeys: isObject(value) ? Object.keys(value) : [],
    });
  }

  /**
   * Returns true if this DataSnapshot contains any data.
   * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#exists
   * @returns {boolean}
   */
  exists(): boolean {
    return this._value !== null;
  }

  /**
   * Enumerates the top-level children in the DataSnapshot.
   * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#forEach
   * @param action
   */
  forEach(action: (key: any) => any): boolean {
    if (!this._childKeys.length) return false;
    let cancelled = false;

    for (let i = 0, len = this._childKeys.length; i < len; i++) {
      const key = this._childKeys[i];
      const childSnapshot = this.child(key);
      const returnValue = action(childSnapshot);

      if (returnValue === true) {
        cancelled = true;
        break;
      }
    }

    return cancelled;
  }

  /**
   * Gets the priority value of the data in this DataSnapshot.
   * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#getPriority
   * @returns {String|Number|null}
   */
  getPriority(): string | number | null {
    return this._priority;
  }

  /**
   * Returns true if the specified child path has (non-null) data.
   * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#hasChild
   * @param path
   * @returns {Boolean}
   */
  hasChild(path: string): boolean {
    return deepExists(this._value, path);
  }

  /**
   * Returns whether or not the DataSnapshot has any non-null child properties.
   * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#hasChildren
   * @returns {boolean}
   */
  hasChildren(): boolean {
    return this.numChildren() > 0;
  }

  /**
   * Returns the number of child properties of this DataSnapshot.
   * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#numChildren
   * @returns {Number}
   */
  numChildren(): number {
    if (!isObject(this._value)) return 0;
    return Object.keys(this._value).length;
  }

  /**
   * Returns a JSON-serializable representation of this object.
   * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#toJSON
   * @returns {any}
   */
  toJSON(): any {
    return this.val();
  }
}
