/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#include "NativeIntersectionObserver.h"
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/uimanager/UIManagerBinding.h>
#include <react/renderer/uimanager/primitives.h>

#include "Plugins.h"

std::shared_ptr<facebook::react::TurboModule>
NativeIntersectionObserverModuleProvider(
    std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
  return std::make_shared<facebook::react::NativeIntersectionObserver>(
      std::move(jsInvoker));
}

namespace facebook::react {

NativeIntersectionObserver::NativeIntersectionObserver(
    std::shared_ptr<CallInvoker> jsInvoker)
    : NativeIntersectionObserverCxxSpec(std::move(jsInvoker)) {}

void NativeIntersectionObserver::observe(
    jsi::Runtime& runtime,
    NativeIntersectionObserverObserveOptions options) {
  auto intersectionObserverId = options.intersectionObserverId;
  auto shadowNode =
      shadowNodeFromValue(runtime, std::move(options.targetShadowNode));
  auto thresholds = options.thresholds;
  auto& uiManager = getUIManagerFromRuntime(runtime);

  intersectionObserverManager_.observe(
      intersectionObserverId, shadowNode, thresholds, uiManager);
}

void NativeIntersectionObserver::unobserve(
    jsi::Runtime& runtime,
    IntersectionObserverObserverId intersectionObserverId,
    jsi::Object targetShadowNode) {
  auto shadowNode = shadowNodeFromValue(runtime, std::move(targetShadowNode));
  intersectionObserverManager_.unobserve(intersectionObserverId, *shadowNode);
}

void NativeIntersectionObserver::connect(
    jsi::Runtime& runtime,
    AsyncCallback<> notifyIntersectionObserversCallback) {
  auto& uiManager = getUIManagerFromRuntime(runtime);
  intersectionObserverManager_.connect(
      uiManager, notifyIntersectionObserversCallback);
}

void NativeIntersectionObserver::disconnect(jsi::Runtime& runtime) {
  auto& uiManager = getUIManagerFromRuntime(runtime);
  intersectionObserverManager_.disconnect(uiManager);
}

std::vector<NativeIntersectionObserverEntry>
NativeIntersectionObserver::takeRecords(jsi::Runtime& runtime) {
  auto entries = intersectionObserverManager_.takeRecords();

  std::vector<NativeIntersectionObserverEntry> nativeModuleEntries;
  nativeModuleEntries.reserve(entries.size());

  for (const auto& entry : entries) {
    nativeModuleEntries.emplace_back(
        convertToNativeModuleEntry(entry, runtime));
  }

  return nativeModuleEntries;
}

NativeIntersectionObserverEntry
NativeIntersectionObserver::convertToNativeModuleEntry(
    IntersectionObserverEntry entry,
    jsi::Runtime& runtime) {
  RectAsTuple targetRect = {
      entry.targetRect.origin.x,
      entry.targetRect.origin.y,
      entry.targetRect.size.width,
      entry.targetRect.size.height};
  RectAsTuple rootRect = {
      entry.rootRect.origin.x,
      entry.rootRect.origin.y,
      entry.rootRect.size.width,
      entry.rootRect.size.height};
  std::optional<RectAsTuple> intersectionRect;
  if (entry.intersectionRect) {
    intersectionRect = {
        entry.intersectionRect.value().origin.x,
        entry.intersectionRect.value().origin.y,
        entry.intersectionRect.value().size.width,
        entry.intersectionRect.value().size.height};
  }

  NativeIntersectionObserverEntry nativeModuleEntry = {
      entry.intersectionObserverId,
      (*entry.shadowNode).getInstanceHandle(runtime),
      targetRect,
      rootRect,
      intersectionRect,
      entry.isIntersectingAboveThresholds,
      entry.time,
  };

  return nativeModuleEntry;
}

UIManager& NativeIntersectionObserver::getUIManagerFromRuntime(
    jsi::Runtime& runtime) {
  return UIManagerBinding::getBinding(runtime)->getUIManager();
}

} // namespace facebook::react
