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

package com.facebook.react.touch

import android.view.MotionEvent
import android.view.ViewGroup
import android.view.ViewParent

/**
 * This class coordinates JSResponder commands for [com.facebook.react.bridge.UIManager]. It should
 * be set as OnInterceptTouchEventListener for all newly created native views that implements [ ]
 * and thanks to the information whether JSResponder is set and to which view it will correctly
 * coordinate the return values of [OnInterceptTouchEventListener] such that touch events will be
 * dispatched to the view selected by JS gesture recognizer.
 *
 * Single [com.facebook.react.bridge.CatalystInstance] should reuse same instance of this class.
 */
internal class JSResponderHandler : OnInterceptTouchEventListener {

  @Volatile private var currentJSResponder = JS_RESPONDER_UNSET
  // We're holding on to the ViewParent that blocked native responders so that we can clear it
  // when we change or clear the current JS responder.

  private var viewParentBlockingNativeResponder: ViewParent? = null

  fun setJSResponder(tag: Int, viewParentBlockingNativeResponder: ViewParent?) {
    currentJSResponder = tag
    // We need to unblock the native responder first, otherwise we can get in a bad state: a
    // ViewParent sets requestDisallowInterceptTouchEvent to true, which sets this setting to true
    // to all of its ancestors. Now, if one of its ancestors sets requestDisallowInterceptTouchEvent
    // to false, it unsets the setting for itself and all of its ancestors, which means that they
    // can intercept events again.
    maybeUnblockNativeResponder()
    if (viewParentBlockingNativeResponder != null) {
      viewParentBlockingNativeResponder.requestDisallowInterceptTouchEvent(true)
      this.viewParentBlockingNativeResponder = viewParentBlockingNativeResponder
    }
  }

  fun clearJSResponder() {
    currentJSResponder = JS_RESPONDER_UNSET
    maybeUnblockNativeResponder()
  }

  private fun maybeUnblockNativeResponder() {
    viewParentBlockingNativeResponder?.requestDisallowInterceptTouchEvent(false)
    viewParentBlockingNativeResponder = null
  }

  override fun onInterceptTouchEvent(view: ViewGroup, event: MotionEvent): Boolean {
    val currentJSResponder = currentJSResponder
    return if (currentJSResponder != JS_RESPONDER_UNSET && event.action != MotionEvent.ACTION_UP) {
      // Don't intercept ACTION_UP events. If we return true here than UP event will not be
      // delivered. That is because intercepted touch events are converted into CANCEL events
      // and make all further events to be delivered to the view that intercepted the event.
      // Therefore since "UP" event is the last event in a gesture, we should just let it reach the
      // original target that is a child view of {@param v}.
      // http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent(android.view.MotionEvent)
      view.id == currentJSResponder
    } else {
      false
    }
  }

  private companion object {
    private const val JS_RESPONDER_UNSET = -1
  }
}
