import Foundation
import UIKit
import Display
import ComponentFlow
import AppBundle
import BundleIconComponent

private final class NavigationContainer: UIView, UIGestureRecognizerDelegate {
    var requestUpdate: ((ComponentTransition) -> Void)?
    var requestPop: (() -> Void)?
    var transitionFraction: CGFloat = 0.0
    
    private var panRecognizer: InteractiveTransitionGestureRecognizer?
    
    var isNavigationEnabled: Bool = false {
        didSet {
            self.panRecognizer?.isEnabled = self.isNavigationEnabled
        }
    }
    
    init() {
        super.init(frame: .zero)
                
        let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
            guard let strongSelf = self else {
                return []
            }
            let _ = strongSelf
            return [.right]
        })
        panRecognizer.delegate = self
        self.addGestureRecognizer(panRecognizer)
        self.panRecognizer = panRecognizer
    }
    
    required public init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return false
    }
    
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
            return false
        }
        if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
            return true
        }
        return false
    }
    
    @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
        switch recognizer.state {
        case .began:
            self.transitionFraction = 0.0
        case .changed:
            let distanceFactor: CGFloat = recognizer.translation(in: self).x / self.bounds.width
            let transitionFraction = max(0.0, min(1.0, distanceFactor))
            if self.transitionFraction != transitionFraction {
                self.transitionFraction = transitionFraction
                self.requestUpdate?(.immediate)
            }
        case .ended, .cancelled:
            let distanceFactor: CGFloat = recognizer.translation(in: self).x / self.bounds.width
            let transitionFraction = max(0.0, min(1.0, distanceFactor))
            if transitionFraction > 0.2 {
                self.transitionFraction = 0.0
                self.requestPop?()
            } else {
                self.transitionFraction = 0.0
                self.requestUpdate?(.spring(duration: 0.45))
            }
        default:
            break
        }
    }
}

public final class NavigationStackComponent<ChildEnvironment: Equatable>: Component {
    public let items: [AnyComponentWithIdentity<ChildEnvironment>]
    public let requestPop: () -> Void
    
    public init(
        items: [AnyComponentWithIdentity<ChildEnvironment>],
        requestPop: @escaping () -> Void
    ) {
        self.items = items
        self.requestPop = requestPop
    }
    
    public static func ==(lhs: NavigationStackComponent, rhs: NavigationStackComponent) -> Bool {
        if lhs.items != rhs.items {
            return false
        }
        return true
    }
        
    private final class ItemView: UIView {
        let contents = ComponentView<ChildEnvironment>()
        let dimView = UIView()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            
            self.dimView.alpha = 0.0
            self.dimView.backgroundColor = UIColor.black.withAlphaComponent(0.2)
            self.dimView.isUserInteractionEnabled = false
            self.addSubview(self.dimView)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    
    private struct ReadyItem {
        var index: Int
        var itemId: AnyHashable
        var itemView: ItemView
        var itemTransition: ComponentTransition
        var itemSize: CGSize
        
        init(index: Int, itemId: AnyHashable, itemView: ItemView, itemTransition: ComponentTransition, itemSize: CGSize) {
            self.index = index
            self.itemId = itemId
            self.itemView = itemView
            self.itemTransition = itemTransition
            self.itemSize = itemSize
        }
    }
    
    public final class View: UIView {
        private var itemViews: [AnyHashable: ItemView] = [:]
        private let navigationContainer = NavigationContainer()
        
        private var component: NavigationStackComponent?
        private var state: EmptyComponentState?
        
        public override init(frame: CGRect) {
            super.init(frame: CGRect())
            
            self.addSubview(self.navigationContainer)
            
            self.navigationContainer.requestUpdate = { [weak self] transition in
                guard let self else {
                    return
                }
                self.state?.updated(transition: transition)
            }
            
            self.navigationContainer.requestPop = { [weak self] in
                guard let self else {
                    return
                }
                self.component?.requestPop()
            }
        }
        
        required public init?(coder: NSCoder) {
            preconditionFailure()
        }
                
