// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of dart._runtime;

/// This library defines a set of general javascript utilities for us
/// by the Dart runtime.
// TODO(ochafik): Rewrite some of these in Dart when possible.

final Function(dynamic, dynamic, dynamic) defineProperty =
    JS('', 'Object.defineProperty');

defineValue(obj, name, value) {
  defineAccessor(obj, name, value: value, configurable: true, writable: true);
  return value;
}

final Function(dynamic, dynamic,
    {dynamic get,
    dynamic set,
    dynamic value,
    bool? configurable,
    bool? writable}) defineAccessor = JS('', 'Object.defineProperty');

final dynamic Function(dynamic, dynamic) getOwnPropertyDescriptor =
    JS('', 'Object.getOwnPropertyDescriptor');

final List Function(dynamic) getOwnPropertyNames =
    JS('', 'Object.getOwnPropertyNames');

final List Function(dynamic) getOwnPropertySymbols =
    JS('', 'Object.getOwnPropertySymbols');

final Function(dynamic) getPrototypeOf = JS('', 'Object.getPrototypeOf');

/// This error indicates a strong mode specific failure, other than a type
/// assertion failure (TypeError) or CastError.
void throwTypeError(String message) {
  throw TypeErrorImpl(message);
}

/// This error indicates a bug in the runtime or the compiler.
void throwInternalError(String message) {
  JS('', 'throw Error(#)', message);
}

Iterable getOwnNamesAndSymbols(obj) {
  var names = getOwnPropertyNames(obj);
  var symbols = getOwnPropertySymbols(obj);
  return JS('', '#.concat(#)', names, symbols);
}

/// Returns the value of field `name` on `obj`.
///
/// We use this instead of obj[name] since obj[name] checks the entire
/// prototype chain instead of just `obj`.
safeGetOwnProperty(obj, name) {
  if (JS<bool>('!', '#.hasOwnProperty(#)', obj, name))
    return JS<Object>('', '#[#]', obj, name);
}

copyTheseProperties(to, from, names) {
  for (int i = 0, n = JS('!', '#.length', names); i < n; ++i) {
    var name = JS('', '#[#]', names, i);
    if (name == 'constructor') continue;
    copyProperty(to, from, name);
  }
  return to;
}

copyProperty(to, from, name) {
  var desc = getOwnPropertyDescriptor(from, name);
  if (JS('!', '# == Symbol.iterator', name)) {
    // On native types, Symbol.iterator may already be present.
    // TODO(jmesserly): investigate if we still need this.
    // If so, we need to find a better solution.
    // See https://github.com/dart-lang/sdk/issues/28324
    var existing = getOwnPropertyDescriptor(to, name);
    if (existing != null) {
      if (JS('!', '#.writable', existing)) {
        JS('', '#[#] = #.value', to, name, desc);
      }
      return;
    }
  }
  defineProperty(to, name, desc);
}

@JSExportName('export')
exportProperty(to, from, name) => copyProperty(to, from, name);

/// Copy properties from source to destination object.
/// This operation is commonly called `mixin` in JS.
copyProperties(to, from) {
  return copyTheseProperties(to, from, getOwnNamesAndSymbols(from));
}
