import UIKit
import Combine
import Dependencies

public final class RootViewController: UIViewController {
  @Dependency(\.app.statusBar) var statusBar
  @Dependency(\.app.hudManager) var hudManager
  @Dependency(\.app.toastManager) var toastManager
  
  var hud: HUDView?
  var cancellables = Set<AnyCancellable>()
  public let navController: UINavigationController
  
  var toastTimer: Timer?
  let toastTopPadding: CGFloat = 10
  var topToastConstraint: NSLayoutConstraint?
  
  public init(_ content: UINavigationController) {
    self.navController = content
    super.init(nibName: nil, bundle: nil)
  }
  
  required init?(coder: NSCoder) { nil }
  
  public override var preferredStatusBarStyle: UIStatusBarStyle  {
    statusBar.get()
  }
  
  public override func viewDidLoad() {
    super.viewDidLoad()
    
    addChild(navController)
    view.addSubview(navController.view)
    navController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    navController.view.frame = view.bounds
    navController.didMove(toParent: self)
    
    statusBar
      .observe()
      .receive(on: DispatchQueue.main)
      .sink { [weak self] _ in
        UIView.animate(withDuration: 0.2) {
          self?.setNeedsStatusBarAppearanceUpdate()
        }
      }.store(in: &cancellables)
    
    toastManager
      .observe()
      .receive(on: DispatchQueue.main)
      .sink { [unowned self] model in
        let toastView = ToastView(model: model)
        add(toastView: toastView)
        present(toastView: toastView)
      }.store(in: &cancellables)
    
    hudManager
      .observe()
      .receive(on: DispatchQueue.main)
      .sink { [unowned self] model in
        guard let model else {
          guard let hud else { return }
          UIView.animate(withDuration: 0.2) {
            hud.alpha = 0.0
          } completion: { _ in
            hud.removeFromSuperview()
            self.hud = nil
          }
          return
        }
        add(hudView: HUDView().setup(model: model))
        view.endEditing(true)
      }.store(in: &cancellables)
  }
}

extension RootViewController {
  @objc private func didPanToast(_ sender: UIPanGestureRecognizer) {
    guard let toastView = sender.view else { return }
    
    switch sender.state {
    case .began, .changed:
      toastTimer?.invalidate()
      let padding = toastTopPadding + min(0, sender.translation(in: view).y)
      topToastConstraint?.constant = padding
      
    case .cancelled, .ended, .failed:
      let halfFrameHeight = -0.5 * toastView.frame.height
      let verticalTranslation = sender.translation(in: toastView).y
      let didSwipeAboveHalf = verticalTranslation < halfFrameHeight
      
      if didSwipeAboveHalf {
        dismiss(toastView: toastView)
      } else {
        present(toastView: toastView)
      }
      
    case .possible:
      break
    @unknown default:
      break
    }
  }
  
  private func dismiss(toastView: UIView) {
    toastView.isUserInteractionEnabled = false
    topToastConstraint?.constant = -(toastView.frame.height + view.safeAreaLayoutGuide.layoutFrame.minY)
    
    topToastConstraint = nil
    UIView.animate(withDuration: 0.25) {
      self.view.setNeedsLayout()
      self.view.layoutIfNeeded()
    } completion: { _ in
      toastView.isUserInteractionEnabled = true
      toastView.removeFromSuperview()
      self.toastManager.dismiss()
    }
  }
  
  private func add(toastView: UIView) {
    let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPanToast(_:)))
    toastView.addGestureRecognizer(gestureRecognizer)
    
    toastView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(toastView)
    
    NSLayoutConstraint.activate([
      toastView.heightAnchor.constraint(equalToConstant: 78),
      toastView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 20),
      toastView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -20)
    ])
    
    topToastConstraint = toastView.topAnchor.constraint(
      equalTo: view.safeAreaLayoutGuide.topAnchor,
      constant: -(toastView.frame.height + view.safeAreaLayoutGuide.layoutFrame.height)
    )
    
    topToastConstraint?.isActive = true
    
    view.setNeedsLayout()
    view.layoutIfNeeded()
  }
  
  private func present(toastView: UIView) {
    toastView.isUserInteractionEnabled = false
    topToastConstraint?.constant = toastTopPadding
    
    UIView.animate(
      withDuration: 0.5,
      delay: 0,
      usingSpringWithDamping: 1,
      initialSpringVelocity: 0.5,
      options: .curveEaseInOut
    ) {
      self.view.setNeedsLayout()
      self.view.layoutIfNeeded()
    } completion: { _ in
      toastView.isUserInteractionEnabled = true
      
      self.toastTimer?.invalidate()
      self.toastTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] _ in
        guard let self else { return }
        self.dismiss(toastView: toastView)
      }
    }
  }
}

extension RootViewController {
  private func add(hudView: HUDView) {
    if let hud {
      hud.removeFromSuperview()
      self.hud = nil
    }
    
    hudView.alpha = 0.0
    hudView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(hudView)
    
    NSLayoutConstraint.activate([
      hudView.topAnchor.constraint(equalTo: view.topAnchor),
      hudView.leftAnchor.constraint(equalTo: view.leftAnchor),
      hudView.rightAnchor.constraint(equalTo: view.rightAnchor),
      hudView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
    ])
    
    view.setNeedsLayout()
    view.layoutIfNeeded()
    
    UIView.animate(withDuration: 0.2) {
      hudView.alpha = 1.0
    }
    
    hud = hudView
  }
}