package com.humand.demo.modules.RNSwipeable

import android.content.Context
import android.view.MotionEvent
import android.view.ViewConfiguration
import com.facebook.react.bridge.Arguments
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.events.RCTEventEmitter
import com.facebook.react.views.view.ReactViewGroup
import kotlin.math.abs
import kotlin.math.roundToInt
import android.animation.ValueAnimator
import android.view.animation.DecelerateInterpolator


private enum class OpenSide { NONE, LEFT, RIGHT }

class RNSwipeableView @JvmOverloads constructor(context: Context) : ReactViewGroup(context) {

    var maxOffsetRight: Float = dpToPx(this,75f)
    var maxOffsetLeft: Float = dpToPx(this,75f)

    var thresholdOpenLeft: Float = dpToPx(this,30f)
    var thresholdCloseLeft: Float = dpToPx(this,30f)

    var thresholdOpenRight: Float = dpToPx(this,30f)
    var thresholdCloseRight: Float = dpToPx(this,30f)

    private var currentOffset = 0f
    private var startOffset = 0f
    private var openSide = OpenSide.NONE

    private var downRawX = 0f
    private var downRawY = 0f
    private var isDragging = false

    private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop

    private var appliedOffsetInt = 0
    private var snapAnimator: ValueAnimator? = null

    init {
        clipChildren = false
        clipToPadding = false
    }

    fun reset() {
        closeInternal(animated = true)
    }

    private fun sendEvent(eventName: String) {
        val reactContext = context as? ThemedReactContext ?: return
        val payload = Arguments.createMap().apply {
            putDouble("offset", currentOffset.toDouble())
            putString("side", openSide.name) // NONE/LEFT/RIGHT
        }

        reactContext
            .getJSModule(RCTEventEmitter::class.java)
            .receiveEvent(id, eventName, payload)
    }

    private fun onBegan() {
        RNSwipeableManager.onDragBegan(this)
        sendEvent("topSwipeableBegan")
    }

    private fun onFinish() {
        sendEvent("topSwipeableFinish")
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        when (ev.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                downRawX = ev.rawX
                downRawY = ev.rawY
                isDragging = false
                startOffset = currentOffset
                return false
            }

            MotionEvent.ACTION_MOVE -> {
                val dx = ev.rawX - downRawX
                val dy = ev.rawY - downRawY

                if (!isDragging && abs(dx) > touchSlop && abs(dx) > abs(dy)) {
                    isDragging = true
                    snapAnimator?.cancel()
                    setLayerType(LAYER_TYPE_HARDWARE, null)

                    parent?.requestDisallowInterceptTouchEvent(true)
                    onBegan()
                    return true
                }
            }

            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
                isDragging = false
            }
        }
        return super.onInterceptTouchEvent(ev)
    }

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        when (ev.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                downRawX = ev.rawX
                downRawY = ev.rawY
                isDragging = false
                startOffset = currentOffset
                return true
            }

            MotionEvent.ACTION_MOVE -> {
                val dx = ev.rawX - downRawX
                val dy = ev.rawY - downRawY

                if (!isDragging) {
                    if (abs(dx) > touchSlop && abs(dx) > abs(dy)) {
                        isDragging = true
                        snapAnimator?.cancel()
                        setLayerType(LAYER_TYPE_HARDWARE, null)
                        parent?.requestDisallowInterceptTouchEvent(true)
                        onBegan()
                    } else {
                        return false
                    }
                }

                var newOffset = startOffset + dx
                val max = if (newOffset > 0f) maxOffsetRight else maxOffsetLeft
                newOffset = newOffset.coerceIn(-max, max)

                setOffset(newOffset)
                return true
            }

            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                if (isDragging) {
                    setLayerType(LAYER_TYPE_NONE, null)

                    val delta = currentOffset - startOffset
                    when (openSide) {
                        OpenSide.NONE -> {
                            when {
                                currentOffset >= thresholdOpenRight -> openRightInternal()
                                currentOffset <= -thresholdOpenLeft -> openLeftInternal()
                                else -> closeInternal(animated = true)
                            }
                        }

                        OpenSide.LEFT -> {
                            if (abs(delta) >= thresholdCloseLeft) closeInternal(animated = true) else openLeftInternal()
                        }

                        OpenSide.RIGHT -> {
                            if (abs(delta) >= thresholdCloseRight) closeInternal(animated = true) else openRightInternal()
                        }
                    }
                    isDragging = false
                    return true
                }

                setLayerType(LAYER_TYPE_NONE, null)
                return super.onTouchEvent(ev)
            }
        }
        return super.onTouchEvent(ev)
    }

    private fun setOffset(offset: Float) {
        currentOffset = offset

        val newInt = offset.roundToInt()
        val delta = newInt - appliedOffsetInt
        if (delta != 0) {
            for (i in 0 until childCount) {
                getChildAt(i).offsetLeftAndRight(delta)
            }
            appliedOffsetInt = newInt
            invalidate()
        }
    }


    private fun openRightInternal() {
        openSide = OpenSide.RIGHT
        animateTo(maxOffsetRight, notifyCoordinator = false)
    }

    private fun openLeftInternal() {
        openSide = OpenSide.LEFT
        animateTo(-maxOffsetLeft, notifyCoordinator = false)
    }

    private fun closeInternal(animated: Boolean, notifyCoordinator: Boolean = true) {
        openSide = OpenSide.NONE

        if (animated) {
            animateTo(0f, notifyCoordinator)
        } else {
            setOffset(0f)
            if (notifyCoordinator) {
                RNSwipeableManager.onClosed(this)
            }
        }
    }

    fun closeFromCoordinator() {
        closeInternal(animated = true, notifyCoordinator = false)
    }

    private fun animateTo(target: Float, notifyCoordinator: Boolean = true) {
        snapAnimator?.cancel()
        val from = currentOffset

        snapAnimator = ValueAnimator.ofFloat(from, target).apply {
            duration = 320L
            interpolator = DecelerateInterpolator()

            addUpdateListener { anim ->
                val v = anim.animatedValue as Float
                setOffset(v)
            }

            addListener(object : android.animation.AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: android.animation.Animator) {
                    // гарантируем точное финальное состояние
                    setOffset(target)
                    setLayerType(LAYER_TYPE_NONE, null)

                    if (target == 0f && notifyCoordinator) {
                        RNSwipeableManager.onClosed(this@RNSwipeableView)
                    }

                    onFinish()
                }
            })
        }

        snapAnimator?.start()
    }
}
