//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023 Jellyfin & Jellyfin Contributors
//

// Modification of https://github.com/zattoo/TvOSSlider

import SwiftUI
import UIKit

// TODO: Replace

private let trackViewHeight: CGFloat = 5
private let animationDuration: TimeInterval = 0.3
private let defaultValue: Float = 0
private let defaultMinimumValue: Float = 0
private let defaultMaximumValue: Float = 1
private let defaultIsContinuous: Bool = true
private let defaultThumbTintColor: UIColor = .white
private let defaultTrackColor: UIColor = .gray
private let defaultMininumTrackTintColor: UIColor = .blue
private let defaultFocusScaleFactor: CGFloat = 1.05
private let defaultStepValue: Float = 0.1
private let decelerationRate: Float = 0.92
private let decelerationMaxVelocity: Float = 1000

/// A control used to select a single value from a continuous range of values.
final class UITVOSSlider: UIControl {

    /// The slider’s current value.
    var value: Float {
        get {
            storedValue
        }
        set {
            storedValue = min(maximumValue, newValue)
            storedValue = max(minimumValue, storedValue)

            var offset = trackView.bounds.width * CGFloat((storedValue - minimumValue) / (maximumValue - minimumValue))
            offset = min(trackView.bounds.width, offset)
            thumbViewCenterXConstraint.constant = offset
        }
    }

    /// The minimum value of the slider.
    var minimumValue: Float = defaultMinimumValue {
        didSet {
            value = max(value, minimumValue)
        }
    }

    /// The maximum value of the slider.
    var maximumValue: Float = defaultMaximumValue {
        didSet {
            value = min(value, maximumValue)
        }
    }

    /// A Boolean value indicating whether changes in the slider’s value generate continuous update events.
    var isContinuous: Bool = defaultIsContinuous

    /// The color used to tint the default minimum track images.
    var minimumTrackTintColor: UIColor? = defaultMininumTrackTintColor {
        didSet {
            minimumTrackView.backgroundColor = minimumTrackTintColor
        }
    }

    /// The color used to tint the default maximum track images.
    var maximumTrackTintColor: UIColor? {
        didSet {
            maximumTrackView.backgroundColor = maximumTrackTintColor
        }
    }

    /// The color used to tint the default thumb images.
    var thumbTintColor: UIColor = defaultThumbTintColor {
        didSet {
            thumbView.backgroundColor = thumbTintColor
        }
    }

    /// Scale factor applied to the slider when receiving the focus
    var focusScaleFactor: CGFloat = defaultFocusScaleFactor {
        didSet {
            updateStateDependantViews()
        }
    }

    /// Value added or subtracted from the current value on steps left or right updates
    var stepValue: Float = defaultStepValue

    /// Damping value for panning gestures
    var panDampingValue: Float = 5

    // Size for thumb view
    var thumbSize: CGFloat = 30

    var fineTunningVelocityThreshold: Float = 600

    /**
     Sets the slider’s current value, allowing you to animate the change visually.

     - Parameters:
        - value: The new value to assign to the value property
        - animated: Specify true to animate the change in value; otherwise, specify false to update the slider’s appearance immediately. Animations are performed asynchronously and do not block the calling thread.
     */
    func setValue(_ value: Float, animated: Bool) {
        self.value = value
        stopDeceleratingTimer()

        if animated {
            UIView.animate(withDuration: animationDuration) {
                self.setNeedsLayout()
                self.layoutIfNeeded()
            }
        }
    }

    /**
     Assigns a minimum track image to the specified control states.

     - Parameters:
        - image: The minimum track image to associate with the specified states.
        - state: The control state with which to associate the image.
     */
    func setMinimumTrackImage(_ image: UIImage?, for state: UIControl.State) {
        minimumTrackViewImages[state.rawValue] = image
        updateStateDependantViews()
    }

    /**
     Assigns a maximum track image to the specified control states.

     - Parameters:
        - image: The maximum track image to associate with the specified states.
        - state: The control state with which to associate the image.
     */
    func setMaximumTrackImage(_ image: UIImage?, for state: UIControl.State) {
        maximumTrackViewImages[state.rawValue] = image
        updateStateDependantViews()
    }

    /**
     Assigns a thumb image to the specified control states.

     - Parameters:
        - image: The thumb image to associate with the specified states.
        - state: The control state with which to associate the image.
     */
    func setThumbImage(_ image: UIImage?, for state: UIControl.State) {
        thumbViewImages[state.rawValue] = image
        updateStateDependantViews()
    }

    // MARK: - Initializers

    private var onEditingChanged: (Bool) -> Void
    private var valueBinding: Binding<CGFloat>

