// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// @docImport 'gesture_detector.dart';
library;

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';

import '_html_element_view_io.dart' if (dart.library.js_interop) '_html_element_view_web.dart';
import 'basic.dart';
import 'debug.dart';
import 'focus_manager.dart';
import 'focus_scope.dart';
import 'framework.dart';

// Examples can assume:
// PlatformViewController createFooWebView(PlatformViewCreationParams params) { return (null as dynamic) as PlatformViewController; }
// Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers = <Factory<OneSequenceGestureRecognizer>>{};
// late PlatformViewController _controller;
// void myOnElementCreated(Object element) {}
// void myOnPlatformViewCreated(int viewId) {}

/// Embeds an Android view in the Widget hierarchy.
///
/// Requires Android API level 23 or greater.
///
/// Embedding Android views is an expensive operation and should be avoided when a Flutter
/// equivalent is possible.
///
/// The embedded Android view is painted just like any other Flutter widget and transformations
/// apply to it as well.
///
/// {@template flutter.widgets.AndroidView.layout}
/// The widget fills all available space, the parent of this object must provide bounded layout
/// constraints.
/// {@endtemplate}
///
/// {@template flutter.widgets.AndroidView.gestures}
/// The widget participates in Flutter's gesture arenas, and dispatches touch events to the
/// platform view iff it won the arena. Specific gestures that should be dispatched to the platform
/// view can be specified in the `gestureRecognizers` constructor parameter. If
/// the set of gesture recognizers is empty, a gesture will be dispatched to the platform
/// view iff it was not claimed by any other gesture recognizer.
/// {@endtemplate}
///
/// The Android view object is created using a [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html).
/// Plugins can register platform view factories with [PlatformViewRegistry#registerViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html#registerViewFactory-java.lang.String-io.flutter.plugin.platform.PlatformViewFactory-).
///
/// Registration is typically done in the plugin's registerWith method, e.g:
///
/// ```java
///   public static void registerWith(Registrar registrar) {
///     registrar.platformViewRegistry().registerViewFactory("webview", WebViewFactory(registrar.messenger()));
///   }
/// ```
///
/// {@template flutter.widgets.AndroidView.lifetime}
/// The platform view's lifetime is the same as the lifetime of the [State] object for this widget.
/// When the [State] is disposed the platform view (and auxiliary resources) are lazily
/// released (some resources are immediately released and some by platform garbage collector).
/// A stateful widget's state is disposed when the widget is removed from the tree or when it is
/// moved within the tree. If the stateful widget has a key and it's only moved relative to its siblings,
/// or it has a [GlobalKey] and it's moved within the tree, it will not be disposed.
/// {@endtemplate}
class AndroidView extends StatefulWidget {
  /// Creates a widget that embeds an Android view.
  ///
  /// {@template flutter.widgets.AndroidView.constructorArgs}
  /// If `creationParams` is not null then `creationParamsCodec` must not be null.
  /// {@endtemplate}
  const AndroidView({
    super.key,
    required this.viewType,
    this.onPlatformViewCreated,
    this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
    this.layoutDirection,
    this.gestureRecognizers,
    this.creationParams,
    this.creationParamsCodec,
    this.clipBehavior = Clip.hardEdge,
  }) : assert(creationParams == null || creationParamsCodec != null);

  /// The unique identifier for Android view type to be embedded by this widget.
  ///
  /// A [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html)
  /// for this type must have been registered.
  ///
  /// See also:
  ///
  ///  * [AndroidView] for an example of registering a platform view factory.
  final String viewType;

  /// {@template flutter.widgets.AndroidView.onPlatformViewCreated}
  /// Callback to invoke after the platform view has been created.
  ///
  /// May be null.
  /// {@endtemplate}
  final PlatformViewCreatedCallback? onPlatformViewCreated;

  /// {@template flutter.widgets.AndroidView.hitTestBehavior}
  /// How this widget should behave during hit testing.
  ///
  /// This defaults to [PlatformViewHitTestBehavior.opaque].
  /// {@endtemplate}
  final PlatformViewHitTestBehavior hitTestBehavior;

  /// {@template flutter.widgets.AndroidView.layoutDirection}
  /// The text direction to use for the embedded view.
  ///
  /// If this is null, the ambient [Directionality] is used instead.
  /// {@endtemplate}
  final TextDirection? layoutDirection;

  /// Which gestures should be forwarded to the Android view.
  ///
  /// {@template flutter.widgets.AndroidView.gestureRecognizers.descHead}
  /// The gesture recognizers built by factories in this set participate in the gesture arena for
  /// each pointer that was put down on the widget. If any of these recognizers win the
  /// gesture arena, the entire pointer event sequence starting from the pointer down event
  /// will be dispatched to the platform view.
  ///
  /// When null, an empty set of gesture recognizer factories is used, in which case a pointer event sequence
  /// will only be dispatched to the platform view if no other member of the arena claimed it.
  /// {@endtemplate}
  ///
  /// For example, with the following setup vertical drags will not be dispatched to the Android
  /// view as the vertical drag gesture is claimed by the parent [GestureDetector].
  ///
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails d) {},
  ///   child: const AndroidView(
  ///     viewType: 'webview',
  ///   ),
  /// )
  /// ```
  ///
  /// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag
  /// gesture recognizer factory in [gestureRecognizers] e.g:
  ///
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails details) {},
  ///   child: SizedBox(
  ///     width: 200.0,
  ///     height: 100.0,
  ///     child: AndroidView(
  ///       viewType: 'webview',
  ///       gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
  ///         Factory<OneSequenceGestureRecognizer>(
  ///           () => EagerGestureRecognizer(),
  ///         ),
  ///       },
  ///     ),
  ///   ),
  /// )
  /// ```
  ///
  /// {@template flutter.widgets.AndroidView.gestureRecognizers.descFoot}
  /// A platform view can be configured to consume all pointers that were put
  /// down in its bounds by passing a factory for an [EagerGestureRecognizer] in
  /// [gestureRecognizers]. [EagerGestureRecognizer] is a special gesture
  /// recognizer that immediately claims the gesture after a pointer down event.
  ///
  /// The [gestureRecognizers] property must not contain more than one factory
  /// with the same [Factory.type].
  ///
  /// Changing [gestureRecognizers] results in rejection of any active gesture
  /// arenas (if the platform view is actively participating in an arena).
  /// {@endtemplate}
  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
  // TODO(amirh): get a list of GestureRecognizers here.
  // https://github.com/flutter/flutter/issues/20953
  final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;

  /// Passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-)
  ///
  /// This can be used by plugins to pass constructor parameters to the embedded Android view.
  final dynamic creationParams;

  /// The codec used to encode `creationParams` before sending it to the
  /// platform side. It should match the codec passed to the constructor of [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-).
  ///
  /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
  ///
  /// This must not be null if [creationParams] is not null.
  final MessageCodec<dynamic>? creationParamsCodec;

  /// {@macro flutter.material.Material.clipBehavior}
  ///
  /// Defaults to [Clip.hardEdge].
  final Clip clipBehavior;

  @override
  State<AndroidView> createState() => _AndroidViewState();
}

