// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';

import 'callable/async.dart';
import 'callable/built_in.dart';
import 'exception.dart';
import 'utils.dart';
import 'value.dart';

export 'callable/async.dart';
export 'callable/async_built_in.dart'
    show AsyncBuiltInCallable, warnForGlobalBuiltIn;
export 'callable/built_in.dart' show BuiltInCallable;
export 'callable/plain_css.dart';
export 'callable/user_defined.dart';

/// An interface for functions and mixins that can be invoked from Sass by
/// passing in arguments.
///
/// This extends [AsyncCallable] because all synchronous callables are also
/// usable in asynchronous contexts. [Callable]s are usable with both the
/// synchronous and asynchronous `compile()` functions, and as such should be
/// used in preference to [AsyncCallable]s if possible.
///
/// When writing custom functions, it's important to make them as user-friendly
/// and as close to the standards set by Sass's core functions as possible. Some
/// good guidelines to follow include:
///
/// * Use `Value.assert*` methods, like [Value.assertString], to cast untyped
///   `Value` objects to more specific types. For values from the argument list,
///   pass in the argument name as well. This ensures that the user gets good
///   error messages when they pass in the wrong type to your function.
///
/// * Individual classes may have more specific `assert*` methods, like
///   [SassNumber.assertInt], which should be used when possible.
///
/// * In Sass, every value counts as a list. Functions should avoid casting
///   values to the `SassList` type, and should use the [Value.asList] method
///   instead.
///
/// * When manipulating values like lists, strings, and numbers that have
///   metadata (comma versus space separated, bracketed versus unbracketed,
///   quoted versus unquoted, units), the output metadata should match the input
///   metadata. For lists, the [Value.withListContents] method can be used to do
///   this automatically.
///
/// * When in doubt, lists should default to comma-separated, strings should
///   default to quoted, and number should default to unitless.
///
/// * In Sass, lists and strings use one-based indexing and use negative indices
///   to index from the end of value. Functions should follow these conventions.
///   The [Value.sassIndexToListIndex] and [SassString.sassIndexToStringIndex]
///   methods can be used to do this automatically.
///
/// * String indexes in Sass refer to Unicode code points while Dart string
///   indices refer to UTF-16 code units. For example, the character U+1F60A,
///   Smiling Face With Smiling Eyes, is a single Unicode code point but is
///   represented in UTF-16 as two code units (`0xD83D` and `0xDE0A`). So in
///   Dart, `"a😊b".codeUnitAt(1)` returns `0xD83D`, whereas in Sass
///   `str-slice("a😊b", 1, 1)` returns `"😊"`. Functions should follow this
///   convention. The [SassString.sassIndexToStringIndex] and
///   [SassString.sassIndexToRuneIndex] methods can be used to do this
///   automatically, and the [SassString.sassLength] getter can be used to
///   access a string's length in code points.
///
/// {@category Compile}
@sealed
abstract interface class Callable implements AsyncCallable {
  @Deprecated('Use `Callable.function` instead.')
  factory Callable(
    String name,
    String arguments,
    Value callback(List<Value> arguments),
  ) =>
      Callable.function(name, arguments, callback);

  /// Creates a function with the given [name] and [arguments] that runs
  /// [callback] when called.
  ///
  /// The argument declaration is parsed from [arguments], which uses the same
  /// syntax as an argument list written in Sass (not including parentheses).
  /// The [arguments] list may be empty to indicate that the function takes no
  /// arguments. Arguments may also have default values. Throws a
  /// [SassFormatException] if parsing fails.
  ///
  /// Any exceptions thrown by [callback] are automatically converted to Sass
  /// errors and associated with the function call.
  ///
  /// For example:
  ///
  /// ```dart
  /// new Callable.function("str-split", r'$string, $divider: " "',
  ///     (arguments) {
  ///   var string = arguments[0].assertString("string");
  ///   var divider = arguments[1].assertString("divider");
  ///   return new SassList(
  ///       string.value.split(divider.value).map((substring) =>
  ///           new SassString(substring, quotes: string.hasQuotes)),
  ///       ListSeparator.comma);
  /// });
  /// ```
  ///
  /// Functions may also take variable length argument lists. These are declared
  /// the same way as in Sass, and are passed as the final argument to the
  /// callback. For example:
  ///
  /// ```dart
  /// new Callable.function("str-join", r'$strings...', (arguments) {
  ///   var args = arguments.first as SassArgumentList;
  ///   var strings = args.map((arg) => arg.assertString()).toList();
  ///   return new SassString(strings.map((string) => string.text).join(),
  ///       quotes: strings.any((string) => string.hasQuotes));
  /// });
  /// ```
  ///
  /// Note that the argument list is always an instance of [SassArgumentList],
  /// which provides access to keyword arguments using
  /// [SassArgumentList.keywords].
  factory Callable.function(
    String name,
    String arguments,
    Value callback(List<Value> arguments),
  ) =>
      BuiltInCallable.function(name, arguments, callback);

  /// Creates a callable with a single [signature] and a single [callback].
  ///
  /// Throws a [SassFormatException] if parsing fails.
  factory Callable.fromSignature(
    String signature,
    Value callback(List<Value> arguments), {
    bool requireParens = true,
  }) {
    var (name, declaration) = parseSignature(
      signature,
      requireParens: requireParens,
    );
    return BuiltInCallable.parsed(name, declaration, callback);
  }
}