    init(
        value: Binding<CGFloat>,
        onEditingChanged: @escaping (Bool) -> Void
    ) {
        self.onEditingChanged = onEditingChanged
        self.valueBinding = value

        super.init(frame: .zero)

        setUpView()
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    // MARK: - UIControlStates

    /// :nodoc:
    override var isEnabled: Bool {
        didSet {
            panGestureRecognizer.isEnabled = isEnabled
            updateStateDependantViews()
        }
    }

    /// :nodoc:
    override var isSelected: Bool {
        didSet {
            updateStateDependantViews()
        }
    }

    /// :nodoc:
    override var isHighlighted: Bool {
        didSet {
            updateStateDependantViews()
        }
    }

    /// :nodoc:
    override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
        coordinator.addCoordinatedAnimations({
            self.updateStateDependantViews()
        }, completion: nil)
    }

    // MARK: - Private

    private typealias ControlState = UInt

    private var storedValue: Float = defaultValue

    private var thumbViewImages: [ControlState: UIImage] = [:]
    private var thumbView: UIImageView!

    private var trackViewImages: [ControlState: UIImage] = [:]
    private var trackView: UIImageView!

    private var minimumTrackViewImages: [ControlState: UIImage] = [:]
    private var minimumTrackView: UIImageView!

    private var maximumTrackViewImages: [ControlState: UIImage] = [:]
    private var maximumTrackView: UIImageView!

    private var panGestureRecognizer: UIPanGestureRecognizer!
    private var leftTapGestureRecognizer: UITapGestureRecognizer!
    private var rightTapGestureRecognizer: UITapGestureRecognizer!

    private var thumbViewCenterXConstraint: NSLayoutConstraint!

    private weak var deceleratingTimer: Timer?
    private var deceleratingVelocity: Float = 0

    private var thumbViewCenterXConstraintConstant: Float = 0

    private func setUpView() {
        setUpTrackView()
        setUpMinimumTrackView()
        setUpMaximumTrackView()
        setUpThumbView()

        setUpTrackViewConstraints()
        setUpMinimumTrackViewConstraints()
        setUpMaximumTrackViewConstraints()
        setUpThumbViewConstraints()

        setUpGestures()

        updateStateDependantViews()
    }

    private func setUpThumbView() {
        thumbView = UIImageView()
        thumbView.layer.cornerRadius = thumbSize / 6
        thumbView.backgroundColor = thumbTintColor
        addSubview(thumbView)
    }

    private func setUpTrackView() {
        trackView = UIImageView()
        trackView.layer.cornerRadius = trackViewHeight / 2
        trackView.backgroundColor = defaultTrackColor.withAlphaComponent(0.3)
        addSubview(trackView)
    }

    private func setUpMinimumTrackView() {
        minimumTrackView = UIImageView()
        minimumTrackView.layer.cornerRadius = trackViewHeight / 2
        minimumTrackView.backgroundColor = minimumTrackTintColor
        addSubview(minimumTrackView)
    }

    private func setUpMaximumTrackView() {
        maximumTrackView = UIImageView()
        maximumTrackView.layer.cornerRadius = trackViewHeight / 2
        maximumTrackView.backgroundColor = maximumTrackTintColor
        addSubview(maximumTrackView)
    }

    private func setUpTrackViewConstraints() {
        trackView.translatesAutoresizingMaskIntoConstraints = false
        trackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        trackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        trackView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
        trackView.heightAnchor.constraint(equalToConstant: trackViewHeight).isActive = true
    }

    private func setUpMinimumTrackViewConstraints() {
        minimumTrackView.translatesAutoresizingMaskIntoConstraints = false
        minimumTrackView.leadingAnchor.constraint(equalTo: trackView.leadingAnchor).isActive = true
        minimumTrackView.trailingAnchor.constraint(equalTo: thumbView.centerXAnchor).isActive = true
        minimumTrackView.centerYAnchor.constraint(equalTo: trackView.centerYAnchor).isActive = true
        minimumTrackView.heightAnchor.constraint(equalToConstant: trackViewHeight).isActive = true
    }

    private func setUpMaximumTrackViewConstraints() {
        maximumTrackView.translatesAutoresizingMaskIntoConstraints = false
        maximumTrackView.leadingAnchor.constraint(equalTo: thumbView.centerXAnchor).isActive = true
        maximumTrackView.trailingAnchor.constraint(equalTo: trackView.trailingAnchor).isActive = true
        maximumTrackView.centerYAnchor.constraint(equalTo: trackView.centerYAnchor).isActive = true
        maximumTrackView.heightAnchor.constraint(equalToConstant: trackViewHeight).isActive = true
    }