/// Common superclass for iOS and macOS platform views.
///
/// Platform views are used to embed native views in the widget hierarchy, with
/// support for transforms, clips, and opacity similar to any other Flutter widget.
abstract class _DarwinView extends StatefulWidget {
  /// Creates a widget that embeds a platform view.
  ///
  /// {@macro flutter.widgets.AndroidView.constructorArgs}
  const _DarwinView({
    super.key,
    required this.viewType,
    this.onPlatformViewCreated,
    this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
    this.layoutDirection,
    this.creationParams,
    this.creationParamsCodec,
    this.gestureRecognizers,
  }) : assert(creationParams == null || creationParamsCodec != null);

  // TODO(amirh): reference the iOS API doc once available.
  /// The unique identifier for iOS view type to be embedded by this widget.
  ///
  /// A PlatformViewFactory for this type must have been registered.
  final String viewType;

  /// {@macro flutter.widgets.AndroidView.onPlatformViewCreated}
  final PlatformViewCreatedCallback? onPlatformViewCreated;

  /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
  final PlatformViewHitTestBehavior hitTestBehavior;

  /// {@macro flutter.widgets.AndroidView.layoutDirection}
  final TextDirection? layoutDirection;

  /// Passed as the `arguments` argument of [-\[FlutterPlatformViewFactory createWithFrame:viewIdentifier:arguments:\]](/ios-embedder/protocol_flutter_platform_view_factory-p.html#a4e3c4390cd6ebd982390635e9bca4edc)
  ///
  /// This can be used by plugins to pass constructor parameters to the embedded iOS view.
  final dynamic creationParams;

  /// The codec used to encode `creationParams` before sending it to the
  /// platform side. It should match the codec returned by [-\[FlutterPlatformViewFactory createArgsCodec:\]](/ios-embedder/protocol_flutter_platform_view_factory-p.html#a32c3c067cb45a83dfa720c74a0d5c93c)
  ///
  /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
  ///
  /// This must not be null if [creationParams] is not null.
  final MessageCodec<dynamic>? creationParamsCodec;

  /// Which gestures should be forwarded to the UIKit view.
  ///
  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead}
  ///
  /// For example, with the following setup vertical drags will not be dispatched to the UIKit
  /// view as the vertical drag gesture is claimed by the parent [GestureDetector].
  ///
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails details) {},
  ///   child: const UiKitView(
  ///     viewType: 'webview',
  ///   ),
  /// )
  /// ```
  ///
  /// To get the [UiKitView] to claim the vertical drag gestures we can pass a vertical drag
  /// gesture recognizer factory in [gestureRecognizers] e.g:
  ///
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails details) {},
  ///   child: SizedBox(
  ///     width: 200.0,
  ///     height: 100.0,
  ///     child: UiKitView(
  ///       viewType: 'webview',
  ///       gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
  ///         Factory<OneSequenceGestureRecognizer>(
  ///           () => EagerGestureRecognizer(),
  ///         ),
  ///       },
  ///     ),
  ///   ),
  /// )
  /// ```
  ///
  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot}
  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
  // TODO(amirh): get a list of GestureRecognizers here.
  // https://github.com/flutter/flutter/issues/20953
  final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
}

// TODO(amirh): describe the embedding mechanism.
// TODO(ychris): remove the documentation for conic path not supported once https://github.com/flutter/flutter/issues/35062 is resolved.
/// Embeds an iOS view in the Widget hierarchy.
///
/// Embedding iOS views is an expensive operation and should be avoided when a Flutter
/// equivalent is possible.
///
/// {@macro flutter.widgets.AndroidView.layout}
///
/// {@macro flutter.widgets.AndroidView.gestures}
///
/// {@macro flutter.widgets.AndroidView.lifetime}
///
/// Construction of UIViews is done asynchronously, before the UIView is ready this widget paints
/// nothing while maintaining the same layout constraints.
///
/// Clipping operations on a UiKitView can result slow performance.
/// If a conic path clipping is applied to a UIKitView,
/// a quad path is used to approximate the clip due to limitation of Quartz.
class UiKitView extends _DarwinView {
  /// Creates a widget that embeds an iOS view.
  ///
  /// {@macro flutter.widgets.AndroidView.constructorArgs}
  const UiKitView({
    super.key,
    required super.viewType,
    super.onPlatformViewCreated,
    super.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
    super.layoutDirection,
    super.creationParams,
    super.creationParamsCodec,
    super.gestureRecognizers,
  }) : assert(creationParams == null || creationParamsCodec != null);

  @override
  State<UiKitView> createState() => _UiKitViewState();
}

/// Widget that contains a macOS AppKit view.
///
/// Embedding macOS views is an expensive operation and should be avoided where
/// a Flutter equivalent is possible.
///
/// The platform view's lifetime is the same as the lifetime of the [State]
/// object for this widget. When the [State] is disposed the platform view (and
/// auxiliary resources) are lazily released (some resources are immediately
/// released and some by platform garbage collector). A stateful widget's state
/// is disposed when the widget is removed from the tree or when it is moved
/// within the tree. If the stateful widget has a key and it's only moved
/// relative to its siblings, or it has a [GlobalKey] and it's moved within the
/// tree, it will not be disposed.
///
/// Construction of AppKitViews is done asynchronously, before the underlying
/// NSView is ready this widget paints nothing while maintaining the same
/// layout constraints.
class AppKitView extends _DarwinView {
  /// Creates a widget that embeds a macOS AppKit NSView.
  const AppKitView({
    super.key,
    required super.viewType,
    super.onPlatformViewCreated,
    super.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
    super.layoutDirection,
    super.creationParams,
    super.creationParamsCodec,
    super.gestureRecognizers,
  });

  @override
  State<AppKitView> createState() => _AppKitViewState();
}