        func update(component: NavigationStackComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ChildEnvironment>, transition: ComponentTransition) -> CGSize {
            self.component = component
            self.state = state
            
            let navigationTransitionFraction = self.navigationContainer.transitionFraction
            self.navigationContainer.isNavigationEnabled = component.items.count > 1
                                    
            var validItemIds: [AnyHashable] = []
        
            var readyItems: [ReadyItem] = []
            for i in 0 ..< component.items.count {
                let item = component.items[i]
                let itemId = item.id
                validItemIds.append(itemId)
                
                let itemView: ItemView
                var itemTransition = transition
                if let current = self.itemViews[itemId] {
                    itemView = current
                } else {
                    itemTransition = itemTransition.withAnimation(.none)
                    itemView = ItemView()
                    self.itemViews[itemId] = itemView
                    itemView.contents.parentState = state
                }
                
                let itemSize = itemView.contents.update(
                    transition: itemTransition,
                    component: item.component,
                    environment: { environment[ChildEnvironment.self] },
                    containerSize: CGSize(width: availableSize.width, height: availableSize.height)
                )
                
                readyItems.append(ReadyItem(
                    index: i,
                    itemId: itemId,
                    itemView: itemView,
                    itemTransition: itemTransition,
                    itemSize: itemSize
                ))
            }
            
            let sortedItems = readyItems.sorted(by: { $0.index < $1.index })
            for readyItem in sortedItems {
                let transitionFraction: CGFloat
                let alphaTransitionFraction: CGFloat
                if readyItem.index == readyItems.count - 1 {
                    transitionFraction = navigationTransitionFraction
                    alphaTransitionFraction = 1.0
                } else if readyItem.index == readyItems.count - 2 {
                    transitionFraction = navigationTransitionFraction - 1.0
                    alphaTransitionFraction = navigationTransitionFraction
                } else {
                    transitionFraction = 0.0
                    alphaTransitionFraction = 0.0
                }
                
                let transitionOffset: CGFloat
                if readyItem.index == readyItems.count - 1 {
                    transitionOffset = readyItem.itemSize.width * transitionFraction
                } else {
                    transitionOffset = readyItem.itemSize.width / 3.0 * transitionFraction
                }
                
                let itemFrame = CGRect(origin: CGPoint(x: transitionOffset, y: 0.0), size: readyItem.itemSize)
                
                let itemBounds = CGRect(origin: .zero, size: itemFrame.size)
                if let itemComponentView = readyItem.itemView.contents.view {
                    var isAdded = false
                    if itemComponentView.superview == nil {
                        isAdded = true
                        
                        readyItem.itemView.insertSubview(itemComponentView, at: 0)
                        self.navigationContainer.addSubview(readyItem.itemView)
                    }
                    readyItem.itemTransition.setFrame(view: readyItem.itemView, frame: itemFrame)
                    readyItem.itemTransition.setFrame(view: itemComponentView, frame: itemBounds)
                    readyItem.itemTransition.setFrame(view: readyItem.itemView.dimView, frame: CGRect(origin: .zero, size: availableSize))
                    readyItem.itemTransition.setAlpha(view: readyItem.itemView.dimView, alpha: 1.0 - alphaTransitionFraction)
                    
                    if readyItem.index > 0 && isAdded {
                        transition.animatePosition(view: itemComponentView, from: CGPoint(x: itemFrame.width, y: 0.0), to: .zero, additive: true, completion: nil)
                    }
                }
            }
            
            let lastHeight = sortedItems.last?.itemSize.height ?? 0.0
            let previousHeight: CGFloat
            if sortedItems.count > 1 {
                previousHeight = sortedItems[sortedItems.count - 2].itemSize.height
            } else {
                previousHeight = lastHeight
            }
            let contentHeight = lastHeight * (1.0 - navigationTransitionFraction) + previousHeight * navigationTransitionFraction
            
            var removedItemIds: [AnyHashable] = []
            for (id, _) in self.itemViews {
                if !validItemIds.contains(id) {
                    removedItemIds.append(id)
                }
            }
            for id in removedItemIds {
                guard let itemView = self.itemViews[id] else {
                    continue
                }
                if let itemComponeentView = itemView.contents.view {
                    var position = itemComponeentView.center
                    position.x += itemComponeentView.bounds.width
                    transition.setPosition(view: itemComponeentView, position: position, completion: { _ in
                        itemView.removeFromSuperview()
                        self.itemViews.removeValue(forKey: id)
                    })
                } else {
                    itemView.removeFromSuperview()
                    self.itemViews.removeValue(forKey: id)
                }
            }
            
            let contentSize = CGSize(width: availableSize.width, height: contentHeight)
            self.navigationContainer.frame = CGRect(origin: .zero, size: contentSize)
            
            return contentSize
        }
    }
    
    public func makeView() -> View {
        return View(frame: CGRect())
    }
    
    public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ChildEnvironment>, transition: ComponentTransition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
    }
}