// Copyright (c) 2013, 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.

/// Low-level support for interoperating with JavaScript.
///
/// You should usually use `package:js` instead of this library. For more
/// information, see the [JS interop page](https://dart.dev/web/js-interop).
///
/// This library provides access to JavaScript objects from Dart, allowing
/// Dart code to get and set properties, and call methods of JavaScript objects
/// and invoke JavaScript functions. The library takes care of converting
/// between Dart and JavaScript objects where possible, or providing proxies if
/// conversion isn't possible.
///
/// This library does not make Dart objects usable from JavaScript, their
/// methods and properties are not accessible, though it does allow Dart
/// functions to be passed into and called from JavaScript.
///
/// [JsObject] is the core type and represents a proxy of a JavaScript object.
/// JsObject gives access to the underlying JavaScript objects properties and
/// methods. `JsObject`s can be acquired by calls to JavaScript, or they can be
/// created from proxies to JavaScript constructors.
///
/// The top-level getter [context] provides a [JsObject] that represents the
/// global object in JavaScript, usually `window`.
///
/// The following example shows an alert dialog via a JavaScript call to the
/// global function `alert()`:
///
///     import 'dart:js';
///
///     main() => context.callMethod('alert', ['Hello from Dart!']);
///
/// This example shows how to create a [JsObject] from a JavaScript constructor
/// and access its properties:
///
///     import 'dart:js';
///
///     main() {
///       var object = JsObject(context['Object']);
///       object['greeting'] = 'Hello';
///       object['greet'] = (name) => "${object['greeting']} $name";
///       var message = object.callMethod('greet', ['JavaScript']);
///       context['console'].callMethod('log', [message]);
///     }
///
/// ## Proxying and automatic conversion
///
/// When setting properties on a JsObject or passing arguments to a Javascript
/// method or function, Dart objects are automatically converted or proxied to
/// JavaScript objects. When accessing JavaScript properties, or when a Dart
/// closure is invoked from JavaScript, the JavaScript objects are also
/// converted to Dart.
///
/// Functions and closures are proxied in such a way that they are callable. A
/// Dart closure assigned to a JavaScript property is proxied by a function in
/// JavaScript. A JavaScript function accessed from Dart is proxied by a
/// [JsFunction], which has a [JsFunction.apply] method to invoke it.
///
/// The following types are transferred directly and not proxied:
///
///   * Basic types: `null`, `bool`, `num`, `String`, `DateTime`
///   * `TypedData`, including its subclasses like `Int32List`, but _not_
///     `ByteBuffer`
///   * When compiling for the web, also: `Blob`, `Event`, `ImageData`,
///     `KeyRange`, `Node`, and `Window`.
///
/// ## Converting collections with JsObject.jsify()
///
/// To create a JavaScript collection from a Dart collection use the
/// [JsObject.jsify] constructor, which converts Dart [Map]s and [Iterable]s
/// into JavaScript Objects and Arrays.
///
/// The following expression creates a new JavaScript object with the properties
/// `a` and `b` defined:
///
///     var jsMap = JsObject.jsify({'a': 1, 'b': 2});
///
/// This expression creates a JavaScript array:
///
///     var jsArray = JsObject.jsify([1, 2, 3]);
///
/// {@category Web}
library dart.js;

import 'dart:collection' show ListMixin;

/// The JavaScript global object, usually `window`.
external JsObject get context;

/// A proxy on a JavaScript object.
///
/// The properties of the JavaScript object are accessible via the `[]` and
/// `[]=` operators. Methods are callable via [callMethod].
class JsObject {
  /// Constructs a JavaScript object from its native [constructor] and returns
  /// a proxy to it.
  external factory JsObject(JsFunction constructor, [List? arguments]);

  /// Constructs a [JsObject] that proxies a native Dart object; _for expert use
  /// only_.
  ///
  /// Use this constructor only if you wish to get access to JavaScript
  /// properties attached to a browser host object, such as a Node or Blob, that
  /// is normally automatically converted into a native Dart object.
  ///
  /// An exception will be thrown if [object] has the type
  /// `bool`, `num`, or `String`.
  external factory JsObject.fromBrowserObject(Object object);