/// The signature of the function that gets called when the [HtmlElementView]
/// DOM element is created.
///
/// [element] is the DOM element that was created.
///
/// This callback is called before [element] is attached to the DOM, so it can
/// be modified as needed by the Flutter web application.
///
/// See [HtmlElementView.fromTagName] that receives a callback of this type.
///
/// {@template flutter.widgets.web.JSInterop.object}
/// Flutter uses type `Object` so this API doesn't force any JS interop API
/// implementation to Flutter users. This `element` can be cast to any compatible
/// JS interop type as needed. For example: `JSAny` (from `dart:js_interop`),
/// `HTMLElement` (from `package:web`) or any custom JS interop definition.
/// See "Next-generation JS interop": https://dart.dev/interop/js-interop
/// {@endtemplate}
typedef ElementCreatedCallback = void Function(Object element);

/// Embeds an HTML element in the Widget hierarchy in Flutter web.
///
/// The embedded HTML is laid out like any other Flutter widget and
/// transformations (like opacity, and clipping) apply to it as well.
///
/// {@macro flutter.widgets.AndroidView.layout}
///
/// Embedding HTML is a _potentially expensive_ operation and should be avoided
/// when a Flutter equivalent is possible. (See **`isVisible` parameter** below.)
/// This widget is useful to integrate native HTML elements to a Flutter web app,
/// like a `<video>` tag, or a `<div>` where a [Google Map](https://pub.dev/packages/google_maps_flutter)
/// can be rendered.
///
/// This widget **only works on Flutter web.** To embed web content on other
/// platforms, consider using the [`webview_flutter` plugin](https://pub.dev/packages/webview_flutter).
///
/// ## Usage
///
/// There's two ways to use the `HtmlElementView` widget:
///
/// ### `HtmlElementView.fromTagName`
///
/// The [HtmlElementView.fromTagName] constructor creates the HTML element
/// specified by `tagName`, and passes it to the `onElementCreated` callback
/// where it can be customized:
///
/// ```dart
/// // In a `build` method...
/// HtmlElementView.fromTagName(
///   tagName: 'div',
///   onElementCreated: myOnElementCreated,
/// );
/// ```
///
/// The example creates a `<div>` element, then calls the `onElementCreated`
/// callback with the created `<div>`, so it can be customized **before** it is
/// attached to the DOM.
///
/// (See more details about `onElementCreated` in the **Lifecycle** section below.)
///
/// ### Using the `PlatformViewRegistry`
///
/// The primitives used to implement [HtmlElementView.fromTagName] are available
/// for general use through `dart:ui_web`'s `platformViewRegistry`.
///
/// Creating an `HtmlElementView` through these primitives is a two step process:
///
/// #### 1. `registerViewFactory`
///
/// First, a `viewFactory` function needs to be registered for a given `viewType`.
/// Flutter web will call this factory function to create the `element` that will
/// be attached later:
///
/// ```dart
/// import 'dart:ui_web' as ui_web;
/// import 'package:web/web.dart' as web;
///
/// void registerRedDivFactory() {
///   ui_web.platformViewRegistry.registerViewFactory(
///     'my-view-type',
///     (int viewId, {Object? params}) {
///       // Create and return an HTML Element from here
///       final web.HTMLDivElement myDiv = web.HTMLDivElement()
///         ..id = 'some_id_$viewId'
///         ..style.backgroundColor = 'red'
///         ..style.width = '100%'
///         ..style.height = '100%';
///       return myDiv;
///     },
///   );
/// }
/// ```
///
/// `registerViewFactory` **must** be called outside of `build` methods, so the
/// registered function is available when `build` happens.
///
/// See the different types of functions that can be used as `viewFactory`:
///
/// * [`typedef ui_web.PlatformViewFactory`](https://api.flutter.dev/flutter/dart-ui_web/PlatformViewFactory.html)
/// * [`typedef ui_web.ParameterizedPlatformViewFactory`](https://api.flutter.dev/flutter/dart-ui_web/ParameterizedPlatformViewFactory.html)
///
/// #### 2. `HtmlElementView` widget
///
/// Once a factory is registered, an `HtmlElementView` widget of `viewType` can
/// be added to the widget tree, like so:
///
/// ```dart
/// // In a `build` method...
/// const HtmlElementView(
///   viewType: 'my-view-type',
///   onPlatformViewCreated: myOnPlatformViewCreated,
///   creationParams: <String, Object?>{
///     'key': 'someValue',
///   },
/// );
/// ```
///
/// [viewType] **must** match the value used to `registerViewFactory` before.
///
/// [creationParams] (optional) will be passed to your `viewFactory` function,
/// if it accepts them.
///
/// [onPlatformViewCreated] will be called with the `viewId` of the platform
/// view (`element`) created by the `viewFactory`, before it gets attached to
/// the DOM.
///
/// The `viewId` can be used to retrieve the created `element` (The same one
/// passed to `onElementCreated` in [HtmlElementView.fromTagName]) with the
/// `ui_web.platformViewRegistry.`[`getViewById` method](https://api.flutter.dev/flutter/dart-ui_web/PlatformViewRegistry/getViewById.html).
///
/// (See more details about `onPlatformViewCreated` in the **Lifecycle** section
/// below.)
///
/// ## Lifecycle
///
/// `HtmlElementView` widgets behave like any other Flutter stateless widget, but
/// with an additional lifecycle method: `onPlatformViewCreated` / `onElementCreated`
/// (depending on the constructor, see **Usage** above).
///
/// The difference between the two callbacks is the parameter they receive:
///
/// * `onPlatformViewCreated` will be called with the created `viewId` as a parameter,
///   and needs `ui_web.platformViewRegistry.getViewById` to retrieve the created
///   element (See [PlatformViewCreatedCallback]).
/// * `onElementCreated` will be called with the created `element` directly,
///   skipping its `viewId` (See [ElementCreatedCallback]).
///
/// Both callbacks are called **after** the HTML `element` has been created, but
/// **before** it's attached to the DOM.
///
/// ### HTML Lifecycle
///
/// The Browser DOM APIs have additional HTML lifecycle callbacks for the root
/// `element` of an `HtmlElementView`.
///
/// #### Element Attached To The DOM
///
/// It is common for JS code to locate the DOM elements they need with a
/// selector, rather than accepting said DOM elements directly. In those cases,
/// the `element` **must** be attached to the DOM for the selector to work.
///
/// The example below demonstrates **how to create an `onElementAttached` function**
/// that gets called when the root `element` is attached to the DOM using a
/// `ResizeObserver` through `package:web` from the `onElementCreated` lifecycle
/// method:
///
/// ```dart
/// import 'dart:js_interop';
/// import 'package:web/web.dart' as web;
///
/// // Called after `element` is attached to the DOM.
/// void onElementAttached(web.HTMLDivElement element) {
///   final web.Element? located = web.document.querySelector('#someIdThatICanFindLater');
///   assert(located == element, 'Wrong `element` located!');
///   // Do things with `element` or `located`, or call your code now...
///   element.style.backgroundColor = 'green';
/// }
///
/// void onElementCreated(Object element) {
///   element as web.HTMLDivElement;
///   element.style.backgroundColor = 'red';
///   element.id = 'someIdThatICanFindLater';
///
///   // Create the observer
///   final web.ResizeObserver observer = web.ResizeObserver((
///     JSArray<web.ResizeObserverEntry> entries,
///     web.ResizeObserver observer,
///   ) {
///     if (element.isConnected) {
///       // The observer is done, disconnect it.
///       observer.disconnect();
///       // Call our callback.
///       onElementAttached(element);
///     }
///   }.toJS);
///
///   // Connect the observer.
///   observer.observe(element);
/// }
/// ```
///
/// * Read more about [`ResizeObserver` in the MDN](https://developer.mozilla.org/en-US/docs/Web/API/Resize_Observer_API).
///
/// #### Other Observers
///
/// The example above uses a `ResizeObserver` because it can be applied directly
/// to the `element` that is about to be attached. Another observer that could
/// be used for this (with a little bit more code) would be a
/// [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
///
/// The `MutationObserver` requires the parent element in which the `HtmlElementView`
/// is going to be inserted. A safe way to retrieve a parent element for the
/// platform view is to retrieve the `hostElement` of the [FlutterView] where the
/// `HtmlElementView` is being rendered.
///
/// The `hostElement` of the current [FlutterView] can be retrieved through:
///
/// ```dart
/// import 'dart:js_interop';
/// import 'dart:ui_web' as ui_web;
/// import 'package:flutter/widgets.dart';
///
/// void useHostElement(BuildContext context) {
///   final int flutterViewId = View.of(context).viewId;
///   final JSAny? hostElement = ui_web.views.getHostElement(flutterViewId);
///   // Use `package:web` with `hostElement`...
/// }
/// ```
///
/// **Important:** `FlutterView.viewId` and the `viewId` parameter passed to
/// the `viewFactory` identify **different objects**:
///
/// * `flutterViewId` (from `View.of(context)`) represents the [FlutterView]
///   where the web app is currently rendering.
/// * `viewId` (passed to the `viewFactory` function) represents a unique ID
///   for the `HtmlElementView` instance that is being attached to the app.
///
/// Read more about [FlutterView] on Flutter's API docs:
///
/// * [`View.of`](https://api.flutter.dev/flutter/widgets/View/of.html)
/// * [`getHostElement`](https://main-api.flutter.dev/flutter/dart-ui_web/FlutterViewManagerProxy/getHostElement.html)
///
/// ## Pointer events
///
/// In order for the `HtmlElementView` contents to be interactive, they're allowed
/// to handle `pointer-events`. This may result in Flutter missing some events
/// because they've been handled by the `HtmlElementView`, and not seen by
/// Flutter.
///
/// [`package:pointer_interceptor`](https://pub.dev/packages/pointer_interceptor)
/// may help in some cases where Flutter content needs to be overlaid on top of
/// an `HtmlElementView`. Alternatively, the `pointer-events: none` property can
/// be set `onElementCreated`; but that will prevent **ALL** interactions with
/// the underlying HTML content.
///
/// If the `HtmlElementView` is an `<iframe>` element, Flutter will not receive
/// pointer events that land in the `<iframe>` (click/tap, drag, drop, etc.)
/// In those cases, the `HtmlElementView` will seem like it's _swallowing_
/// the events and not participating in Flutter's gesture detection.
///
/// ## `isVisible` parameter
///
/// Rendering custom HTML content (from `HtmlElementView`) in between `canvas`
/// pixels means that the Flutter web engine needs to _split_ the canvas drawing
/// into elements drawn _behind_ the HTML content, and those drawn _above_ it.
///
/// In the Flutter web engine, each of these _splits of the canvas to sandwich
/// HTML content in between_ is referred to as an **overlay**.
///
/// Each _overlay_ present in a scene has implications both in memory and
/// execution performance, and it is best to minimize their amount; browsers
/// support a limited number of _overlays_ on a single scene at a given time.
///
/// `HtmlElementView` objects have an `isVisible` property that can be passed
/// through `registerViewFactory`, or `fromTagName`. `isVisible` refers
/// to whether the `HtmlElementView` will paint pixels on the screen or not.
///
/// Correctly defining this value helps the Flutter web rendering engine optimize
/// the amount of _overlays_ it'll need to render a particular scene.
///
/// In general, `isVisible` should be left to its default value of `true`, but
/// in some `HtmlElementView`s (like the `pointer_interceptor` or `Link` widget),
/// it can be set to `false`, so the engine doesn't _waste_ an overlay to render
/// Flutter content on top of views that don't paint any pixels.
class HtmlElementView extends StatelessWidget {
  /// Creates a platform view for Flutter web.
  ///
  /// `viewType` identifies the type of platform view to create.
  const HtmlElementView({
    super.key,
    required this.viewType,
    this.onPlatformViewCreated,
    this.creationParams,
    this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
  });

