package expo.modules.devmenu.fab

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Path
import android.graphics.RectF
import android.graphics.Region
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import expo.modules.devmenu.DevMenuManager
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

private const val CLICK_DRAG_TOLERANCE = 10f
private const val MARGIN = 24
private const val SIZE = 150

@SuppressLint("ViewConstructor")
class MovableFloatingActionButton(
  context: Context,
  private val updateSystemGestureExclusionRects: () -> Unit
) : FrameLayout(context), View.OnTouchListener {
  private var downRawX = 0f
  private var downRawY = 0f
  private var dX = 0f
  private var dY = 0f
  private var isActive = false

  // stencilPath is used to make the view rounded
  private val stencilPath = Path()

  // eventRegion is used to add rounded corners to the touch area
  private var eventRegion = Region()

  init {
    layoutParams = LayoutParams(SIZE, SIZE).apply {
      gravity = Gravity.BOTTOM or Gravity.END
      setMargins(MARGIN, MARGIN, MARGIN, MARGIN)
    }
    z = Float.MAX_VALUE

    setOnTouchListener(this)

    addView(
      View(context).apply {
        layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT).apply {
          gravity = Gravity.CENTER
        }
        setBackgroundColor(Color.MAGENTA)
      }
    )
  }

  fun onClick() {
    val reactHostWrapper = DevMenuManager.getReactHost()
      ?: return
    val currentActivity = reactHostWrapper.currentReactContext?.currentActivity
      ?: return

    DevMenuManager.openMenu(currentActivity)
  }

  override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
    val layoutParams = view.layoutParams as MarginLayoutParams
    val action = motionEvent.action

    when (action) {
      MotionEvent.ACTION_DOWN -> {
        if (!eventRegion.contains(motionEvent.x.toInt(), motionEvent.y.toInt())) {
          return false
        }

        downRawX = motionEvent.rawX
        downRawY = motionEvent.rawY
        dX = view.x - downRawX
        dY = view.y - downRawY
        isActive = true

        return true
      }

      MotionEvent.ACTION_MOVE, MotionEvent.ACTION_OUTSIDE -> {
        if (!isActive) {
          return false
        }

        val viewWidth = view.width
        val viewHeight = view.height

        val viewParent = view.parent as View
        val parentWidth = viewParent.width
        val parentHeight = viewParent.height

        var newX = motionEvent.rawX + dX
        newX = max(layoutParams.leftMargin.toDouble(), newX.toDouble()).toFloat()
        newX = min((parentWidth - viewWidth - layoutParams.rightMargin).toDouble(), newX.toDouble()).toFloat()

        var newY = motionEvent.rawY + dY
        newY = max(layoutParams.topMargin.toDouble(), newY.toDouble()).toFloat()
        newY = min((parentHeight - viewHeight - layoutParams.bottomMargin).toDouble(), newY.toDouble()).toFloat()

        view.animate()
          .x(newX)
          .y(newY)
          .setDuration(0)
          .start()

        return true
      }

      MotionEvent.ACTION_UP -> {
        if (!isActive) {
          return false
        }

        isActive = false

        val upRawX = motionEvent.rawX
        val upRawY = motionEvent.rawY

        val upDX = upRawX - downRawX
        val upDY = upRawY - downRawY

        return if (abs(upDX.toDouble()) < CLICK_DRAG_TOLERANCE && abs(upDY.toDouble()) < CLICK_DRAG_TOLERANCE) {
          onClick()
          true
        } else {
          val viewWidth = view.width

          val centerX = view.x + viewWidth / 2

          val viewParent = view.parent as View
          val parentWidth = viewParent.width

          val newX = if (centerX < parentWidth / 2) {
            layoutParams.leftMargin.toFloat()
          } else {
            (parentWidth - viewWidth - layoutParams.rightMargin).toFloat()
          }

          view.animate()
            .x(newX)
            .setDuration(100)
            .scaleX(1f)
            .scaleY(1f)
            .withEndAction { updateSystemGestureExclusionRects() }
            .start()

          true
        }
      }

      else -> {
        return super.onTouchEvent(motionEvent)
      }
    }
  }

  override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)

    // compute the path
    stencilPath.reset()
    stencilPath.addRoundRect(0f, 0f, w.toFloat(), h.toFloat(), w.toFloat(), h.toFloat(), Path.Direction.CW)
    stencilPath.close()

    val rectF = RectF()
    stencilPath.computeBounds(rectF, true)

    eventRegion = Region()
    eventRegion.setPath(stencilPath, Region((rectF.left).toInt(), (rectF.top).toInt(), (rectF.right).toInt(), (rectF.bottom).toInt()))
  }

  override fun dispatchDraw(canvas: Canvas) {
    val save = canvas.save()
    canvas.clipPath(stencilPath)
    super.dispatchDraw(canvas)
    canvas.restoreToCount(save)
  }
}