  /// Recursively converts a JSON-like collection of Dart objects to a
  /// collection of JavaScript objects and returns a [JsObject] proxy to it.
  ///
  /// [object] must be a [Map] or [Iterable], the contents of which are also
  /// converted. Maps and Iterables are copied to a new JavaScript object.
  /// Primitives and other transferable values are directly converted to their
  /// JavaScript type, and all other objects are proxied.
  external factory JsObject.jsify(Object object);

  /// Returns the value associated with [property] from the proxied JavaScript
  /// object.
  ///
  /// The type of [property] must be either [String] or [num].
  external dynamic operator [](Object property);

  // Sets the value associated with [property] on the proxied JavaScript
  // object.
  //
  // The type of [property] must be either [String] or [num].
  external void operator []=(Object property, Object? value);

  int get hashCode => 0;

  external bool operator ==(Object other);

  /// Returns `true` if the JavaScript object contains the specified property
  /// either directly or though its prototype chain.
  ///
  /// This is the equivalent of the `in` operator in JavaScript.
  external bool hasProperty(Object property);

  /// Removes [property] from the JavaScript object.
  ///
  /// This is the equivalent of the `delete` operator in JavaScript.
  external void deleteProperty(Object property);

  /// Returns `true` if the JavaScript object has [type] in its prototype chain.
  ///
  /// This is the equivalent of the `instanceof` operator in JavaScript.
  external bool instanceof(JsFunction type);

  /// Returns the result of the JavaScript objects `toString` method.
  external String toString();

  /// Calls [method] on the JavaScript object with the arguments [args] and
  /// returns the result.
  ///
  /// The type of [method] must be either [String] or [num].
  external dynamic callMethod(Object method, [List? args]);
}

/// A proxy on a JavaScript Function object.
class JsFunction extends JsObject {
  /// Returns a [JsFunction] that captures its 'this' binding and calls [f]
  /// with the value of JavaScript `this` passed as the first argument.
  external factory JsFunction.withThis(Function f);

  /// Invokes the JavaScript function with arguments [args]. If [thisArg] is
  /// supplied it is the value of `this` for the invocation.
  external dynamic apply(List args, {thisArg});
}

/// A [List] that proxies a JavaScript array.
class JsArray<E> extends JsObject with ListMixin<E> {
  /// Creates an empty JavaScript array.
  external factory JsArray();

  /// Creates a new JavaScript array and initializes it to the contents of
  /// [other].
  external factory JsArray.from(Iterable<E> other);

  // Methods required by ListMixin

  external E operator [](Object index);

  external void operator []=(Object index, E value);

  external int get length;

  external void set length(int length);

  // Methods overridden for better performance

  external void add(E value);

  external void addAll(Iterable<E> iterable);

  external void insert(int index, E element);

  external E removeAt(int index);

  external E removeLast();

  external void removeRange(int start, int end);

  external void setRange(int start, int end, Iterable<E> iterable,
      [int skipCount = 0]);

  external void sort([int compare(E a, E b)?]);
}

/// Returns a wrapper around function [f] that can be called from JavaScript
/// using `package:js` JavaScript interop.
///
/// The calling conventions in Dart2Js differ from JavaScript and so, by
/// default, it is not possible to call a Dart function directly. Wrapping with
/// `allowInterop` creates a function that can be called from JavaScript or
/// Dart. The semantics of the wrapped function are still more strict than
/// JavaScript, and the function will throw if called with too many or too few
/// arguments.
///
/// Calling this method repeatedly on a function will return the same result.
external F allowInterop<F extends Function>(F f);

/// Returns a wrapper around function [f] that can be called from JavaScript
/// using `package:js` JavaScript interop, passing JavaScript `this` as the first
/// argument.
///
/// See [allowInterop].
///
/// When called from Dart, [null] will be passed as the first argument.
external Function allowInteropCaptureThis(Function f);