  /// Creates a platform view that creates a DOM element specified by [tagName].
  ///
  /// [isVisible] indicates whether the view is visible to the user or not.
  /// Setting this to false allows the rendering pipeline to perform extra
  /// optimizations knowing that the view will not result in any pixels painted
  /// on the screen.
  ///
  /// [onElementCreated] is called when the DOM element is created. It can be
  /// used by the app to customize the element by adding attributes and styles.
  /// This method is called *before* the element is attached to the DOM.
  factory HtmlElementView.fromTagName({
    Key? key,
    required String tagName,
    bool isVisible = true,
    ElementCreatedCallback? onElementCreated,
    PlatformViewHitTestBehavior hitTestBehavior = PlatformViewHitTestBehavior.opaque,
  }) => HtmlElementViewImpl.createFromTagName(
    key: key,
    tagName: tagName,
    isVisible: isVisible,
    onElementCreated: onElementCreated,
    hitTestBehavior: hitTestBehavior,
  );

  /// The unique identifier for the HTML view type to be embedded by this widget.
  ///
  /// A PlatformViewFactory for this type must have been registered.
  final String viewType;

  /// Callback to invoke after the platform view has been created.
  ///
  /// This method is called *before* the platform view is attached to the DOM.
  ///
  /// May be null.
  final PlatformViewCreatedCallback? onPlatformViewCreated;

