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

import 'dart:_internal' show EfficientLengthIterable, patch, unsafeCast;
import 'dart:_js_helper' as js;
import 'dart:_js_types';
import 'dart:_string';
import 'dart:_wasm';
import 'dart:js_interop';
import 'dart:typed_data';

@patch
class String {
  @patch
  factory String.fromCharCodes(
    Iterable<int> charCodes, [
    int start = 0,
    int? end,
  ]) {
    RangeError.checkNotNegative(start, "start");
    if (end != null) {
      if (end < start) {
        throw RangeError.range(end, start, null, "end");
      }
      if (end == start) return "";
    }

    // Skip until `start`.
    final it = charCodes.iterator;
    for (int i = 0; i < start; i++) {
      it.moveNext();
    }

    // The part of the iterable converted to string is collected in a JS typed
    // array, to be able to effciently get subarrays, to pass to
    // `String.fromCharCode.apply`.
    final charCodesLength = (end ?? charCodes.length) - start;
    if (charCodesLength <= 0) return "";
    final typedArrayLength = charCodesLength * 2;
    final list = JSUint32ArrayImpl(typedArrayLength);
    int index = 0; // index in `list`.
    end ??= start + charCodesLength;
    for (int i = start; i < end; i++) {
      if (!it.moveNext()) {
        break;
      }
      final charCode = it.current;
      if (charCode >= 0 && charCode <= 0xffff) {
        list[index++] = charCode;
      } else if (charCode >= 0 && charCode <= 0x10ffff) {
        list[index++] = 0xd800 + ((((charCode - 0x10000) >> 10) & 0x3ff));
        list[index++] = 0xdc00 + (charCode & 0x3ff);
      } else {
        throw RangeError.range(charCode, 0, 0x10ffff);
      }
    }

    // Create JS string from `list`.
    const kMaxApply = 500;
    if (index <= kMaxApply) {
      return _fromCharCodeApplySubarray(list, 0, index);
    }

    String result = '';
    for (int i = 0; i < index; i += kMaxApply) {
      final chunkEnd = (i + kMaxApply < index) ? i + kMaxApply : index;
      result += _fromCharCodeApplySubarray(list, i, chunkEnd);
    }
    return result;
  }

  @patch
  factory String.fromCharCode(int charCode) => _fromCharCode(charCode);

  static String _fromOneByteCharCode(int charCode) =>
      JSStringImpl.fromRefUnchecked(
        js.JS<WasmExternRef?>(
          'c => String.fromCharCode(c)',
          charCode.toDouble(),
        ),
      );

  static String _fromTwoByteCharCode(int low, int high) =>
      JSStringImpl.fromRefUnchecked(
        js.JS<WasmExternRef?>(
          '(l, h) => String.fromCharCode(h, l)',
          low.toDouble(),
          high.toDouble(),
        ),
      );

  static String _fromCharCode(int charCode) {
    if (0 <= charCode) {
      if (charCode <= 0xffff) {
        return _fromOneByteCharCode(charCode);
      }
      if (charCode <= 0x10ffff) {
        var bits = charCode - 0x10000;
        var low = 0xDC00 | (bits & 0x3ff);
        var high = 0xD800 | (bits >> 10);
        return _fromTwoByteCharCode(low, high);
      }
    }
    throw RangeError.range(charCode, 0, 0x10ffff);
  }

  static String _fromCharCodeApplySubarray(
    JSUint32ArrayImpl charCodes,
    int index,
    int end,
  ) {
    return JSStringImpl.fromRefUnchecked(
      js.JS<WasmExternRef?>(
        '(c, i, e) => String.fromCharCode.apply(null, new Uint32Array(c.buffer, c.byteOffset + i, e))',
        charCodes.wrappedExternRef,
        WasmI32.fromInt(index * 4),
        WasmI32.fromInt(end - index),
      ),
    );
  }
}

extension _StringExt on String {
  int firstNonWhitespace() =>
      unsafeCast<JSStringImpl>(this).firstNonWhitespace();

  int lastNonWhitespace() => unsafeCast<JSStringImpl>(this).lastNonWhitespace();
}
