//
//  EnhancedNavigationViewController.swift
//  Tusker
//
//  Created by Shadowfacts on 2/19/20.
//  Copyright © 2020 Shadowfacts. All rights reserved.
//

import UIKit

class EnhancedNavigationViewController: UINavigationController {
    
    let useBrowserStyleNavigation = Preferences.shared.hasFeatureFlag(.iPadBrowserNavigation)
    
    var poppedViewControllers = [UIViewController]()
    var skipResetPoppedOnNextPush = false
    
    private var interactivePushTransition: InteractivePushTransition!
    
    override var viewControllers: [UIViewController] {
        didSet {
            poppedViewControllers = []
            if #available(iOS 16.0, *),
               useBrowserStyleNavigation {
                // TODO: this for loop might not be necessary
                for vc in viewControllers {
                    configureNavItem(vc.navigationItem)
                }
                updateTopNavItemState()
            }
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        self.interactivePushTransition = InteractivePushTransition(navigationController: self)
        
        if #available(iOS 16.0, *),
           useBrowserStyleNavigation,
           let topViewController {
            configureNavItem(topViewController.navigationItem)
            updateTopNavItemState()
        }
    }
    
    override func popViewController(animated: Bool) -> UIViewController? {
        let popped = performAfterAnimating(block: {
            super.popViewController(animated: animated)
        }, after: {
            if #available(iOS 16.0, *) {
                self.updateTopNavItemState()
            }
        }, animated: animated)
        if let popped {
            poppedViewControllers.insert(popped, at: 0)
        }
        return popped
    }
    