  /// Passed as the 2nd argument (i.e. `params`) of the registered view factory.
  final Object? creationParams;

  /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
  final PlatformViewHitTestBehavior hitTestBehavior;

  @override
  Widget build(BuildContext context) => buildImpl(context);
}

class _AndroidViewState extends State<AndroidView> {
  int? _id;
  late AndroidViewController _controller;
  TextDirection? _layoutDirection;
  bool _initialized = false;
  FocusNode? _focusNode;

  static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
      <Factory<OneSequenceGestureRecognizer>>{};

  @override
  Widget build(BuildContext context) {
    return Focus(
      focusNode: _focusNode,
      onFocusChange: _onFocusChange,
      child: _AndroidPlatformView(
        controller: _controller,
        hitTestBehavior: widget.hitTestBehavior,
        gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
        clipBehavior: widget.clipBehavior,
      ),
    );
  }

  void _initializeOnce() {
    if (_initialized) {
      return;
    }
    _initialized = true;
    _createNewAndroidView();
    _focusNode = FocusNode(debugLabel: 'AndroidView(id: $_id)');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final TextDirection newLayoutDirection = _findLayoutDirection();
    final didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
    _layoutDirection = newLayoutDirection;

    _initializeOnce();
    if (didChangeLayoutDirection) {
      // The native view will update asynchronously, in the meantime we don't want
      // to block the framework. (so this is intentionally not awaiting).
      _controller.setLayoutDirection(_layoutDirection!);
    }
  }

  @override
  void didUpdateWidget(AndroidView oldWidget) {
    super.didUpdateWidget(oldWidget);

    final TextDirection newLayoutDirection = _findLayoutDirection();
    final didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
    _layoutDirection = newLayoutDirection;

    if (widget.viewType != oldWidget.viewType) {
      _controller.disposePostFrame();
      _createNewAndroidView();
      return;
    }

    if (didChangeLayoutDirection) {
      _controller.setLayoutDirection(_layoutDirection!);
    }
  }

  TextDirection _findLayoutDirection() {
    assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
    return widget.layoutDirection ?? Directionality.of(context);
  }

  @override
  void dispose() {
    _controller.dispose();
    _focusNode?.dispose();
    _focusNode = null;
    super.dispose();
  }

  void _createNewAndroidView() {
    _id = platformViewsRegistry.getNextPlatformViewId();
    _controller = PlatformViewsService.initAndroidView(
      id: _id!,
      viewType: widget.viewType,
      layoutDirection: _layoutDirection!,
      creationParams: widget.creationParams,
      creationParamsCodec: widget.creationParamsCodec,
      onFocus: () {
        _focusNode!.requestFocus();
      },
    );
    if (widget.onPlatformViewCreated != null) {
      _controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!);
    }
  }

  void _onFocusChange(bool isFocused) {
    if (!_controller.isCreated) {
      return;
    }
    if (!isFocused) {
      _controller.clearFocus().catchError((dynamic e) {
        if (e is MissingPluginException) {
          // We land the framework part of Android platform views keyboard
          // support before the engine part. There will be a commit range where
          // clearFocus isn't implemented in the engine. When that happens we
          // just swallow the error here. Once the engine part is rolled to the
          // framework I'll remove this.
          // TODO(amirh): remove this once the engine's clearFocus is rolled.
          return;
        }
      });
      return;
    }
    SystemChannels.textInput
        .invokeMethod<void>('TextInput.setPlatformViewClient', <String, dynamic>{
          'platformViewId': _id,
        })
        .catchError((dynamic e) {
          if (e is MissingPluginException) {
            // We land the framework part of Android platform views keyboard
            // support before the engine part. There will be a commit range where
            // setPlatformViewClient isn't implemented in the engine. When that
            // happens we just swallow the error here. Once the engine part is
            // rolled to the framework I'll remove this.
            // TODO(amirh): remove this once the engine's clearFocus is rolled.
            return;
          }
        });
  }
}

abstract class _DarwinViewState<
  PlatformViewT extends _DarwinView,
  ControllerT extends DarwinPlatformViewController,
  RenderT extends RenderDarwinPlatformView<ControllerT>,
  ViewT extends _DarwinPlatformView<ControllerT, RenderT>
>
    extends State<PlatformViewT> {
  ControllerT? _controller;
  TextDirection? _layoutDirection;
  bool _initialized = false;

  @visibleForTesting
  FocusNode? focusNode;

  static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
      <Factory<OneSequenceGestureRecognizer>>{};

  @override
  Widget build(BuildContext context) {
    final ControllerT? controller = _controller;
    if (controller == null) {
      return const SizedBox.expand();
    }
    return Focus(
      focusNode: focusNode,
      onFocusChange: (bool isFocused) => _onFocusChange(isFocused, controller),
      child: childPlatformView(),
    );
  }

  ViewT childPlatformView();

  void _initializeOnce() {
    if (_initialized) {
      return;
    }
    _initialized = true;
    _createNewUiKitView();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final TextDirection newLayoutDirection = _findLayoutDirection();
    final didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
    _layoutDirection = newLayoutDirection;

    _initializeOnce();
    if (didChangeLayoutDirection) {
      // The native view will update asynchronously, in the meantime we don't want
      // to block the framework. (so this is intentionally not awaiting).
      _controller?.setLayoutDirection(_layoutDirection!);
    }
  }

  @override
  void didUpdateWidget(PlatformViewT oldWidget) {
    super.didUpdateWidget(oldWidget);

    final TextDirection newLayoutDirection = _findLayoutDirection();
    final didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
    _layoutDirection = newLayoutDirection;

    if (widget.viewType != oldWidget.viewType) {
      _controller?.dispose();
      _controller = null;
      focusNode?.dispose();
      focusNode = null;
      _createNewUiKitView();
      return;
    }

    if (didChangeLayoutDirection) {
      _controller?.setLayoutDirection(_layoutDirection!);
    }
  }

  TextDirection _findLayoutDirection() {
    assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
    return widget.layoutDirection ?? Directionality.of(context);
  }

  @override
  void dispose() {
    _controller?.dispose();
    _controller = null;
    focusNode?.dispose();
    focusNode = null;
    super.dispose();
  }

  Future<void> _createNewUiKitView() async {
    final int id = platformViewsRegistry.getNextPlatformViewId();
    final ControllerT controller = await createNewViewController(id);
    if (!mounted) {
      controller.dispose();
      return;
    }
    widget.onPlatformViewCreated?.call(id);
    setState(() {
      _controller = controller;
      focusNode = FocusNode(debugLabel: 'UiKitView(id: $id)');
    });
  }

  Future<ControllerT> createNewViewController(int id);

  void _onFocusChange(bool isFocused, ControllerT controller) {
    if (!isFocused) {
      // Unlike Android, we do not need to send "clearFocus" channel message
      // to the engine, because focusing on another view will automatically
      // cancel the focus on the previously focused platform view.
      return;
    }
    SystemChannels.textInput.invokeMethod<void>(
      'TextInput.setPlatformViewClient',
      <String, dynamic>{'platformViewId': controller.id},
    );
  }
}

