// Copyright (c) 2022, 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 'core_patch.dart';

@patch
class Error {
  @patch
  static String _objectToString(Object object) => Object._toString(object);

  @patch
  static String _stringToSafeString(String string) {
    return jsonEncode(string);
  }

  @pragma('wasm:entry-point')
  static Never _throwWithCurrentStackTrace(Object object) =>
      Error._throw(object, StackTrace.current);

  @patch
  StackTrace? get stackTrace => _stackTrace;

  @pragma("wasm:entry-point")
  StackTrace? _stackTrace;

  @pragma("wasm:entry-point")
  static void _trySetStackTrace(Object object, StackTrace stackTrace) {
    // Guard against implementors of [Error] that do not have the stack trace
    // field by ensuring the error object is a direct/indirect subclass.
    if (isSubClassOf<Error>(object)) {
      final error = unsafeCast<Error>(object);
      error._stackTrace ??= stackTrace;
    }
  }

  @patch
  @pragma("wasm:intrinsic")
  external static Never _throw(Object object, StackTrace stackTrace);
}

class _Error extends Error {
  final String _message;

  _Error(this._message);

  @override
  String toString() => _message;
}

// This error is emitted when we catch an opaque object that was thrown from
// JavaScript.
@pragma("wasm:entry-point")
class _JavaScriptError extends Error {
  final WasmExternRef? _errorRef;

  _JavaScriptError(this._errorRef);

  @pragma("wasm:entry-point")
  factory _JavaScriptError._(WasmExternRef? errorRef) =>
      _JavaScriptError(errorRef);

  @override
  String toString() => JSStringImpl.fromRefUnchecked(
    JS<WasmExternRef?>("(exn) => exn.toString()", _errorRef),
  );

  @override
  @pragma("wasm:entry-point")
  StackTrace get stackTrace =>
      _JavaScriptStack(JS<WasmExternRef?>("(exn) => exn.stack", _errorRef));
}

class _TypeError extends _Error implements TypeError {
  _TypeError(String message) : super(message);

  factory _TypeError.fromMessageAndStackTrace(
    String message,
    StackTrace stackTrace,
  ) {
    final typeError = _TypeError(message);
    typeError._stackTrace = stackTrace;
    return typeError;
  }

  @pragma("wasm:entry-point")
  @pragma("wasm:never-inline")
  static Never _throwNullCheckErrorWithCurrentStack() {
    final stackTrace = StackTrace.current;
    final typeError = _TypeError.fromMessageAndStackTrace(
      "Null check operator used on a null value",
      stackTrace,
    );
    return Error._throw(typeError, stackTrace);
  }

  @pragma("wasm:entry-point")
  static Never _throwAsCheckError(Object? operand, Type? type) {
    final stackTrace = StackTrace.current;
    final typeError = _TypeError.fromMessageAndStackTrace(
      "Type '${operand.runtimeType}' is not a subtype of type '$type'"
      " in type cast",
      stackTrace,
    );
    return Error._throw(typeError, stackTrace);
  }

  @pragma("wasm:entry-point")
  static Never _throwWasmRefError(String expected, StackTrace stackTrace) {
    final typeError = _TypeError.fromMessageAndStackTrace(
      "The Wasm reference is not $expected",
      stackTrace,
    );
    return Error._throw(typeError, stackTrace);
  }

  @pragma("wasm:entry-point")
  static Never _throwArgumentTypeCheckError(
    Object? arg,
    _Type param,
    String paramName,
    StackTrace stackTrace,
  ) {
    final typeError = _TypeError.fromMessageAndStackTrace(
      "type '${arg.runtimeType}' is not a subtype of "
      "type '$param' of '$paramName'",
      stackTrace,
    );
    return Error._throw(typeError, stackTrace);
  }

  @pragma("wasm:entry-point")
  static Never _throwTypeArgumentBoundCheckError(
    _Type param,
    _Type bound,
    String paramName,
    StackTrace stackTrace,
  ) {
    final typeError = _TypeError.fromMessageAndStackTrace(
      "type '$param' is not a subtype of type '$bound' of '$paramName'",
      stackTrace,
    );
    return Error._throw(typeError, stackTrace);
  }
}

@patch
class NoSuchMethodError {
  final Object? _receiver;
  final Symbol _memberName;
  final List? _arguments;
  final Map<Symbol, dynamic>? _namedArguments;
  final List? _existingArgumentNames;

  @patch
  factory NoSuchMethodError.withInvocation(
    Object? receiver,
    Invocation invocation,
  ) => NoSuchMethodError(
    receiver,
    invocation.memberName,
    invocation.positionalArguments,
    invocation.namedArguments,
  );

