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

#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_ACCESSIBILITY_BRIDGE_H_
#define FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_ACCESSIBILITY_BRIDGE_H_

// Work around symbol conflicts with ICU.
#undef TRUE
#undef FALSE

#include <fuchsia/accessibility/semantics/cpp/fidl.h>
#include <fuchsia/ui/gfx/cpp/fidl.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/sys/inspect/cpp/component.h>
#include <zircon/types.h>

#include <memory>
#include <optional>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "flutter/fml/macros.h"
#include "flutter/lib/ui/semantics/semantics_node.h"

namespace flutter_runner {

// Accessibility bridge.
//
// This class intermediates accessibility-related calls between Fuchsia and
// Flutter. It serves to resolve the impedance mismatch between Flutter's
// platform-agnostic accessibility APIs and Fuchsia's APIs and behaviour.
//
// This bridge performs the following functions, among others:
//
// * Translates Flutter's semantics node updates to events Fuchsia requires
//   (e.g. Flutter only sends updates for changed nodes, but Fuchsia requires
//   the entire flattened subtree to be sent when a node changes.
class AccessibilityBridge
    : public fuchsia::accessibility::semantics::SemanticListener {
 public:
  using SetSemanticsEnabledCallback = std::function<void(bool)>;
  using DispatchSemanticsActionCallback =
      std::function<void(int32_t, flutter::SemanticsAction)>;

  // TODO(MI4-2531, FIDL-718): Remove this. We shouldn't be worried about
  // batching messages at this level.
  // FIDL may encode a C++ struct as larger than the sizeof the C++ struct.
  // This is to make sure we don't send updates that are too large.
  static constexpr uint32_t kMaxMessageSize = ZX_CHANNEL_MAX_MSG_BYTES / 2;

  static_assert(fuchsia::accessibility::semantics::MAX_LABEL_SIZE <
                kMaxMessageSize - 1);

  // Flutter uses signed 32 bit integers for node IDs, while Fuchsia uses
  // unsigned 32 bit integers. A change in the size on either one would break
  // casts and size tracking logic in the implementation.
  static constexpr size_t kNodeIdSize = sizeof(flutter::SemanticsNode::id);
  static_assert(
      kNodeIdSize ==
          sizeof(fuchsia::accessibility::semantics::Node().node_id()),
      "flutter::SemanticsNode::id and "
      "fuchsia::accessibility::semantics::Node::node_id differ in size.");

  // Maximum number of node ids to be deleted through the Semantics API.
  static constexpr size_t kMaxDeletionsPerUpdate =
      kMaxMessageSize / kNodeIdSize;

  AccessibilityBridge(
      SetSemanticsEnabledCallback set_semantics_enabled_callback,
      DispatchSemanticsActionCallback dispatch_semantics_action_callback,
      fuchsia::accessibility::semantics::SemanticsManagerHandle
          semantics_manager,
      fuchsia::ui::views::ViewRef view_ref,
      inspect::Node inspect_node);

  // Returns true if accessible navigation is enabled.
  bool GetSemanticsEnabled() const;

  // Enables Flutter accessibility navigation features.
  //
  // Once enabled, any semantics updates in the Flutter application will
  // trigger |FuchsiaAccessibility::DispatchAccessibilityEvent| callbacks
  // to send events back to the Fuchsia SemanticsManager.
  void SetSemanticsEnabled(bool enabled);

  // Adds a semantics node update to the buffer of node updates to apply.
  void AddSemanticsNodeUpdate(const flutter::SemanticsNodeUpdates update,
                              float view_pixel_ratio);

  // Requests a message announcement from the accessibility TTS system.
  void RequestAnnounce(const std::string message);

  // Notifies the bridge of a 'hover move' touch exploration event.
  zx_status_t OnHoverMove(double x, double y);

  // |fuchsia::accessibility::semantics::SemanticListener|
  void HitTest(
      fuchsia::math::PointF local_point,
      fuchsia::accessibility::semantics::SemanticListener::HitTestCallback
          callback) override;

  // |fuchsia::accessibility::semantics::SemanticListener|
  void OnAccessibilityActionRequested(
      uint32_t node_id,
      fuchsia::accessibility::semantics::Action action,
      fuchsia::accessibility::semantics::SemanticListener::
          OnAccessibilityActionRequestedCallback callback) override;

 private:
  // Fuchsia's default root semantic node id.
  static constexpr int32_t kRootNodeId = 0;

  // Represents an atomic semantic update to Fuchsia, which can contain deletes
  // and updates of semantic nodes.
  //
  // An atomic update is a set of operations that take a semantic tree in a
  // valid state to another valid state. Please check the semantics FIDL
  // documentation for details.
  struct FuchsiaAtomicUpdate {
    FuchsiaAtomicUpdate() = default;
    ~FuchsiaAtomicUpdate() = default;
    FuchsiaAtomicUpdate(FuchsiaAtomicUpdate&&) = default;

    // Adds a node to be updated. |size| contains the
    // size in bytes of the node to be updated.
    void AddNodeUpdate(fuchsia::accessibility::semantics::Node node,
                       size_t size);

    // Adds a node to be deleted.
    void AddNodeDeletion(uint32_t id);

    std::vector<std::pair<fuchsia::accessibility::semantics::Node, size_t>>
        updates;
    std::vector<uint32_t> deletions;
  };

  // Holds a flutter semantic node and some extra info.
  // In particular, it adds a screen_rect field to flutter::SemanticsNode.
  struct SemanticsNode {
    flutter::SemanticsNode data;
    SkRect screen_rect;
  };

  fuchsia::accessibility::semantics::Node GetRootNodeUpdate(size_t& node_size);

  // Derives the BoundingBox of a Flutter semantics node from its
  // rect and elevation.
  fuchsia::ui::gfx::BoundingBox GetNodeLocation(
      const flutter::SemanticsNode& node) const;

  // Gets mat4 transformation from a Flutter semantics node.
  fuchsia::ui::gfx::mat4 GetNodeTransform(
      const flutter::SemanticsNode& node) const;

  // Converts a Flutter semantics node's transformation to a mat4.
  fuchsia::ui::gfx::mat4 ConvertSkiaTransformToMat4(
      const SkM44 transform) const;

  // Derives the attributes for a Fuchsia semantics node from a Flutter
  // semantics node.
  fuchsia::accessibility::semantics::Attributes GetNodeAttributes(
      const flutter::SemanticsNode& node,
      size_t* added_size) const;

  // Derives the states for a Fuchsia semantics node from a Flutter semantics
  // node.
  fuchsia::accessibility::semantics::States GetNodeStates(
      const flutter::SemanticsNode& node,
      size_t* additional_size) const;

  // Derives the set of supported actions for a Fuchsia semantics node from
  // a Flutter semantics node.
  std::vector<fuchsia::accessibility::semantics::Action> GetNodeActions(
      const flutter::SemanticsNode& node,
      size_t* additional_size) const;

  // Derives the role for a Fuchsia semantics node from a Flutter
  // semantics node.
  fuchsia::accessibility::semantics::Role GetNodeRole(
      const flutter::SemanticsNode& node) const;

  // Gets the set of reachable descendants from the given node id.
  std::unordered_set<int32_t> GetDescendants(int32_t node_id) const;

  // Removes internal references to any dangling nodes from previous
  // updates, and adds the nodes to be deleted to the current |atomic_update|.
  //
  // The node ids to be deleted are only collected at this point, and will be
  // committed in the next call to |Apply()|.
  void PruneUnreachableNodes(FuchsiaAtomicUpdate* atomic_update);

  // Updates the on-screen positions of accessibility elements,
  // starting from the root element with an identity matrix.
  //
  // This should be called from Update.
  void UpdateScreenRects();

  // Updates the on-screen positions of accessibility elements, starting
  // from node_id and using the specified transform.
  //
  // Update calls this via UpdateScreenRects().
  void UpdateScreenRects(int32_t node_id,
                         SkM44 parent_transform,
                         std::unordered_set<int32_t>* visited_nodes);

  // Traverses the semantics tree to find the node_id hit by the given x,y
  // point.
  //
  // Assumes that SemanticsNode::screen_rect is up to date.
  std::optional<int32_t> GetHitNode(int32_t node_id, float x, float y);

  // Returns whether the node is considered focusable.
  bool IsFocusable(const flutter::SemanticsNode& node) const;

  // Converts a fuchsia::accessibility::semantics::Action to a
  // flutter::SemanticsAction.
  //
  // The node_id parameter is used for printing warnings about unsupported
  // action types.
  std::optional<flutter::SemanticsAction> GetFlutterSemanticsAction(
      fuchsia::accessibility::semantics::Action fuchsia_action,
      uint32_t node_id);

  // Applies the updates and deletions in |atomic_update|, sending them via
  // |tree_ptr|.
  void Apply(FuchsiaAtomicUpdate* atomic_update);

  // |fuchsia::accessibility::semantics::SemanticListener|
  void OnSemanticsModeChanged(bool enabled,
                              OnSemanticsModeChangedCallback callback) override;

#if !FLUTTER_RELEASE
  // Fills the inspect tree with debug information about the semantic tree.
  void FillInspectTree(int32_t flutter_node_id,
                       int32_t current_level,
                       inspect::Node inspect_node,
                       inspect::Inspector* inspector) const;
#endif  // !FLUTTER_RELEASE

  SetSemanticsEnabledCallback set_semantics_enabled_callback_;
  DispatchSemanticsActionCallback dispatch_semantics_action_callback_;
  flutter::SemanticsNode root_flutter_semantics_node_;
  float last_seen_view_pixel_ratio_ = 1.f;

  fidl::Binding<fuchsia::accessibility::semantics::SemanticListener> binding_;
  fuchsia::accessibility::semantics::SemanticsManagerPtr
      fuchsia_semantics_manager_;
  fuchsia::accessibility::semantics::SemanticTreePtr tree_ptr_;

  // This is the cache of all nodes we've sent to Fuchsia's SemanticsManager.
  // Assists with pruning unreachable nodes and hit testing.
  std::unordered_map<int32_t, SemanticsNode> nodes_;
  bool semantics_enabled_;

  // Queue of atomic updates to be sent to Fuchsia.
  std::shared_ptr<std::queue<FuchsiaAtomicUpdate>> atomic_updates_;

  // Node to publish inspect data.
  inspect::Node inspect_node_;

#if !FLUTTER_RELEASE
  // Inspect node to store a dump of the semantic tree. Note that this only gets
  // computed if requested, so it does not use memory to store the dump unless
  // an explicit request is made.
  inspect::LazyNode inspect_node_tree_dump_;
#endif  // !FLUTTER_RELEASE

  FML_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridge);
};
}  // namespace flutter_runner

#endif  // FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_ACCESSIBILITY_BRIDGE_H_