    override func popToRootViewController(animated: Bool) -> [UIViewController]? {
        let popped = performAfterAnimating(block: {
            super.popToRootViewController(animated: animated)
        }, after: {
            if #available(iOS 16.0, *) {
                self.updateTopNavItemState()
            }
        }, animated: animated)
        if let popped {
            poppedViewControllers = popped
        }
        return popped
    }
    
    override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? {
        let popped = performAfterAnimating(block: {
            super.popToViewController(viewController, animated: animated)
        }, after: {
            if #available(iOS 16.0, *) {
                self.updateTopNavItemState()
            }
        }, animated: animated)
        if let popped {
            poppedViewControllers.insert(contentsOf: popped, at: 0)
        }
        return popped
    }
    
    override func pushViewController(_ viewController: UIViewController, animated: Bool) {
        if skipResetPoppedOnNextPush {
            skipResetPoppedOnNextPush = false
        } else {
            self.poppedViewControllers = []
        }
        
        if #available(iOS 16.0, *) {
            configureNavItem(viewController.navigationItem)
        }
        
        super.pushViewController(viewController, animated: animated)
        
        if #available(iOS 16.0, *) {
            updateTopNavItemState()
        }
    }
    
    func pushPoppedViewController() {
        guard !poppedViewControllers.isEmpty else {
            return
        }
        skipResetPoppedOnNextPush = true
        pushViewController(poppedViewControllers.removeFirst(), animated: true)
    }
    
    func pushToPoppedViewController(_ target: UIViewController) {
        guard poppedViewControllers.contains(target) else {
            return
        }
        var toInsert: [UIViewController] = []
        while true {
            let vc = poppedViewControllers.removeFirst()
            if vc == target {
                break
            } else {
                toInsert.append(vc)
            }
        }
        // match the system behavior when popping multiple by animated-ly pushing the final destination one,
        // and then inserting the intermediary ones before it, as if they'd all been pushed together
        performAfterAnimating(block: {
            pushViewController(target, animated: true)
        }, after: {
            self.viewControllers.insert(contentsOf: toInsert, at: self.viewControllers.count - 1)
            if #available(iOS 16.0, *) {
                self.updateTopNavItemState()
            }
        }, animated: true)
    }
    
    func onWillShow() {
        self.transitionCoordinator?.notifyWhenInteractionChanges({ (context) in
            if context.isCancelled {
                if self.interactivePushTransition.interactive {
                    // when an interactive push gesture is cancelled, make sure to adding the VC that was being pushed back onto the popped stack so it doesn't disappear
                    self.poppedViewControllers.insert(self.interactivePushTransition.pushingViewController!, at: 0)
                } else if self.interactivePopGestureRecognizer?.state == .ended {
                    // when an interactive pop gesture is cancelled (i.e. the user lifts their finger before it triggers),
                    // the popViewController(animated:) method has already been called so the VC has already been added to the popped stack
                    // so we make sure to remove it, otherwise there could be duplicate VCs on the navigation stasck
                    if !self.poppedViewControllers.isEmpty {
                        self.poppedViewControllers.remove(at: 0)
                    }
                }
            }
        })
    }
    
    @available(iOS 16.0, *)
    private func configureNavItem(_ navItem: UINavigationItem) {
        guard useBrowserStyleNavigation,
              UIDevice.current.userInterfaceIdiom != .phone else {
            return
        }
        navItem.style = .browser
        navItem.hidesBackButton = true
        if let titleView = navItem.titleView,
           titleView.tag != ViewTags.navEmptyTitleView {
            // blergh, i don't like changing this out from under some other view controller
            // we use an empty view because otherwise the title label displays in addition to the new title view bar button item
            navItem.titleView = UIView()
            navItem.titleView?.tag = ViewTags.navEmptyTitleView
            // TODO: centerItemGroups don't animate out during nav transitions, the just (dis)appear abruptly
            navItem.centerItemGroups = [
                .fixedGroup(items: [UIBarButtonItem(customView: titleView)])
            ]
        }
        let back = UIBarButtonItem(image: UIImage(systemName: "chevron.backward"), style: .plain, target: self, action: #selector(goBack))
        back.tag = ViewTags.navBackBarButton
        back.menu = UIMenu(children: [
            UIDeferredMenuElement({ [unowned self] completion in
                completion(self.viewControllers.dropLast(1).reversed().map { vc in
                    UIAction(title: vc.navigationItem.title ?? "Back") { [weak self] _ in
                        _ = self?.popToViewController(vc, animated: true)
                    }
                })
            })
        ])
        let forward = UIBarButtonItem(image: UIImage(systemName: "chevron.forward"), style: .plain, target: self, action: #selector(goForward))
        forward.tag = ViewTags.navForwardBarButton
        forward.menu = UIMenu(children: [
            UIDeferredMenuElement.uncached({ [unowned self] completion in
                completion(poppedViewControllers.map { vc in
                    UIAction(title: vc.navigationItem.title ?? "Forward") { [weak self] _ in
                        self?.pushToPoppedViewController(vc)
                    }
                })
            })
        ])
        navItem.leadingItemGroups = [
            .fixedGroup(items: [
                back,
                forward,
            ])
        ]
    }
    
    @available(iOS 16.0, *)
    private func updateTopNavItemState() {
        guard useBrowserStyleNavigation,
              UIDevice.current.userInterfaceIdiom != .phone,
              let vc = topViewController,
              let group = vc.navigationItem.leadingItemGroups.first,
              group.barButtonItems.count == 2,
              group.barButtonItems[0].tag == ViewTags.navBackBarButton,
              group.barButtonItems[1].tag == ViewTags.navForwardBarButton else {
            return
        }
        group.barButtonItems[0].isEnabled = viewControllers.count > 1
        group.barButtonItems[1].isEnabled = !poppedViewControllers.isEmpty
    }
    
    private func performAfterAnimating<R>(block: () -> R, after: @escaping () -> Void, animated: Bool) -> R {
        if animated {
            CATransaction.begin()
            let result = block()
            CATransaction.setCompletionBlock {
                after()
            }
            CATransaction.commit()
            return result
        } else {
            let result = block()
            after()
            return result
        }
    }
    
    @objc private func goBack() {
        _ = popViewController(animated: true)
    }
    
    @objc private func goForward() {
        pushPoppedViewController()
    }

}

extension EnhancedNavigationViewController: BackgroundableViewController {
    func sceneDidEnterBackground() {
        if let topVC = topViewController as? BackgroundableViewController {
            topVC.sceneDidEnterBackground()
        }
    }
}

extension EnhancedNavigationViewController: StatusBarTappableViewController {
    func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
        if let topVC = topViewController as? StatusBarTappableViewController {
            return topVC.handleStatusBarTapped(xPosition: xPosition)
        }
        return .continue
    }
}