  NoSuchMethodError(
    Object? receiver,
    Symbol memberName,
    List? positionalArguments,
    Map<Symbol, dynamic>? namedArguments, [
    List? existingArgumentNames = null,
  ]) : _receiver = receiver,
       _memberName = memberName,
       _arguments = positionalArguments,
       _namedArguments = namedArguments,
       _existingArgumentNames = existingArgumentNames;

  @pragma("wasm:entry-point")
  static Never _throwWithInvocation(Object? receiver, Invocation invocation) {
    throw NoSuchMethodError.withInvocation(receiver, invocation);
  }

  @pragma("wasm:entry-point")
  static Never _throwUnimplementedExternalMemberError(
    Object? receiver,
    Symbol memberName,
  ) {
    throw NoSuchMethodError(receiver, memberName, null, null);
  }

  @patch
  String toString() {
    StringBuffer sb = StringBuffer('');
    String comma = '';
    List? arguments = _arguments;
    if (arguments != null) {
      for (var argument in arguments) {
        sb.write(comma);
        sb.write(Error.safeToString(argument));
        comma = ', ';
      }
    }
    Map<Symbol, dynamic>? namedArguments = _namedArguments;
    if (namedArguments != null) {
      namedArguments.forEach((Symbol key, var value) {
        sb.write(comma);
        sb.write(_symbolToString(key));
        sb.write(": ");
        sb.write(Error.safeToString(value));
        comma = ', ';
      });
    }
    String memberName = _symbolToString(_memberName);
    String receiverText = Error.safeToString(_receiver);
    String actualParameters = '$sb';
    List? existingArgumentNames = _existingArgumentNames;
    if (existingArgumentNames == null) {
      return "NoSuchMethodError: method not found: '$memberName'\n"
          "Receiver: ${receiverText}\n"
          "Arguments: [$actualParameters]";
    } else {
      String formalParameters = existingArgumentNames.join(', ');
      return "NoSuchMethodError: incorrect number of arguments passed to "
          "method named '$memberName'\n"
          "Receiver: ${receiverText}\n"
          "Tried calling: $memberName($actualParameters)\n"
          "Found: $memberName($formalParameters)";
    }
  }
}

class _AssertionErrorImpl extends AssertionError {
  _AssertionErrorImpl(
    Object? message,
    this._fileUri,
    this._line,
    this._column,
    this._conditionSource,
  ) : super(message);

  final String? _fileUri;
  final int _line;
  final int _column;
  final String? _conditionSource;

  String toString() {
    var failureMessage = "";
    if (_fileUri != null && _conditionSource != null) {
      failureMessage += "$_fileUri:${_line}:${_column}\n$_conditionSource\n";
    }
    failureMessage += message != null
        ? Error.safeToString(message)
        : "is not true";

    return "Assertion failed: $failureMessage";
  }
}

@patch
class AssertionError {
  @pragma("wasm:entry-point")
  static Never _throwWithMessage(
    Object? message,
    String? fileUri,
    int line,
    int column,
    String? conditionSource,
  ) {
    throw _AssertionErrorImpl(message, fileUri, line, column, conditionSource);
  }
}

/// Used by Fasta to report a runtime error when a final field with an
/// initializer is also initialized in a generative constructor.
///
/// Note: In strong mode, this is a compile-time error, but the CFE still needs
/// this class to exist in `dart:core`.
class _DuplicatedFieldInitializerError extends Error {
  final String _name;

  _DuplicatedFieldInitializerError(this._name);

  toString() => "Error: field '$_name' is already initialized.";
}

@patch
class StateError {
  static _throwNew(String msg) {
    throw StateError(msg);
  }
}

// Implementations needed to implement the `_stackTrace` member added
// in the @patch class of [Error].

@patch
class OutOfMemoryError {
  StackTrace? get _stackTrace =>
      throw UnsupportedError('OutOfMemoryError._stackTrace');
  void set _stackTrace(StackTrace? _) {
    throw UnsupportedError('OutOfMemoryError._stackTrace');
  }
}

@patch
class StackOverflowError {
  StackTrace? get _stackTrace =>
      throw UnsupportedError('StackOverflowError._stackTrace');
  void set _stackTrace(StackTrace? _) {
    throw UnsupportedError('StackOverflowError._stackTrace');
  }
}

@patch
class IntegerDivisionByZeroException {
  StackTrace? get _stackTrace =>
      throw UnsupportedError('IntegerDivisionByZeroException._stackTrace');
  void set _stackTrace(StackTrace? _) {
    throw UnsupportedError('IntegerDivisionByZeroException._stackTrace');
  }
}