class _UiKitViewState
    extends _DarwinViewState<UiKitView, UiKitViewController, RenderUiKitView, _UiKitPlatformView> {
  @override
  Future<UiKitViewController> createNewViewController(int id) async {
    return PlatformViewsService.initUiKitView(
      id: id,
      viewType: widget.viewType,
      layoutDirection: _layoutDirection!,
      creationParams: widget.creationParams,
      creationParamsCodec: widget.creationParamsCodec,
      onFocus: () {
        focusNode?.requestFocus();
      },
    );
  }

  @override
  _UiKitPlatformView childPlatformView() {
    return _UiKitPlatformView(
      controller: _controller!,
      hitTestBehavior: widget.hitTestBehavior,
      gestureRecognizers: widget.gestureRecognizers ?? _DarwinViewState._emptyRecognizersSet,
    );
  }
}

class _AppKitViewState
    extends
        _DarwinViewState<AppKitView, AppKitViewController, RenderAppKitView, _AppKitPlatformView> {
  @override
  Future<AppKitViewController> createNewViewController(int id) async {
    return PlatformViewsService.initAppKitView(
      id: id,
      viewType: widget.viewType,
      layoutDirection: _layoutDirection!,
      creationParams: widget.creationParams,
      creationParamsCodec: widget.creationParamsCodec,
      onFocus: () {
        focusNode?.requestFocus();
      },
    );
  }

  @override
  _AppKitPlatformView childPlatformView() {
    return _AppKitPlatformView(
      controller: _controller!,
      hitTestBehavior: widget.hitTestBehavior,
      gestureRecognizers: widget.gestureRecognizers ?? _DarwinViewState._emptyRecognizersSet,
    );
  }
}

class _AndroidPlatformView extends LeafRenderObjectWidget {
  const _AndroidPlatformView({
    required this.controller,
    required this.hitTestBehavior,
    required this.gestureRecognizers,
    this.clipBehavior = Clip.hardEdge,
  });

  final AndroidViewController controller;
  final PlatformViewHitTestBehavior hitTestBehavior;
  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
  final Clip clipBehavior;

  @override
  RenderObject createRenderObject(BuildContext context) => RenderAndroidView(
    viewController: controller,
    hitTestBehavior: hitTestBehavior,
    gestureRecognizers: gestureRecognizers,
    clipBehavior: clipBehavior,
  );

  @override
  void updateRenderObject(BuildContext context, RenderAndroidView renderObject) {
    renderObject.controller = controller;
    renderObject.hitTestBehavior = hitTestBehavior;
    renderObject.updateGestureRecognizers(gestureRecognizers);
    renderObject.clipBehavior = clipBehavior;
  }
}

abstract class _DarwinPlatformView<
  TController extends DarwinPlatformViewController,
  TRender extends RenderDarwinPlatformView<TController>
>
    extends LeafRenderObjectWidget {
  const _DarwinPlatformView({
    required this.controller,
    required this.hitTestBehavior,
    required this.gestureRecognizers,
  });

  final TController controller;
  final PlatformViewHitTestBehavior hitTestBehavior;
  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;

  @override
  @mustCallSuper
  void updateRenderObject(BuildContext context, TRender renderObject) {
    renderObject
      ..viewController = controller
      ..hitTestBehavior = hitTestBehavior
      ..updateGestureRecognizers(gestureRecognizers);
  }
}

class _UiKitPlatformView extends _DarwinPlatformView<UiKitViewController, RenderUiKitView> {
  const _UiKitPlatformView({
    required super.controller,
    required super.hitTestBehavior,
    required super.gestureRecognizers,
  });

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderUiKitView(
      viewController: controller,
      hitTestBehavior: hitTestBehavior,
      gestureRecognizers: gestureRecognizers,
    );
  }
}

class _AppKitPlatformView extends _DarwinPlatformView<AppKitViewController, RenderAppKitView> {
  const _AppKitPlatformView({
    required super.controller,
    required super.hitTestBehavior,
    required super.gestureRecognizers,
  });

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderAppKitView(
      viewController: controller,
      hitTestBehavior: hitTestBehavior,
      gestureRecognizers: gestureRecognizers,
    );
  }
}

/// The parameters used to create a [PlatformViewController].
///
/// See also:
///
///  * [CreatePlatformViewCallback] which uses this object to create a [PlatformViewController].
class PlatformViewCreationParams {
  const PlatformViewCreationParams._({
    required this.id,
    required this.viewType,
    required this.onPlatformViewCreated,
    required this.onFocusChanged,
  });

  /// The unique identifier for the new platform view.
  ///
  /// [PlatformViewController.viewId] should match this id.
  final int id;

  /// The unique identifier for the type of platform view to be embedded.
  ///
  /// This viewType is used to tell the platform which type of view to
  /// associate with the [id].
  final String viewType;

  /// Callback invoked after the platform view has been created.
  final PlatformViewCreatedCallback onPlatformViewCreated;

  /// Callback invoked when the platform view's focus is changed on the platform side.
  ///
  /// The value is true when the platform view gains focus and false when it loses focus.
  final ValueChanged<bool> onFocusChanged;
}

/// A factory for a surface presenting a platform view as part of the widget hierarchy.
///
/// The returned widget should present the platform view associated with `controller`.
///
/// See also:
///
///  * [PlatformViewSurface], a common widget for presenting platform views.
typedef PlatformViewSurfaceFactory =
    Widget Function(BuildContext context, PlatformViewController controller);

/// Constructs a [PlatformViewController].
///
/// The [PlatformViewController.viewId] field of the created controller must match the value of the
/// params [PlatformViewCreationParams.id] field.
///
/// See also:
///
///  * [PlatformViewLink], which links a platform view with the Flutter framework.
typedef CreatePlatformViewCallback =
    PlatformViewController Function(PlatformViewCreationParams params);

