import Foundation
import UIKit
import Display
import ComponentFlow
import ComponentDisplayAdapters
import AppBundle
import MultilineTextComponent

private let titleFontWithIcon = Font.medium(13.0)
private let titleFontWithoutIcon = Font.regular(17.0)

private final class SwipeOptionsGestureRecognizer: UIPanGestureRecognizer {
    public var validatedGesture = false
    public var firstLocation: CGPoint = CGPoint()
    
    public var allowAnyDirection = false
    public var lastVelocity: CGPoint = CGPoint()
    
    override public init(target: Any?, action: Selector?) {
        super.init(target: target, action: action)
        
        if #available(iOS 13.4, *) {
            self.allowedScrollTypesMask = .continuous
        }
        
        self.maximumNumberOfTouches = 1
    }
    
    override public func reset() {
        super.reset()
        
        self.validatedGesture = false
    }
    
    public func becomeCancelled() {
        self.state = .cancelled
    }
    
    override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesBegan(touches, with: event)
        
        let touch = touches.first!
        self.firstLocation = touch.location(in: self.view)
    }
    
    override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        let location = touches.first!.location(in: self.view)
        let translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y)
        
        if !self.validatedGesture {
            if !self.allowAnyDirection && translation.x > 0.0 {
                self.state = .failed
            } else if abs(translation.y) > 4.0 && abs(translation.y) > abs(translation.x) * 2.5 {
                self.state = .failed
            } else if abs(translation.x) > 4.0 && abs(translation.y) * 2.5 < abs(translation.x) {
                self.validatedGesture = true
            }
        }
        
        if self.validatedGesture {
            self.lastVelocity = self.velocity(in: self.view)
            super.touchesMoved(touches, with: event)
        }
    }
}

open class ListItemSwipeOptionContainer: UIView, UIGestureRecognizerDelegate {
    public struct Option: Equatable {
        public enum Icon: Equatable {
            case none
            case image(image: UIImage)
            
            public static func ==(lhs: Icon, rhs: Icon) -> Bool {
                switch lhs {
                case .none:
                    if case .none = rhs {
                        return true
                    } else {
                        return false
                    }
                case let .image(lhsImage):
                    if case let .image(rhsImage) = rhs, lhsImage == rhsImage {
                        return true
                    } else {
                        return false
                    }
                }
            }
        }
        
        public let key: AnyHashable
        public let title: String
        public let icon: Icon
        public let color: UIColor
        public let textColor: UIColor
        
        public init(key: AnyHashable, title: String, icon: Icon, color: UIColor, textColor: UIColor) {
            self.key = key
            self.title = title
            self.icon = icon
            self.color = color
            self.textColor = textColor
        }
        
        public static func ==(lhs: Option, rhs: Option) -> Bool {
            if lhs.key != rhs.key {
                return false
            }
            if lhs.title != rhs.title {
                return false
            }
            if !lhs.color.isEqual(rhs.color) {
                return false
            }
            if !lhs.textColor.isEqual(rhs.textColor) {
                return false
            }
            if lhs.icon != rhs.icon {
                return false
            }
            return true
        }
    }

    private enum OptionAlignment {
        case left
        case right
    }

    private final class OptionView: UIView {
        private let backgroundView: UIView
        private let title = ComponentView<Empty>()
        private var iconView: UIImageView?
        
        private let titleString: String
        private let textColor: UIColor
        
        private var titleSize: CGSize?
        
        var alignment: OptionAlignment?
        var isExpanded: Bool = false
        
