// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once
#include <Fabric/Composition/RootComponentView.h>
#include <Fabric/ReactTaggedView.h>
#include <IReactInstance.h>
#include <JSValue.h>
#include <ReactContext.h>
#include <react/renderer/components/view/PointerEvent.h>
#include <react/renderer/components/view/Touch.h>
#include <react/renderer/components/view/TouchEventEmitter.h>
#include <winrt/Microsoft.ReactNative.h>
#include <winrt/Windows.Devices.Input.h>
#include <optional>
#include <set>
#include <unordered_set>

namespace winrt {
using namespace Windows::UI;
using namespace Windows::Foundation;
} // namespace winrt

namespace Microsoft::ReactNative {

struct FabricUIManager;
struct winrt::Microsoft::ReactNative::implementation::ComponentView;
typedef int PointerId;

class CompositionEventHandler : public std::enable_shared_from_this<CompositionEventHandler> {
 public:
  CompositionEventHandler(
      const winrt::Microsoft::ReactNative::ReactContext &context,
      const winrt::Microsoft::ReactNative::ReactNativeIsland &ReactNativeIsland);
  virtual ~CompositionEventHandler();

  void Initialize() noexcept;
  int64_t SendMessage(HWND hwnd, uint32_t msg, uint64_t wParam, int64_t lParam) noexcept;
  void RemoveTouchHandlers();
  winrt::Microsoft::UI::Input::VirtualKeyStates GetKeyState(winrt::Windows::System::VirtualKey key) noexcept;

  bool CapturePointer(
      const winrt::Microsoft::ReactNative::Composition::Input::Pointer &pointer,
      facebook::react::Tag tag) noexcept;
  bool ReleasePointerCapture(
      const winrt::Microsoft::ReactNative::Composition::Input::Pointer &pointer,
      facebook::react::Tag tag) noexcept;
  facebook::react::Tag PointerCapturingComponent() noexcept;

