//
//  RNSwipeableView.swift
//  humand
//
//  Created by Oleksandr Shumihin on 16/2/26.
//  Copyright © 2026 Humand. All rights reserved.
//

import Foundation
import UIKit

private enum OpenSide {
  case none
  case left
  case right
}

@objc(RNSwipeableView)
final class SwipeableView: UIView, UIGestureRecognizerDelegate {
  @objc var maxOffsetRight: CGFloat = 75
  @objc var maxOffsetLeft: CGFloat = 75

  @objc var thresholdOpenLeft: CGFloat = 30
  @objc var thresholdCloseLeft: CGFloat = 30

  @objc var thresholdOpenRight: CGFloat = 30
  @objc var thresholdCloseRight: CGFloat = 30

  private var pan: UIPanGestureRecognizer!
  private var currentOffset: CGFloat = 0
  private var startOffset: CGFloat = 0
  private var openSide: OpenSide = .none

  @objc var onSwipeableBegan: RCTDirectEventBlock?
  @objc var onSwipeableFinish: RCTDirectEventBlock?

  private weak static var activeView: SwipeableView?

  @objc
  static func closeAll() {
    if let prev = activeView {
      prev.reset()
      activeView = nil
    }
  }

  private static func saveOpen(_ view: SwipeableView) {
    if let prev = activeView, prev !== view {
      prev.reset()
    }
    activeView = view
  }

  private static func closeOther(except view: SwipeableView) {
    if let prev = activeView, prev !== view {
      prev.reset()
      activeView = nil
    }
  }

  private static func notifyClosed(_ view: SwipeableView) {
    if activeView === view {
      activeView = nil
    }
  }

  @objc
  override init(frame: CGRect) {
    super.init(frame: frame)
    setup()
  }

  @objc
  required init?(coder: NSCoder) {
    super.init(coder: coder)
    setup()
  }

  private func setup() {
    clipsToBounds = false

    pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
    pan.delegate = self

    pan.cancelsTouchesInView = false // If true, once this gesture is recognized, touches in subviews are cancelled.
    pan.delaysTouchesBegan = false // If true, subviews won’t receive touchesBegan until this gesture fails.
    pan.delaysTouchesEnded = false // If true, subviews won’t receive touchesEnded until this gesture fails.

    addGestureRecognizer(pan)
  }

  @objc private func handlePan(_ g: UIPanGestureRecognizer) {
    switch g.state {
    case .began:
      startOffset = currentOffset
      onSwipeableBegan?([:])

      SwipeableView.closeOther(except: self)

    case .changed:
      let dx = g.translation(in: self).x
      var newOffset = startOffset + dx
      let maxOffset = newOffset > 0 ? maxOffsetRight : maxOffsetLeft

      newOffset = min(max(newOffset, -maxOffset), maxOffset)
      setOffset(newOffset)

    case .ended:
      let delta = currentOffset - startOffset
      switch openSide {
      case .none:
        if currentOffset >= thresholdOpenRight {
          openRight()
        } else if currentOffset <= -thresholdOpenLeft {
          openLeft()
        } else {
          close()
        }
      case .left:
        if abs(delta) >= thresholdCloseLeft {
          close()
        } else {
          openLeft()
        }
      case .right:
        if abs(delta) >= thresholdCloseRight {
          close()
        } else {
          openRight()
        }
      }

    case .cancelled, .failed:
      close()

    default:
      break
    }
  }

  private func openRight() {
    openSide = .right
    animateOffset(to: maxOffsetRight)
    onSwipeableFinish?([:])

    SwipeableView.saveOpen(self)
  }

  private func openLeft() {
    openSide = .left
    animateOffset(to: -maxOffsetLeft)
    onSwipeableFinish?([:])

    SwipeableView.saveOpen(self)
  }

  private func close() {
    animateOffset(to: 0)
    openSide = .none
    onSwipeableFinish?([:])

    SwipeableView.notifyClosed(self)
  }

  private func setOffset(_ offset: CGFloat) {
    currentOffset = offset
    applyOffset(offset)
  }

  private func applyOffset(_ offset: CGFloat) {
    self.transform = CGAffineTransform(translationX: offset, y: 0)
  }

  private func animateOffset(to target: CGFloat) {
    let animator = UIViewPropertyAnimator(duration: 0.32, dampingRatio: 0.85) { [weak self] in
      self?.setOffset(target)
      self?.layoutIfNeeded()
    }
    animator.startAnimation()
  }

  // if true  -> pan begins
  // if false -> pan does not start
  override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    guard let pan = gestureRecognizer as? UIPanGestureRecognizer else { return true }
    let t = pan.translation(in: self)
    let v = pan.velocity(in: self)

    // Activate Pan if horizontal movement is detected and is velocity is greater than vertical movement  
    return abs(t.x) > 0 && abs(v.x) >= abs(v.y)
  }

  // if true  -> both gestures can recognize together
  // if false -> only one gesture can win
  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                         shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
  {
    return true
  }

  // if true  -> this gesture waits until the other fails
  // if false -> this gesture does not wait
  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                         shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool
  {
    return false
  }

  // if true  -> other gestures must wait until this one fails
  // if false -> other gestures do not wait
  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                         shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool
  {
    return true
  }

  @objc func reset() {
    close()
  }
}