        init(title: String, icon: Option.Icon, color: UIColor, textColor: UIColor) {
            self.titleString = title
            self.textColor = textColor
            
            self.backgroundView = UIView()
            
            switch icon {
            case let .image(image):
                let iconView = UIImageView()
                iconView.image = image.withRenderingMode(.alwaysTemplate)
                iconView.tintColor = textColor
                self.iconView = iconView
            case .none:
                self.iconView = nil
            }
            
            super.init(frame: CGRect())
            
            self.addSubview(self.backgroundView)
            if let iconView = self.iconView {
                self.addSubview(iconView)
            }
            self.backgroundView.backgroundColor = color
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        func updateLayout(
            isFirst: Bool,
            isLeft: Bool,
            baseSize: CGSize,
            alignment: OptionAlignment,
            isExpanded: Bool,
            extendedWidth: CGFloat,
            sideInset: CGFloat,
            transition: ComponentTransition,
            additive: Bool,
            revealFactor: CGFloat,
            animateIconMovement: Bool
        ) {
            var animateAdditive = false
            if additive && !transition.animation.isImmediate && self.isExpanded != isExpanded {
                animateAdditive = true
            }
            
            let backgroundFrame: CGRect
            if isFirst {
                backgroundFrame = CGRect(origin: CGPoint(x: isLeft ? -400.0 : 0.0, y: 0.0), size: CGSize(width: extendedWidth + 400.0, height: baseSize.height))
            } else {
                backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: extendedWidth, height: baseSize.height))
            }
            let deltaX: CGFloat
            if animateAdditive {
                let previousFrame = self.backgroundView.frame
                self.backgroundView.frame = backgroundFrame
                if isLeft {
                    deltaX = previousFrame.width - backgroundFrame.width
                } else {
                    deltaX = -(previousFrame.width - backgroundFrame.width)
                }
                if !animateIconMovement {
                    transition.animatePosition(view: self.backgroundView, from: CGPoint(x: deltaX, y: 0.0), to: CGPoint(), additive: true)
                }
            } else {
                deltaX = 0.0
                transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
            }
            
            self.alignment = alignment
            self.isExpanded = isExpanded
            let titleSize = self.titleSize ?? CGSize(width: 32.0, height: 10.0)
            var contentRect = CGRect(origin: CGPoint(), size: baseSize)
            switch alignment {
            case .left:
                contentRect.origin.x = 0.0
            case .right:
                contentRect.origin.x = extendedWidth - contentRect.width
            }
            
