// 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.

/// Helper layer library that includes additional extension methods on existing
/// `package:web` types.
///
/// The extensions here are added by hand over time, depending on needs and use
/// cases. They currently consist of:
///
///  * renames: methods that provide the same functionality, but use a more
///    idiomatic Dart name. Typically these renames match the names used in
///    `dart:html` in the past.
///
//// * operators: for example access local storage items using `operator[]`.
///
///  * helper logic: commonly these are methods that were very useful and
///    popular from `dart:html` (like the context2D getter in
///    `HTMLCanvasElement`).
///
///  * conversions: for example to wrap a `TouchList` as a `List<Touch>`
library;

import 'dart:convert';
import 'dart:js_interop';
import 'dart:math' show Point;

import '../dom.dart';
import 'lists.dart';

export 'cross_origin.dart'
    show CrossOriginContentWindowExtension, CrossOriginWindowExtension;

extension HTMLCanvasElementGlue on HTMLCanvasElement {
  CanvasRenderingContext2D get context2D =>
      getContext('2d') as CanvasRenderingContext2D;

  String toDataUrl(String type, [num? quality]) =>
      (quality == null) ? toDataURL(type) : toDataURL(type, quality.toJS);

  RenderingContext? getContext3d(
      {bool alpha = true,
      bool depth = true,
      bool stencil = false,
      bool antialias = true,
      bool premultipliedAlpha = true,
      bool preserveDrawingBuffer = false}) {
    final options = {
      'alpha': alpha,
      'depth': depth,
      'stencil': stencil,
      'antialias': antialias,
      'premultipliedAlpha': premultipliedAlpha,
      'preserveDrawingBuffer': preserveDrawingBuffer,
    }.jsify();
    return getContext('webgl', options) ??
        getContext('experimental-webgl', options);
  }
}

extension CanvasRenderingContext2DGlue on CanvasRenderingContext2D {
  void drawImageScaled(
    CanvasImageSource image,
    double dx,
    double dy,
    double dw,
    double dh,
  ) =>
      drawImage(image, dx, dy, dw, dh);
}

extension NodeGlue on Node {
  set text(String s) => textContent = s;
  @Deprecated('See Node.appendChild()')
  Node append(Node other) => appendChild(other);
  @Deprecated('See Node.cloneNode()')
  Node clone(bool? deep) => cloneNode(deep ?? false);
}

extension EventGlue on MouseEvent {
  /// A [Point] representation of the [clientX] and [clientY] properties
  /// of this [MouseEvent].
  ///
  /// **Deprecated:** Prefer directly accessing
  /// the [clientX] and [clientY] properties on [MouseEvent].
  @Deprecated('Instead directly access the clientX and clientY properties.')
  Point get client => Point(clientX, clientY);
}

extension TouchGlue on Touch {
  /// A [Point] representation of the [clientX] and [clientY] properties
  /// of this [Touch] event.
  ///
  /// **Deprecated:** Prefer directly accessing
  /// the [clientX] and [clientY] properties on [Touch].
  @Deprecated('Instead directly access the clientX and clientY properties.')
  Point get client => Point(clientX, clientY);
}

extension StorageGlue on Storage {
  String? operator [](String key) => getItem(key);
  void operator []=(String key, String value) => setItem(key, value);
}

@Deprecated('Use JSImmutableListWrapper<TouchList, Touch> instead.')
extension TouchListConvert on TouchList {
  @Deprecated('Use JSImmutableListWrapper<TouchList, Touch> directly instead.')
  List<Touch> toList() => JSImmutableListWrapper<TouchList, Touch>(this);
}

extension XMLHttpRequestGlue on XMLHttpRequest {
  /// Returns all response headers as a key-value map.
  ///
  /// Multiple values for the same header key can be combined into one,
  /// separated by a comma and a space.
  ///
  /// See: https://xhr.spec.whatwg.org/#the-getresponseheader()-method
  Map<String, String> get responseHeaders {
    // from Closure's goog.net.Xhrio.getResponseHeaders.
    final headers = <String, String>{};
    final headersString = getAllResponseHeaders();
    final headersList =
        LineSplitter.split(headersString).where((header) => header.isNotEmpty);
    for (final header in headersList) {
      final split = header.split(': ');
      if (split.length <= 1) {
        continue;
      }
      final key = split[0].toLowerCase();
      final value = split.skip(1).join(': ');
      headers.update(
        key,
        (oldValue) => '$oldValue, $value',
        ifAbsent: () => value,
      );
    }
    return headers;
  }
}
