import { assert, deprecate } from '@ember/debug';
import EmberObject from './system/object';
import Copyable from './mixins/copyable';

/**
 @module @ember/object
*/
function _copy(obj, deep, seen, copies) {
  // primitive data types are immutable, just return them.
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  let ret, loc;

  // avoid cyclical loops
  if (deep && (loc = seen.indexOf(obj)) >= 0) {
    return copies[loc];
  }

  // IMPORTANT: this specific test will detect a native array only. Any other
  // object will need to implement Copyable.
  if (Array.isArray(obj)) {
    ret = obj.slice();

    if (deep) {
      loc = ret.length;

      while (--loc >= 0) {
        ret[loc] = _copy(ret[loc], deep, seen, copies);
      }
    }
  } else if (Copyable.detect(obj)) {
    ret = obj.copy(deep, seen, copies);
  } else if (obj instanceof Date) {
    ret = new Date(obj.getTime());
  } else {
    assert(
      'Cannot clone an EmberObject that does not implement Copyable',
      !(obj instanceof EmberObject) || Copyable.detect(obj)
    );

    ret = {};
    let key;
    for (key in obj) {
      // support Null prototype
      if (!Object.prototype.hasOwnProperty.call(obj, key)) {
        continue;
      }

      // Prevents browsers that don't respect non-enumerability from
      // copying internal Ember properties
      if (key.substring(0, 2) === '__') {
        continue;
      }

      ret[key] = deep ? _copy(obj[key], deep, seen, copies) : obj[key];
    }
  }

  if (deep) {
    seen.push(obj);
    copies.push(ret);
  }

  return ret;
}

/**
  Creates a shallow copy of the passed object. A deep copy of the object is
  returned if the optional `deep` argument is `true`.

  If the passed object implements the `Copyable` interface, then this
  function will delegate to the object's `copy()` method and return the
  result. See `Copyable` for further details.

  For primitive values (which are immutable in JavaScript), the passed object
  is simply returned.

  @method copy
  @deprecated Use 'ember-copy' addon instead
  @static
  @for @ember/object/internals
  @param {Object} obj The object to clone
  @param {Boolean} [deep=false] If true, a deep copy of the object is made.
  @return {Object} The copied object
  @public
*/
export default function copy(obj, deep) {
  deprecate('Use ember-copy addon instead of copy method and Copyable mixin.', false, {
    id: 'ember-runtime.deprecate-copy-copyable',
    until: '4.0.0',
    url: 'https://emberjs.com/deprecations/v3.x/#toc_ember-runtime-deprecate-copy-copyable',
  });

  // fast paths
  if ('object' !== typeof obj || obj === null) {
    return obj; // can't copy primitives
  }

  if (!Array.isArray(obj) && Copyable.detect(obj)) {
    return obj.copy(deep);
  }

  return _copy(obj, deep, deep ? [] : null, deep ? [] : null);
}