/// Links a platform view with the Flutter framework.
///
/// Provides common functionality for embedding a platform view (e.g an android.view.View on Android)
/// with the Flutter framework.
///
/// {@macro flutter.widgets.AndroidView.lifetime}
///
/// To implement a new platform view widget, return this widget in the `build` method.
/// For example:
///
/// ```dart
/// class FooPlatformView extends StatelessWidget {
///   const FooPlatformView({super.key});
///   @override
///   Widget build(BuildContext context) {
///     return PlatformViewLink(
///       viewType: 'webview',
///       onCreatePlatformView: createFooWebView,
///       surfaceFactory: (BuildContext context, PlatformViewController controller) {
///         return PlatformViewSurface(
///           gestureRecognizers: gestureRecognizers,
///           controller: controller,
///           hitTestBehavior: PlatformViewHitTestBehavior.opaque,
///         );
///       },
///    );
///   }
/// }
/// ```
///
/// The `surfaceFactory` and the `onCreatePlatformView` are only called when the
/// state of this widget is initialized, or when the `viewType` changes.
class PlatformViewLink extends StatefulWidget {
  /// Construct a [PlatformViewLink] widget.
  ///
  /// See also:
  ///
  ///  * [PlatformViewSurface] for details on the widget returned by `surfaceFactory`.
  ///  * [PlatformViewCreationParams] for how each parameter can be used when implementing `createPlatformView`.
  const PlatformViewLink({
    super.key,
    required PlatformViewSurfaceFactory surfaceFactory,
    required CreatePlatformViewCallback onCreatePlatformView,
    required this.viewType,
  }) : _surfaceFactory = surfaceFactory,
       _onCreatePlatformView = onCreatePlatformView;

  final PlatformViewSurfaceFactory _surfaceFactory;
  final CreatePlatformViewCallback _onCreatePlatformView;

  /// The unique identifier for the view type to be embedded.
  ///
  /// Typically, this viewType has already been registered on the platform side.
  final String viewType;

  @override
  State<StatefulWidget> createState() => _PlatformViewLinkState();
}

class _PlatformViewLinkState extends State<PlatformViewLink> {
  int? _id;
  PlatformViewController? _controller;
  bool _platformViewCreated = false;
  Widget? _surface;
  FocusNode? _focusNode;

  @override
  Widget build(BuildContext context) {
    final PlatformViewController? controller = _controller;
    if (controller == null) {
      return const SizedBox.expand();
    }
    if (!_platformViewCreated) {
      // Depending on the implementation, the first non-empty size can be used
      // to size the platform view.
      return _PlatformViewPlaceHolder(
        onLayout: (Size size, Offset position) {
          if (controller.awaitingCreation && !size.isEmpty) {
            controller.create(size: size, position: position);
          }
        },
      );
    }
    _surface ??= widget._surfaceFactory(context, controller);
    return Focus(
      focusNode: _focusNode,
      onFocusChange: _handleFrameworkFocusChanged,
      child: _surface!,
    );
  }

  @override
  void initState() {
    _focusNode = FocusNode(debugLabel: 'PlatformView(id: $_id)');
    _initialize();
    super.initState();
  }

  @override
  void didUpdateWidget(PlatformViewLink oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (widget.viewType != oldWidget.viewType) {
      _controller?.disposePostFrame();
      // The _surface has to be recreated as its controller is disposed.
      // Setting _surface to null will trigger its creation in build().
      _surface = null;
      _initialize();
    }
  }

  void _initialize() {
    _id = platformViewsRegistry.getNextPlatformViewId();
    _controller = widget._onCreatePlatformView(
      PlatformViewCreationParams._(
        id: _id!,
        viewType: widget.viewType,
        onPlatformViewCreated: _onPlatformViewCreated,
        onFocusChanged: _handlePlatformFocusChanged,
      ),
    );
  }

  void _onPlatformViewCreated(int id) {
    if (mounted) {
      setState(() {
        _platformViewCreated = true;
      });
    }
  }

  void _handleFrameworkFocusChanged(bool isFocused) {
    if (!isFocused) {
      _controller?.clearFocus();
    }
    SystemChannels.textInput.invokeMethod<void>(
      'TextInput.setPlatformViewClient',
      <String, dynamic>{'platformViewId': _id},
    );
  }

  void _handlePlatformFocusChanged(bool isFocused) {
    if (isFocused) {
      _focusNode!.requestFocus();
    }
  }

  @override
  void dispose() {
    _controller?.dispose();
    _controller = null;
    _focusNode?.dispose();
    _focusNode = null;
    super.dispose();
  }
}

/// Integrates a platform view with Flutter's compositor, touch, and semantics subsystems.
///
/// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewSurface]
/// isn't supported on all platforms (e.g on Android platform views can be composited by using a [TextureLayer] or
/// [AndroidViewSurface]).
/// Custom Flutter embedders can support [PlatformViewLayer]s by implementing a SystemCompositor.
///
/// The widget fills all available space, the parent of this object must provide bounded layout
/// constraints.
///
/// If the associated platform view is not created the [PlatformViewSurface] does not paint any contents.
///
/// See also:
///
///  * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer].
///  * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
// TODO(amirh): Link to the embedder's system compositor documentation once available.
class PlatformViewSurface extends LeafRenderObjectWidget {
  /// Construct a [PlatformViewSurface].
  const PlatformViewSurface({
    super.key,
    required this.controller,
    required this.hitTestBehavior,
    required this.gestureRecognizers,
  });

  /// The controller for the platform view integrated by this [PlatformViewSurface].
  ///
  /// [PlatformViewController] is used for dispatching touch events to the platform view.
  /// [PlatformViewController.viewId] identifies the platform view whose contents are painted by this widget.
  final PlatformViewController controller;

  /// Which gestures should be forwarded to the PlatformView.
  ///
  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead}
  ///
  /// For example, with the following setup vertical drags will not be dispatched to the platform view
  /// as the vertical drag gesture is claimed by the parent [GestureDetector].
  ///
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails details) { },
  ///   child: PlatformViewSurface(
  ///     gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
  ///     controller: _controller,
  ///     hitTestBehavior: PlatformViewHitTestBehavior.opaque,
  ///   ),
  /// )
  /// ```
  ///
  /// To get the [PlatformViewSurface] to claim the vertical drag gestures we can pass a vertical drag
  /// gesture recognizer factory in [gestureRecognizers] e.g:
  ///
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails details) { },
  ///   child: SizedBox(
  ///     width: 200.0,
  ///     height: 100.0,
  ///     child: PlatformViewSurface(
  ///       gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
  ///         Factory<OneSequenceGestureRecognizer>(
  ///           () => EagerGestureRecognizer(),
  ///         ),
  ///       },
  ///       controller: _controller,
  ///       hitTestBehavior: PlatformViewHitTestBehavior.opaque,
  ///     ),
  ///   ),
  /// )
  /// ```
  ///
  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot}
  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
  // TODO(amirh): get a list of GestureRecognizers here.
  // https://github.com/flutter/flutter/issues/20953
  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;

  /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
  final PlatformViewHitTestBehavior hitTestBehavior;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return PlatformViewRenderBox(
      controller: controller,
      gestureRecognizers: gestureRecognizers,
      hitTestBehavior: hitTestBehavior,
    );
  }

  @override
  void updateRenderObject(BuildContext context, PlatformViewRenderBox renderObject) {
    renderObject
      ..controller = controller
      ..hitTestBehavior = hitTestBehavior
      ..updateGestureRecognizers(gestureRecognizers);
  }
}