    private func setUpThumbViewConstraints() {
        thumbView.translatesAutoresizingMaskIntoConstraints = false
        thumbView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
        thumbView.widthAnchor.constraint(equalToConstant: thumbSize / 3).isActive = true
        thumbView.heightAnchor.constraint(equalToConstant: thumbSize).isActive = true
        thumbViewCenterXConstraint = thumbView.centerXAnchor.constraint(equalTo: trackView.leadingAnchor, constant: CGFloat(value))
        thumbViewCenterXConstraint.isActive = true
    }

    private func setUpGestures() {
        panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureWasTriggered(panGestureRecognizer:)))
        addGestureRecognizer(panGestureRecognizer)

        leftTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(leftTapWasTriggered))
        leftTapGestureRecognizer.allowedPressTypes = [NSNumber(value: UIPress.PressType.leftArrow.rawValue)]
        leftTapGestureRecognizer.allowedTouchTypes = [NSNumber(value: UITouch.TouchType.indirect.rawValue)]
        addGestureRecognizer(leftTapGestureRecognizer)

        rightTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(rightTapWasTriggered))
        rightTapGestureRecognizer.allowedPressTypes = [NSNumber(value: UIPress.PressType.rightArrow.rawValue)]
        rightTapGestureRecognizer.allowedTouchTypes = [NSNumber(value: UITouch.TouchType.indirect.rawValue)]
        addGestureRecognizer(rightTapGestureRecognizer)
    }

    private func updateStateDependantViews() {
        thumbView.image = thumbViewImages[state.rawValue] ?? thumbViewImages[UIControl.State.normal.rawValue]

        if isFocused {
            thumbView.transform = CGAffineTransform(scaleX: focusScaleFactor, y: focusScaleFactor)
        } else {
            thumbView.transform = CGAffineTransform.identity
        }
    }

    @objc
    private func handleDeceleratingTimer(timer: Timer) {
        let centerX = thumbViewCenterXConstraintConstant + deceleratingVelocity * 0.01
        let percent = centerX / Float(trackView.frame.width)
        value = minimumValue + ((maximumValue - minimumValue) * percent)

        if isContinuous {
            sendActions(for: .valueChanged)
        }

        thumbViewCenterXConstraintConstant = Float(thumbViewCenterXConstraint.constant)

        deceleratingVelocity *= decelerationRate
        if !isFocused || abs(deceleratingVelocity) < 1 {
            stopDeceleratingTimer()
        }

        valueBinding.wrappedValue = CGFloat(percent)
        onEditingChanged(false)
    }

    private func stopDeceleratingTimer() {
        deceleratingTimer?.invalidate()
        deceleratingTimer = nil
        deceleratingVelocity = 0
        sendActions(for: .valueChanged)
    }

    private func isVerticalGesture(_ recognizer: UIPanGestureRecognizer) -> Bool {
        let translation = recognizer.translation(in: self)
        if abs(translation.y) > abs(translation.x) {
            return true
        }
        return false
    }

    // MARK: - Actions

    @objc
    private func panGestureWasTriggered(panGestureRecognizer: UIPanGestureRecognizer) {

        guard !isVerticalGesture(panGestureRecognizer) else { return }

        let translation = Float(panGestureRecognizer.translation(in: self).x)
        let velocity = Float(panGestureRecognizer.velocity(in: self).x)

        switch panGestureRecognizer.state {
        case .began:
            onEditingChanged(true)

            stopDeceleratingTimer()
            thumbViewCenterXConstraintConstant = Float(thumbViewCenterXConstraint.constant)
        case .changed:
            let centerX = thumbViewCenterXConstraintConstant + translation / panDampingValue
            let percent = centerX / Float(trackView.frame.width)
            value = minimumValue + ((maximumValue - minimumValue) * percent)
            if isContinuous {
                sendActions(for: .valueChanged)
            }

            valueBinding.wrappedValue = CGFloat(percent)
        case .ended, .cancelled:

            thumbViewCenterXConstraintConstant = Float(thumbViewCenterXConstraint.constant)

            if abs(velocity) > fineTunningVelocityThreshold {
                let direction: Float = velocity > 0 ? 1 : -1
                deceleratingVelocity = abs(velocity) > decelerationMaxVelocity ? decelerationMaxVelocity * direction : velocity
                deceleratingTimer = Timer.scheduledTimer(
                    timeInterval: 0.01,
                    target: self,
                    selector: #selector(handleDeceleratingTimer(timer:)),
                    userInfo: nil,
                    repeats: true
                )
            } else {
                onEditingChanged(false)
                stopDeceleratingTimer()
            }
        default:
            break
        }
    }

    @objc
    private func leftTapWasTriggered() {
        //        setValue(value-stepValue, animated: true)
//        viewModel.playerOverlayDelegate?.didSelectBackward()
    }

    @objc
    private func rightTapWasTriggered() {
        //        setValue(value+stepValue, animated: true)
//        viewModel.playerOverlayDelegate?.didSelectForward()
    }
}