 private:
  void onPointerPressed(
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept;
  void onPointerReleased(
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept;
  void onPointerMoved(
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept;
  void onPointerExited(
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept;
  void onPointerWheelChanged(
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept;
  void onPointerCaptureLost(
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept;
  void onPointerRoutedAway(
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept;
  void onKeyDown(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept;
  void onKeyUp(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept;
  void onCharacterReceived(
      const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept;

  void getTargetPointerArgs(
      const std::shared_ptr<FabricUIManager> &fabricuiManager,
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      facebook::react::Tag &tag,
      facebook::react::Point &ptScaled,
      facebook::react::Point &ptLocal) const noexcept;
  bool releasePointerCapture(PointerId pointerId, facebook::react::Tag tag) noexcept;

  facebook::react::SurfaceId SurfaceId() const noexcept;
  winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *RootComponentView() const noexcept;

  enum class UITouchType {
    Mouse,
    Pen,
    Touch,
  };
  enum class TouchEventType { Start = 0, End, Move, Cancel, CaptureLost, PointerEntered, PointerExited, PointerMove };
  void DispatchTouchEvent(
      TouchEventType eventType,
      PointerId pointerId,
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      winrt::Windows::System::VirtualKeyModifiers keyModifiers);
  void HandleIncomingPointerEvent(
      facebook::react::PointerEvent &pe,
      const winrt::Microsoft::ReactNative::ComponentView &targetView,
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      winrt::Windows::System::VirtualKeyModifiers keyModifiers,
      std::function<void(std::vector<winrt::Microsoft::ReactNative::ComponentView> &)> handler);
  void ClearAllHoveredForPointer(const facebook::react::PointerEvent &pointerEvent) noexcept;

  struct ActiveTouch {
    facebook::react::Touch touch;
    facebook::react::SharedTouchEventEmitter eventEmitter;

    /*
     * The type of touch received.
     */
    UITouchType touchType;

    /*
     * The button mask of the touch
     */
    // UIEventButtonMask buttonMask;

    /*
     * The bit mask of modifier flags in the gesture represented by the receiver.
     */
    // UIKeyModifierFlags modifierFlags;

    /*
     * Indicates if the active touch represents the primary pointer of this pointer type.
     */
    bool isPrimary;

    /*
     * The button number that was pressed (if applicable) when the event was fired.
     */
    int button;

    /*
     * Informs the event system that when the touch is released it should be treated as the
     * pointer leaving the screen entirely.
     */
    bool shouldLeaveWhenReleased;

    /*
     * A component view on which the touch was begun.
     */
    ReactTaggedView initialComponentView{nullptr};
  };

  bool IsPointerWithinInitialTree(const ActiveTouch &activeTouch) noexcept;
  static bool IsEndishEventType(TouchEventType eventType) noexcept;
  static const char *PointerTypeCStringFromUITouchType(UITouchType type) noexcept;
  static facebook::react::PointerEvent CreatePointerEventFromActiveTouch(
      const ActiveTouch &activeTouch,
      TouchEventType eventType) noexcept;
  static void
  UpdateActiveTouch(ActiveTouch &activeTouch, facebook::react::Point ptScaled, facebook::react::Point ptLocal) noexcept;

  void DispatchSynthesizedTouchCancelForActiveTouch(
      const ActiveTouch &cancelledTouch,
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      winrt::Windows::System::VirtualKeyModifiers keyModifiers);

  // Look up the active touch for pointerId, erase it, and dispatch cancel events.
  // Returns true iff a touch was found and cancel events were dispatched.
  bool CancelActiveTouchForPointerInternal(
      PointerId pointerId,
      const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
      winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept;

  std::vector<winrt::Microsoft::ReactNative::ComponentView> GetTouchableViewsInPathToRoot(
      const winrt::Microsoft::ReactNative::ComponentView &componentView);

  void UpdateCursor() noexcept;
  void SetCursor(facebook::react::Cursor cursor, HCURSOR hcur) noexcept;

  // Allocates a small touch identifier (0–19) that is safe to use as a JS array index.
  // Windows pointer IDs can be arbitrarily large, which causes React Native to warn and
  // back-fill huge arrays, corrupting touch state after scrolling.
  int AllocateTouchIdentifier() noexcept;

  std::map<PointerId, ActiveTouch> m_activeTouches; // iOS is map of touch event args to ActiveTouch..?
  int m_touchId = 0; // cycling base used by AllocateTouchIdentifier

  std::map<PointerId, std::vector<ReactTaggedView>> m_currentlyHoveredViewsPerPointer;
  winrt::weak_ref<winrt::Microsoft::ReactNative::ReactNativeIsland> m_wkRootView;
  winrt::Microsoft::ReactNative::ReactContext m_context;

  facebook::react::Tag m_pointerCapturingComponentTag{-1}; // Component that has captured input
  std::unordered_set<PointerId> m_capturedPointers;
  HCURSOR m_hcursor{nullptr};
  bool m_hcursorOwned{false}; // If we create the cursor, so we need to destroy it
  facebook::react::Cursor m_currentCursor{facebook::react::Cursor::Auto};
  winrt::Microsoft::UI::Input::InputCursor m_inputCursor{nullptr};

  winrt::event_token m_pointerPressedToken;
  winrt::event_token m_pointerReleasedToken;
  winrt::event_token m_pointerMovedToken;
  winrt::event_token m_pointerWheelChangedToken;
  winrt::event_token m_pointerCaptureLostToken;
  winrt::event_token m_pointerRoutedAwayToken;
  winrt::event_token m_pointerExitedToken;
  winrt::event_token m_keyDownToken;
  winrt::event_token m_keyUpToken;
  winrt::event_token m_characterReceivedToken;
  winrt::event_token m_contextMenuKeyToken;
};

} // namespace Microsoft::ReactNative