/// Integrates an Android view with Flutter's compositor, touch, and semantics subsystems.
///
/// The compositor integration is done by adding a [TextureLayer] to the layer tree.
///
/// The parent of this object must provide bounded layout constraints.
///
/// If the associated platform view is not created, the [AndroidViewSurface] does not paint any contents.
///
/// When possible, you may want to use [AndroidView] directly, since it requires less boilerplate code
/// than [AndroidViewSurface], and there's no difference in performance, or other trade-off(s).
///
/// See also:
///
///  * [AndroidView] which embeds an Android platform view in the widget hierarchy.
///  * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
class AndroidViewSurface extends StatefulWidget {
  /// Construct an `AndroidPlatformViewSurface`.
  const AndroidViewSurface({
    super.key,
    required this.controller,
    required this.hitTestBehavior,
    required this.gestureRecognizers,
  });

  /// The controller for the platform view integrated by this [AndroidViewSurface].
  ///
  /// See [PlatformViewSurface.controller] for details.
  final AndroidViewController controller;

  /// Which gestures should be forwarded to the PlatformView.
  ///
  /// See [PlatformViewSurface.gestureRecognizers] for details.
  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;

  /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
  final PlatformViewHitTestBehavior hitTestBehavior;

  @override
  State<StatefulWidget> createState() {
    return _AndroidViewSurfaceState();
  }
}

class _AndroidViewSurfaceState extends State<AndroidViewSurface> {
  @override
  void initState() {
    super.initState();
    if (!widget.controller.isCreated) {
      // Schedule a rebuild once creation is complete and the final display
      // type is known.
      widget.controller.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
    }
  }

  @override
  void dispose() {
    widget.controller.removeOnPlatformViewCreatedListener(_onPlatformViewCreated);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (widget.controller.requiresViewComposition) {
      return _PlatformLayerBasedAndroidViewSurface(
        controller: widget.controller,
        hitTestBehavior: widget.hitTestBehavior,
        gestureRecognizers: widget.gestureRecognizers,
      );
    } else {
      return _TextureBasedAndroidViewSurface(
        controller: widget.controller,
        hitTestBehavior: widget.hitTestBehavior,
        gestureRecognizers: widget.gestureRecognizers,
      );
    }
  }

  void _onPlatformViewCreated(int _) {
    // Trigger a re-build based on the current controller state.
    setState(() {});
  }
}

// Displays an Android platform view via GL texture.
class _TextureBasedAndroidViewSurface extends PlatformViewSurface {
  const _TextureBasedAndroidViewSurface({
    required AndroidViewController super.controller,
    required super.hitTestBehavior,
    required super.gestureRecognizers,
  });

  @override
  RenderObject createRenderObject(BuildContext context) {
    final viewController = controller as AndroidViewController;
    // Use GL texture based composition.
    // App should use GL texture unless they require to embed a SurfaceView.
    final renderBox = RenderAndroidView(
      viewController: viewController,
      gestureRecognizers: gestureRecognizers,
      hitTestBehavior: hitTestBehavior,
    );
    viewController.pointTransformer = (Offset position) => renderBox.globalToLocal(position);
    return renderBox;
  }
}

class _PlatformLayerBasedAndroidViewSurface extends PlatformViewSurface {
  const _PlatformLayerBasedAndroidViewSurface({
    required AndroidViewController super.controller,
    required super.hitTestBehavior,
    required super.gestureRecognizers,
  });

  @override
  RenderObject createRenderObject(BuildContext context) {
    final viewController = controller as AndroidViewController;
    final renderBox = super.createRenderObject(context) as PlatformViewRenderBox;
    viewController.pointTransformer = (Offset position) => renderBox.globalToLocal(position);
    return renderBox;
  }
}

/// A callback used to notify the size of the platform view placeholder.
/// This size is the initial size of the platform view.
typedef _OnLayoutCallback = void Function(Size size, Offset position);

/// A [RenderBox] that notifies its size to the owner after a layout.
class _PlatformViewPlaceholderBox extends RenderConstrainedBox {
  _PlatformViewPlaceholderBox({required this.onLayout})
    : super(
        additionalConstraints: const BoxConstraints.tightFor(
          width: double.infinity,
          height: double.infinity,
        ),
      );

  _OnLayoutCallback onLayout;

  @override
  void performLayout() {
    super.performLayout();
    // A call to `localToGlobal` requires waiting for a frame to render first.
    SchedulerBinding.instance.addPostFrameCallback((_) {
      onLayout(size, localToGlobal(Offset.zero));
    }, debugLabel: 'PlatformViewPlaceholderBox.onLayout');
  }
}

/// When a platform view is in the widget hierarchy, this widget is used to capture
/// the size of the platform view after the first layout.
/// This placeholder is basically a [SizedBox.expand] with a [onLayout] callback to
/// notify the size of the render object to its parent.
class _PlatformViewPlaceHolder extends SingleChildRenderObjectWidget {
  const _PlatformViewPlaceHolder({required this.onLayout});

  final _OnLayoutCallback onLayout;

  @override
  _PlatformViewPlaceholderBox createRenderObject(BuildContext context) {
    return _PlatformViewPlaceholderBox(onLayout: onLayout);
  }

  @override
  void updateRenderObject(BuildContext context, _PlatformViewPlaceholderBox renderObject) {
    renderObject.onLayout = onLayout;
  }
}

extension on PlatformViewController {
  /// Disposes the controller in a post-frame callback, to allow other widgets to
  /// remove their listeners before the controller is disposed.
  void disposePostFrame() {
    SchedulerBinding.instance.addPostFrameCallback((_) {
      dispose();
    }, debugLabel: 'PlatformViewController.dispose');
  }
}