            if let iconView = self.iconView, let imageSize = iconView.image?.size {
                let iconOffset: CGFloat = -9.0
                let titleIconSpacing: CGFloat = 11.0
                let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - imageSize.width + sideInset) / 2.0), y: contentRect.midY - imageSize.height / 2.0 + iconOffset), size: imageSize)
                if animateAdditive {
                    let iconOffsetX = animateIconMovement ? iconView.frame.minX - iconFrame.minX : deltaX
                    iconView.frame = iconFrame
                    transition.animatePosition(view: iconView, from: CGPoint(x: iconOffsetX, y: 0.0), to: CGPoint(), additive: true)
                } else {
                    transition.setFrame(view: iconView, frame: iconFrame)
                }
                
                let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.midY + titleIconSpacing), size: titleSize)
                if let titleView = self.title.view {
                    if titleView.superview == nil {
                        self.addSubview(titleView)
                    }
                    if animateAdditive {
                        let titleOffsetX = animateIconMovement ? titleView.frame.minX - titleFrame.minX : deltaX
                        titleView.frame = titleFrame
                        transition.animatePosition(view: titleView, from: CGPoint(x: titleOffsetX, y: 0.0), to: CGPoint(), additive: true)
                    } else {
                        transition.setFrame(view: titleView, frame: titleFrame)
                    }
                }
            } else {
                let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.minY + floor((baseSize.height - titleSize.height) / 2.0)), size: titleSize)
                
                if let titleView = self.title.view {
                    if titleView.superview == nil {
                        self.addSubview(titleView)
                    }
                    if animateAdditive {
                        let titleOffsetX = animateIconMovement ? titleView.frame.minX - titleFrame.minX : deltaX
                        titleView.frame = titleFrame
                        transition.animatePosition(view: titleView, from: CGPoint(x: titleOffsetX, y: 0.0), to: CGPoint(), additive: true)
                    } else {
                        transition.setFrame(view: titleView, frame: titleFrame)
                    }
                }
            }
        }
        
        func calculateSize(_ constrainedSize: CGSize) -> CGSize {
            let titleSize = self.title.update(
                transition: .immediate,
                component: AnyComponent(MultilineTextComponent(
                    text: .plain(NSAttributedString(string: self.titleString, font: self.iconView == nil ? titleFontWithoutIcon : titleFontWithIcon, textColor: self.textColor))
                )),
                environment: {},
                containerSize: CGSize(width: 200.0, height: 100.0)
            )
            self.titleSize = titleSize
            
            var maxWidth = titleSize.width
            if let iconView = self.iconView, let image = iconView.image {
                maxWidth = max(image.size.width, maxWidth)
            }
            return CGSize(width: max(74.0, maxWidth + 20.0), height: constrainedSize.height)
        }
    }

    public final class OptionsView: UIView {
        private let optionSelected: (Option) -> Void
        private let tapticAction: () -> Void
        
        private var options: [Option] = []
        private var isLeft: Bool = false
        
        private var optionViews: [OptionView] = []
        private var revealOffset: CGFloat = 0.0
        private var sideInset: CGFloat = 0.0
        
        public init(optionSelected: @escaping (Option) -> Void, tapticAction: @escaping () -> Void) {
            self.optionSelected = optionSelected
            self.tapticAction = tapticAction
            
            super.init(frame: CGRect())
            
            let gestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
            gestureRecognizer.tapActionAtPoint = { _ in
                return .waitForSingleTap
            }
            self.addGestureRecognizer(gestureRecognizer)
        }
        
        required public init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        public func setOptions(_ options: [Option], isLeft: Bool) {
            if self.options != options || self.isLeft != isLeft {
                self.options = options
                self.isLeft = isLeft
                for optionView in self.optionViews {
                    optionView.removeFromSuperview()
                }
                self.optionViews = options.map { option in
                    return OptionView(title: option.title, icon: option.icon, color: option.color, textColor: option.textColor)
                }
                if isLeft {
                    for optionView in self.optionViews.reversed() {
                        self.addSubview(optionView)
                    }
                } else {
                    for optionView in self.optionViews {
                        self.addSubview(optionView)
                    }
                }
            }
        }
        
        func calculateSize(_ constrainedSize: CGSize) -> CGSize {
            var maxWidth: CGFloat = 0.0
            for optionView in self.optionViews {
                let nodeSize = optionView.calculateSize(constrainedSize)
                maxWidth = max(nodeSize.width, maxWidth)
            }
            return CGSize(width: maxWidth * CGFloat(self.optionViews.count), height: constrainedSize.height)
        }
        
        public func updateRevealOffset(offset: CGFloat, sideInset: CGFloat, transition: ComponentTransition) {
            self.revealOffset = offset
            self.sideInset = sideInset
            self.updateNodesLayout(transition: transition)
        }
        
        private func updateNodesLayout(transition: ComponentTransition) {
            let size = self.bounds.size
            if size.width.isLessThanOrEqualTo(0.0) || self.optionViews.isEmpty {
                return
            }
            let basicNodeWidth = floor((size.width - abs(self.sideInset)) / CGFloat(self.optionViews.count))
            let lastNodeWidth = size.width - basicNodeWidth * CGFloat(self.optionViews.count - 1)
            let revealFactor = self.revealOffset / size.width
            let boundaryRevealFactor: CGFloat
            if self.optionViews.count > 2 {
                boundaryRevealFactor = 1.0 + 16.0 / size.width
            } else {
                boundaryRevealFactor = 1.0 + basicNodeWidth / size.width
            }
            let startingOffset: CGFloat
            if self.isLeft {
                startingOffset = size.width + max(0.0, abs(revealFactor) - 1.0) * size.width
            } else {
                startingOffset = 0.0
            }
            
            var completionCount = self.optionViews.count
            let intermediateCompletion = {
            }
            
            var i = self.isLeft ? (self.optionViews.count - 1) : 0
            while i >= 0 && i < self.optionViews.count {
                let optionView = self.optionViews[i]
                let nodeWidth = i == (self.optionViews.count - 1) ? lastNodeWidth : basicNodeWidth
                var nodeTransition = transition
                var isExpanded = false
                if (self.isLeft && i == 0) || (!self.isLeft && i == self.optionViews.count - 1) {
                    if abs(revealFactor) > boundaryRevealFactor {
                        isExpanded = true
                    }
                }
                if let _ = optionView.alignment, optionView.isExpanded != isExpanded {
                    nodeTransition = !transition.animation.isImmediate ? transition : .easeInOut(duration: 0.2)
                    if transition.animation.isImmediate {
                        self.tapticAction()
                    }
                }
                
                var sideInset: CGFloat = 0.0
                if i == self.optionViews.count - 1 {
                    sideInset = self.sideInset
                }
                
                let extendedWidth: CGFloat
                let nodeLeftOffset: CGFloat
                if isExpanded {
                    nodeLeftOffset = 0.0
                    extendedWidth = size.width * max(1.0, abs(revealFactor))
                } else if self.isLeft {
                    let offset = basicNodeWidth * CGFloat(self.optionViews.count - 1 - i)
                    extendedWidth = (size.width - offset) * max(1.0, abs(revealFactor))
                    nodeLeftOffset = startingOffset - extendedWidth - floorToScreenPixels(offset * abs(revealFactor))
                } else {
                    let offset = basicNodeWidth * CGFloat(i)
                    extendedWidth = (size.width - offset) * max(1.0, abs(revealFactor))
                    nodeLeftOffset = startingOffset + floorToScreenPixels(offset * abs(revealFactor))
                }
                
                transition.setFrame(view: optionView, frame: CGRect(origin: CGPoint(x: nodeLeftOffset, y: 0.0), size: CGSize(width: extendedWidth, height: size.height)), completion: { _ in
                    completionCount -= 1
                    intermediateCompletion()
                })
                
                var nodeAlignment: OptionAlignment
                if (self.optionViews.count > 1) {
                    nodeAlignment = self.isLeft ? .right : .left
                } else {
                    if self.isLeft {
                        nodeAlignment = isExpanded ? .right : .left
                    } else {
                        nodeAlignment = isExpanded ? .left : .right
                    }
                }
                let animateIconMovement = self.optionViews.count == 1
                optionView.updateLayout(isFirst: (self.isLeft && i == 0) || (!self.isLeft && i == self.optionViews.count - 1), isLeft: self.isLeft, baseSize: CGSize(width: nodeWidth, height: size.height), alignment: nodeAlignment, isExpanded: isExpanded, extendedWidth: extendedWidth, sideInset: sideInset, transition: nodeTransition, additive: transition.animation.isImmediate, revealFactor: revealFactor, animateIconMovement: animateIconMovement)
                
                if self.isLeft {
                    i -= 1
                } else {
                    i += 1
                }
            }
        }
        
        @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
            if case .ended = recognizer.state, let gesture = recognizer.lastRecognizedGestureAndLocation?.0, case .tap = gesture {
                let location = recognizer.location(in: self)
                var selectedOption: Int?
                
                var i = self.isLeft ? 0 : (self.optionViews.count - 1)
                while i >= 0 && i < self.optionViews.count {
                    if self.optionViews[i].frame.contains(location) {
                        selectedOption = i
                        break
                    }
                    if self.isLeft {
                        i += 1
                    } else {
                        i -= 1
                    }
                }
                if let selectedOption {
                    self.optionSelected(self.options[selectedOption])
                }
            }
        }
        
        public func isDisplayingExtendedAction() -> Bool {
            return self.optionViews.contains(where: { $0.isExpanded })
        }
    }
    
    private var validLayout: (size: CGSize, leftInset: CGFloat, reftInset: CGFloat)?
    
    private var leftRevealView: OptionsView?
    private var rightRevealView: OptionsView?
    private var revealOptions: (left: [Option], right: [Option]) = ([], [])
    
    private var initialRevealOffset: CGFloat = 0.0
    public private(set) var revealOffset: CGFloat = 0.0
    
    private var recognizer: SwipeOptionsGestureRecognizer?
    private var tapRecognizer: UITapGestureRecognizer?
    private var hapticFeedback: HapticFeedback?
    
    private var allowAnyDirection: Bool = false
    
    public var updateRevealOffset: ((CGFloat, ComponentTransition) -> Void)?
    public var revealOptionsInteractivelyOpened: (() -> Void)?
    public var revealOptionsInteractivelyClosed: (() -> Void)?
    public var revealOptionSelected: ((Option, Bool) -> Void)?
    
    open var controlsContainer: UIView {
        return self
    }
    
    public var isDisplayingRevealedOptions: Bool {
        return !self.revealOffset.isZero
    }
    
    override public init(frame: CGRect) {
        super.init(frame: frame)
        
        let recognizer = SwipeOptionsGestureRecognizer(target: self, action: #selector(self.revealGesture(_:)))
        self.recognizer = recognizer
        recognizer.delegate = self
        recognizer.allowAnyDirection = self.allowAnyDirection
        self.addGestureRecognizer(recognizer)
        
        let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.revealTapGesture(_:)))
        self.tapRecognizer = tapRecognizer
        tapRecognizer.delegate = self
        self.addGestureRecognizer(tapRecognizer)
        
        self.disablesInteractiveTransitionGestureRecognizer = self.allowAnyDirection
        
        self.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
            guard let self else {
                return false
            }
            if !self.revealOffset.isZero {
                return true
            }
            return false
        }
    }
    
    required public init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    open func setRevealOptions(_ options: (left: [Option], right: [Option])) {
        if self.revealOptions == options {
            return
        }
        let previousOptions = self.revealOptions
        let wasEmpty = self.revealOptions.left.isEmpty && self.revealOptions.right.isEmpty
        self.revealOptions = options
        let isEmpty = options.left.isEmpty && options.right.isEmpty
        if options.left.isEmpty {
            if let _ = self.leftRevealView {
                self.recognizer?.becomeCancelled()
                self.updateRevealOffsetInternal(offset: 0.0, transition: .spring(duration: 0.3))
            }
        } else if previousOptions.left != options.left {
        }
        if options.right.isEmpty {
            if let _ = self.rightRevealView {
                self.recognizer?.becomeCancelled()
                self.updateRevealOffsetInternal(offset: 0.0, transition: .spring(duration: 0.3))
            }
        } else if previousOptions.right != options.right {
            if let _ = self.rightRevealView {
            }
        }
        if wasEmpty != isEmpty {
            self.recognizer?.isEnabled = !isEmpty
        }
        let allowAnyDirection = !options.left.isEmpty || !self.revealOffset.isZero
        if allowAnyDirection != self.allowAnyDirection {
            self.allowAnyDirection = allowAnyDirection
            self.recognizer?.allowAnyDirection = allowAnyDirection
            self.disablesInteractiveTransitionGestureRecognizer = allowAnyDirection
        }
    }
    
    override open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if let recognizer = self.recognizer, gestureRecognizer == self.tapRecognizer {
            return abs(self.revealOffset) > 0.0 && !recognizer.validatedGesture
        } else if let recognizer = self.recognizer, gestureRecognizer == self.recognizer, recognizer.numberOfTouches == 0 {
            let translation = recognizer.velocity(in: recognizer.view)
            if abs(translation.y) > 4.0 && abs(translation.y) > abs(translation.x) * 2.5 {
                return false
            }
        }
        return true
    }
    
    open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        if let recognizer = self.recognizer, otherGestureRecognizer == recognizer {
            return true
        } else {
            return false
        }
    }
    
    open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        /*if gestureRecognizer === self.recognizer && otherGestureRecognizer is InteractiveTransitionGestureRecognizer {
            return true
        }*/
        return false
    }
    
    @objc private func revealTapGesture(_ recognizer: UITapGestureRecognizer) {
        if case .ended = recognizer.state {
            self.updateRevealOffsetInternal(offset: 0.0, transition: .spring(duration: 0.3))
            self.revealOptionsInteractivelyClosed?()
        }
    }

    @objc private func revealGesture(_ recognizer: SwipeOptionsGestureRecognizer) {
        guard let (size, _, _) = self.validLayout else {
            return
        }
        switch recognizer.state {
            case .began:
                if let leftRevealView = self.leftRevealView {
                    let revealSize = leftRevealView.bounds.size
                    let location = recognizer.location(in: self)
                    if location.x < revealSize.width {
                        recognizer.becomeCancelled()
                    } else {
                        self.initialRevealOffset = self.revealOffset
                    }
                } else if let rightRevealView = self.rightRevealView {
                    let revealSize = rightRevealView.bounds.size
                    let location = recognizer.location(in: self)
                    if location.x > size.width - revealSize.width {
                        recognizer.becomeCancelled()
                    } else {
                        self.initialRevealOffset = self.revealOffset
                    }
                } else {
                    if self.revealOptions.left.isEmpty && self.revealOptions.right.isEmpty {
                        recognizer.becomeCancelled()
                    }
                    self.initialRevealOffset = self.revealOffset
                }
            case .changed:
                var translation = recognizer.translation(in: self)
                translation.x += self.initialRevealOffset
                if self.revealOptions.left.isEmpty {
                    translation.x = min(0.0, translation.x)
                }
                if self.leftRevealView == nil && CGFloat(0.0).isLess(than: translation.x) {
                    self.setupAndAddLeftRevealNode()
                    self.revealOptionsInteractivelyOpened?()
                } else if self.rightRevealView == nil && translation.x.isLess(than: 0.0) {
                    self.setupAndAddRightRevealNode()
                    self.revealOptionsInteractivelyOpened?()
                }
                self.updateRevealOffsetInternal(offset: translation.x, transition: .immediate)
                if self.leftRevealView == nil && self.rightRevealView == nil {
                    self.revealOptionsInteractivelyClosed?()
                }
            case .ended, .cancelled:
                guard let recognizer = self.recognizer else {
                    break
                }
                
                if let leftRevealView = self.leftRevealView {
                    let velocity = recognizer.velocity(in: self)
                    let revealSize = leftRevealView.bounds.size
                    var reveal = false
                    if abs(velocity.x) < 100.0 {
                        if self.initialRevealOffset.isZero && self.revealOffset > 0.0 {
                            reveal = true
                        } else if self.revealOffset > revealSize.width {
                            reveal = true
                        } else {
                            reveal = false
                        }
                    } else {
                        if velocity.x > 0.0 {
                            reveal = true
                        } else {
                            reveal = false
                        }
                    }
                    
                    var selectedOption: Option?
                    if reveal && leftRevealView.isDisplayingExtendedAction() {
                        reveal = false
                        selectedOption = self.revealOptions.left.first
                    } else {
                        self.updateRevealOffsetInternal(offset: reveal ? revealSize.width : 0.0, transition: .spring(duration: 0.3))
                    }
            
                    if let selectedOption = selectedOption {
                        self.revealOptionSelected?(selectedOption, true)
                    } else {
                        if !reveal {
                            self.revealOptionsInteractivelyClosed?()
                        }
                    }
                } else if let rightRevealView = self.rightRevealView {
                    let velocity = recognizer.velocity(in: self)
                    let revealSize = rightRevealView.bounds.size
                    var reveal = false
                    if abs(velocity.x) < 100.0 {
                        if self.initialRevealOffset.isZero && self.revealOffset < 0.0 {
                            reveal = true
                        } else if self.revealOffset < -revealSize.width {
                            reveal = true
                        } else {
                            reveal = false
                        }
                    } else {
                        if velocity.x < 0.0 {
                            reveal = true
                        } else {
                            reveal = false
                        }
                    }
                    
                    var selectedOption: Option?
                    if reveal && rightRevealView.isDisplayingExtendedAction() {
                        reveal = false
                        selectedOption = self.revealOptions.right.last
                    } else {
                        self.updateRevealOffsetInternal(offset: reveal ? -revealSize.width : 0.0, transition: .spring(duration: 0.3))
                    }
                    
                    if let selectedOption = selectedOption {
                        self.revealOptionSelected?(selectedOption, true)
                    } else {
                        if !reveal {
                            self.revealOptionsInteractivelyClosed?()
                        }
                    }
                }
            default:
                break
        }
    }
    
    private func setupAndAddLeftRevealNode() {
        if !self.revealOptions.left.isEmpty {
            let revealView = OptionsView(optionSelected: { [weak self] option in
                self?.revealOptionSelected?(option, false)
            }, tapticAction: { [weak self] in
                self?.hapticImpact()
            })
            revealView.setOptions(self.revealOptions.left, isLeft: true)
            self.leftRevealView = revealView
            
            if let (size, leftInset, _) = self.validLayout {
                var revealSize = revealView.calculateSize(CGSize(width: CGFloat.greatestFiniteMagnitude, height: size.height))
                revealSize.width += leftInset
                
                revealView.frame = CGRect(origin: CGPoint(x: min(self.revealOffset - revealSize.width, 0.0), y: 0.0), size: revealSize)
                revealView.updateRevealOffset(offset: 0.0, sideInset: leftInset, transition: .immediate)
            }
            
            self.controlsContainer.addSubview(revealView)
        }
    }
    
    private func setupAndAddRightRevealNode() {
        if !self.revealOptions.right.isEmpty {
            let revealView = OptionsView(optionSelected: { [weak self] option in
                self?.revealOptionSelected?(option, false)
            }, tapticAction: { [weak self] in
                self?.hapticImpact()
            })
            revealView.setOptions(self.revealOptions.right, isLeft: false)
            self.rightRevealView = revealView
            
            if let (size, _, rightInset) = self.validLayout {
                var revealSize = revealView.calculateSize(CGSize(width: CGFloat.greatestFiniteMagnitude, height: size.height))
                revealSize.width += rightInset
                
                revealView.frame = CGRect(origin: CGPoint(x: size.width + max(self.revealOffset, -revealSize.width), y: 0.0), size: revealSize)
                revealView.updateRevealOffset(offset: 0.0, sideInset: -rightInset, transition: .immediate)
            }
            
            self.controlsContainer.addSubview(revealView)
        }
    }
    
    public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
        self.validLayout = (size, leftInset, rightInset)
        
        if let leftRevealView = self.leftRevealView {
            var revealSize = leftRevealView.calculateSize(CGSize(width: CGFloat.greatestFiniteMagnitude, height: size.height))
            revealSize.width += leftInset
            leftRevealView.frame = CGRect(origin: CGPoint(x: min(self.revealOffset - revealSize.width, 0.0), y: 0.0), size: revealSize)
        }
        
        if let rightRevealView = self.rightRevealView {
            var revealSize = rightRevealView.calculateSize(CGSize(width: CGFloat.greatestFiniteMagnitude, height: size.height))
            revealSize.width += rightInset
            rightRevealView.frame = CGRect(origin: CGPoint(x: size.width + max(self.revealOffset, -revealSize.width), y: 0.0), size: revealSize)
        }
    }
    
    open func updateRevealOffsetInternal(offset: CGFloat, transition: ComponentTransition, completion: (() -> Void)? = nil) {
        self.revealOffset = offset
        guard let (size, leftInset, rightInset) = self.validLayout else {
            return
        }
        
        var leftRevealCompleted = true
        var rightRevealCompleted = true
        let intermediateCompletion = {
            if leftRevealCompleted && rightRevealCompleted {
                completion?()
            }
        }
        
        if let leftRevealView = self.leftRevealView {
            leftRevealCompleted = false
            
            let revealSize = leftRevealView.bounds.size
            
            let revealFrame = CGRect(origin: CGPoint(x: min(self.revealOffset - revealSize.width, 0.0), y: 0.0), size: revealSize)
            let revealNodeOffset = -self.revealOffset
            leftRevealView.updateRevealOffset(offset: revealNodeOffset, sideInset: leftInset, transition: transition)
            
            if CGFloat(offset).isLessThanOrEqualTo(0.0) {
                self.leftRevealView = nil
                transition.setFrame(view: leftRevealView, frame: revealFrame, completion: { [weak leftRevealView] _ in
                    leftRevealView?.removeFromSuperview()
                    
                    leftRevealCompleted = true
                    intermediateCompletion()
                })
            } else {
                transition.setFrame(view: leftRevealView, frame: revealFrame, completion: { _ in
                    leftRevealCompleted = true
                    intermediateCompletion()
                })
            }
        }
        if let rightRevealView = self.rightRevealView {
            rightRevealCompleted = false
            
            let revealSize = rightRevealView.bounds.size
            
            let revealFrame = CGRect(origin: CGPoint(x: min(size.width, size.width + self.revealOffset), y: 0.0), size: revealSize)
            let revealNodeOffset = -self.revealOffset
            rightRevealView.updateRevealOffset(offset: revealNodeOffset, sideInset: -rightInset, transition: transition)
            
            if CGFloat(0.0).isLessThanOrEqualTo(offset) {
                self.rightRevealView = nil
                transition.setFrame(view: rightRevealView, frame: revealFrame, completion: { [weak rightRevealView] _ in
                    rightRevealView?.removeFromSuperview()
                    
                    rightRevealCompleted = true
                    intermediateCompletion()
                })
            } else {
                transition.setFrame(view: rightRevealView, frame: revealFrame, completion: { _ in
                    rightRevealCompleted = true
                    intermediateCompletion()
                })
            }
        }
        let allowAnyDirection = !self.revealOptions.left.isEmpty || !offset.isZero
        if allowAnyDirection != self.allowAnyDirection {
            self.allowAnyDirection = allowAnyDirection
            self.recognizer?.allowAnyDirection = allowAnyDirection
            self.disablesInteractiveTransitionGestureRecognizer = allowAnyDirection
        }
        
        self.updateRevealOffset?(offset, transition)
    }
    
    open func setRevealOptionsOpened(_ value: Bool, animated: Bool) {
        if value != !self.revealOffset.isZero {
            if !self.revealOffset.isZero {
                self.recognizer?.becomeCancelled()
            }
            let transition: ComponentTransition
            if animated {
                transition = .spring(duration: 0.3)
            } else {
                transition = .immediate
            }
            if value {
                if self.rightRevealView == nil {
                    self.setupAndAddRightRevealNode()
                    if let rightRevealView = self.rightRevealView, let validLayout = self.validLayout {
                        let revealSize = rightRevealView.calculateSize(CGSize(width: CGFloat.greatestFiniteMagnitude, height: validLayout.size.height))
                        self.updateRevealOffsetInternal(offset: -revealSize.width, transition: transition)
                    }
                }
            } else if !self.revealOffset.isZero {
                self.updateRevealOffsetInternal(offset: 0.0, transition: transition)
            }
        }
    }
    
    open func animateRevealOptionsFill(completion: (() -> Void)? = nil) {
        if let validLayout = self.validLayout {
            self.layer.allowsGroupOpacity = true
            self.updateRevealOffsetInternal(offset: -validLayout.0.width - 74.0, transition: .spring(duration: 0.3), completion: {
                self.layer.allowsGroupOpacity = false
                completion?()
            })
        }
    }
    
    open var preventsTouchesToOtherItems: Bool {
        return self.isDisplayingRevealedOptions
    }
    
    open func touchesToOtherItemsPrevented() {
        if self.isDisplayingRevealedOptions {
            self.setRevealOptionsOpened(false, animated: true)
        }
    }
    
    private func hapticImpact() {
        if self.hapticFeedback == nil {
            self.hapticFeedback = HapticFeedback()
        }
        self.hapticFeedback?.impact(.medium)
    }
}