//
//  SplitNavigationController.swift
//  Tusker
//
//  Created by Shadowfacts on 7/1/22.
//  Copyright © 2022 Shadowfacts. All rights reserved.
//

import UIKit

class SplitNavigationController: UIViewController {
    
    private let rootNav = SplitRootNavigationController()
    private let secondaryNav = SplitSecondaryNavigationController()
    private let separatorView = UIView()
    
    private var constraints: [NSLayoutConstraint] = []
    
    var viewControllers: [UIViewController] {
        get {
            return rootNav.viewControllers + secondaryNav.viewControllers
        }
        set {
            if newValue.isEmpty {
                rootNav.viewControllers = []
                secondaryNav.viewControllers = []
            } else if canShowSecondaryNav {
                var newValue = newValue
                rootNav.viewControllers = [newValue.removeFirst()]
                secondaryNav.viewControllers = newValue
            } else {
                rootNav.viewControllers = newValue
                secondaryNav.viewControllers = []
            }
            updateSecondaryNavVisibility()
        }
    }
    
    /// This property is only valid after the view has been laid out.
    private var canShowSecondaryNav: Bool {
        // minimum of 360pt for each column
        // this allows split navigation on all ipads in portrait w/ sidebar hidden and in landscape (regardless of sidebar)
        (viewIfLoaded?.bounds.width ?? 0) >= 720
    }
    
    init(rootViewController: UIViewController? = nil) {
        super.init(nibName: nil, bundle: nil)
        
        rootNav.showImpl = { [unowned self] vc, sender in
            if self.canShowSecondaryNav {
                self.setSecondaryViewControllers([vc], animated: true)
                SplitNavigationController.clearSelectedRow(sender: sender)
            } else {
                self.rootNav.pushViewController(vc, animated: true)
            }
        }
        secondaryNav.owner = self
        secondaryNav.closeSecondaryImpl = { [unowned self] in
            self.popToRootViewController(animated: true)
        }
        
        if let rootViewController {
            rootNav.viewControllers = [rootViewController]
        }
        
        // add the child VCs here, rather than in viewDidLoad, because this VC is added to the UISplitViewController,
        // it needs a UINavigationController to be this VC's first child, otherwise it will embed this VC inside
        // yet another UINavigationController, which can then cause a crash when we try to embed a nav controller inside
        // of ourself (because nested nav controllers are forbidden)
        // and because of that, the view needs to be added here, in between the addChild/didMove(toParent:) calls
        // and so the view needs to be loaded immediately
        loadViewIfNeeded()
        
        addChild(rootNav)
        rootNav.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(rootNav.view)
        rootNav.didMove(toParent: self)
        addChild(secondaryNav)
        secondaryNav.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(secondaryNav.view)
        secondaryNav.didMove(toParent: self)
        
        separatorView.backgroundColor = .separator
        separatorView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(separatorView)
        
        NSLayoutConstraint.activate([
            rootNav.view.topAnchor.constraint(equalTo: view.topAnchor),
            rootNav.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            rootNav.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),

            separatorView.topAnchor.constraint(equalTo: view.topAnchor),
            separatorView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            separatorView.leadingAnchor.constraint(equalTo: rootNav.view.trailingAnchor),
            separatorView.widthAnchor.constraint(equalToConstant: 0.5),

            secondaryNav.view.topAnchor.constraint(equalTo: view.topAnchor),
            secondaryNav.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            secondaryNav.view.leadingAnchor.constraint(equalTo: separatorView.trailingAnchor),
        ])
        
        updateSecondaryNavVisibility()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    static func clearSelectedRow(sender: Any?) {
        // the split nav shouldn't really be reaching down into the inner VCs like this,
        // but I can't think of a cleaner way
        if let tableVC = sender as? UITableViewController,
           let selectedIndexPath = tableVC.tableView.indexPathForSelectedRow {
            tableVC.tableView.deselectRow(at: selectedIndexPath, animated: true)
        } else if let sender = sender as? UIViewController,
                  let collectionView = (sender as? CollectionViewController)?.collectionView ?? sender.view as? UICollectionView {
            // the collection view's animation speed is weirdly fast, so we do it slower
            UIView.animate(withDuration: 0.5, delay: 0) {
                collectionView.indexPathsForSelectedItems?.forEach { collectionView.deselectItem(at: $0, animated: false) }
            }
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // NOTE: as explained by the large comment above, viewDidLoad is called during initialization, and so things may not be fully setup when it is
    }
    
    override func show(_ vc: UIViewController, sender: Any?) {
        if !canShowSecondaryNav {
            rootNav.pushViewController(vc, animated: true)
        } else if rootNav.viewControllers.isEmpty {
            rootNav.pushViewController(vc, animated: false)
        } else {
            secondaryNav.pushViewController(vc, animated: true)
        }
        updateSecondaryNavVisibility()
    }
    
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        if !isLayingOutForAnimation {
            updateSecondaryNavVisibility()
        }
    }
    
    func pushViewController(_ vc: UIViewController, animated: Bool) {
        if !canShowSecondaryNav {
            rootNav.pushViewController(vc, animated: animated)
        } else if rootNav.viewControllers.isEmpty {
            rootNav.pushViewController(vc, animated: false)
        } else {
            secondaryNav.pushViewController(vc, animated: animated)
        }
        updateSecondaryNavVisibility()
    }
    
    private func updateSecondaryNavVisibility() {
        guard isViewLoaded else {
            return
        }
        
        if canShowSecondaryNav {
            if rootNav.viewControllers.count > 1 {
                var vcs = rootNav.viewControllers
                let root = vcs.removeFirst()
                rootNav.viewControllers = [root]
                // this shouldn't be necessary since the vcs are removed from their parent vc by setting rootNav.viewControllers
                // but it doesn't remove the views from their superview (until the next runloop iteration?)
                // so we need to do that ourselves before we can set them on the secondary nav (otherwise it raises an exception)
                vcs.forEach { $0.removeViewAndController() }
                secondaryNav.viewControllers = vcs
            }
        } else {
            if !secondaryNav.viewControllers.isEmpty {
                let firstSecondary = secondaryNav.viewControllers.first!
                // remove the left bar button item so that the builtin Back item shows
                if firstSecondary.navigationItem.leftBarButtonItem?.tag == ViewTags.splitNavCloseSecondaryButton {
                    firstSecondary.navigationItem.leftBarButtonItem = nil
                }
                rootNav.viewControllers.append(contentsOf: secondaryNav.viewControllers)
                secondaryNav.viewControllers = []
            }
        }
        
        setSecondaryVisible(canShowSecondaryNav && !secondaryNav.viewControllers.isEmpty)
    }
    
    private func setSecondaryVisible(_ visible: Bool) {
        guard isViewLoaded else {
            return
        }
        
        NSLayoutConstraint.deactivate(constraints)
        if visible {
            constraints = [
                rootNav.view.trailingAnchor.constraint(equalTo: view.centerXAnchor),
                secondaryNav.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            ]
        } else {
            constraints = [
                rootNav.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                secondaryNav.view.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),
            ]
        }
        NSLayoutConstraint.activate(constraints)
    }
    
    private func setSecondaryViewControllers(_ vcs: [UIViewController], animated: Bool) {
        if animated {
            if vcs.isEmpty {
                popToRootViewController(animated: true)
            } else {
                let wasVisible = !secondaryNav.viewControllers.isEmpty
                secondaryNav.viewControllers = vcs
                secondaryNav.view.frame = CGRect(x: view.bounds.width, y: 0, width: view.bounds.width / 2, height: view.bounds.height)
                secondaryNav.view.layoutIfNeeded()
                if !wasVisible {
                    let animator = UIViewPropertyAnimator(duration: 0.35, curve: .easeInOut) {
                        self.updateSecondaryNavVisibility()
                        self.view.layoutIfNeeded()
                    }
                    animator.startAnimation()
                }
            }
        } else {
            secondaryNav.viewControllers = vcs
            updateSecondaryNavVisibility()
        }
    }
    
    private var isLayingOutForAnimation = false
    
    @discardableResult
    func popToRootViewController(animated: Bool) -> [UIViewController]? {
        let vcs = secondaryNav.viewControllers
        if animated {
            // we don't update secondaryNav.viewControllers until after the animation is completed
            // otherwise the secondary nav's contents disappear immediately, rather than sliding off-screen
            let animator = UIViewPropertyAnimator(duration: 0.35, curve: .easeInOut) {
                self.isLayingOutForAnimation = true
                self.setSecondaryVisible(false)
                self.view.layoutIfNeeded()
            }
            animator.addCompletion { _ in
                self.secondaryNav.viewControllers = []
                self.isLayingOutForAnimation = false
//                self.updateSecondaryNavVisibility()
            }
            animator.startAnimation()
        } else {
            self.secondaryNav.viewControllers = []
            self.updateSecondaryNavVisibility()
        }
        return vcs
    }
}

extension SplitNavigationController: StatusBarTappableViewController {
    func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
        let vcs = viewControllers
        if !canShowSecondaryNav || vcs.count < 2 {
            return (vcs.last! as? StatusBarTappableViewController)?.handleStatusBarTapped(xPosition: xPosition) ?? .continue
        } else {
            let positionInRoot = rootNav.view.convert(CGPoint(x: xPosition, y: 0), from: view)
            let positionInSecondary = secondaryNav.view.convert(CGPoint(x: xPosition, y: 0), from: view)
            if rootNav.view.bounds.contains(positionInRoot) {
                return (rootNav.topViewController as? StatusBarTappableViewController)?.handleStatusBarTapped(xPosition: positionInRoot.x) ?? .continue
            } else if secondaryNav.view.bounds.contains(positionInSecondary) {
                return (secondaryNav.topViewController as? StatusBarTappableViewController)?.handleStatusBarTapped(xPosition: positionInRoot.x) ?? .continue
            }
        }
        return .continue
    }
}

private class SplitRootNavigationController: UINavigationController {
    fileprivate var showImpl: ((UIViewController, Any?) -> Void)!
    
    override func show(_ vc: UIViewController, sender: Any?) {
        showImpl(vc, sender)
    }
}

private class SplitSecondaryNavigationController: EnhancedNavigationViewController {
    fileprivate unowned var owner: SplitNavigationController!
    fileprivate var closeSecondaryImpl: (() -> Void)!
    
    override var viewControllers: [UIViewController] {
        didSet {
            for vc in oldValue where vc.parent !== self {
                removeSecondarySplitCloseButton(for: vc)
            }
            if let first = viewControllers.first {
                configureSecondarySplitCloseButton(for: first)
            }
        }
    }
    
    override var next: UIResponder? {
        // ordinarily, the next responder in the chain would be the SplitNavigationController's view
        // but that would bypass the VC in the root nav, so we reroute the repsonder chain to include it
        // first seems to be nil when using the view debugger for some reason, so in that case, defer to super
        if let root = owner.viewControllers.first {
            return root.innermostResponder() ?? super.next
        } else {
            return super.next
        }
    }
    
    override func pushViewController(_ viewController: UIViewController, animated: Bool) {
        if viewControllers.isEmpty {
            configureSecondarySplitCloseButton(for: viewController)
        }
        
        super.pushViewController(viewController, animated: animated)
    }
    
    private func configureSecondarySplitCloseButton(for viewController: UIViewController) {
        guard viewController.navigationItem.leftBarButtonItem?.tag != ViewTags.splitNavCloseSecondaryButton else {
            return
        }
        let item = UIBarButtonItem(title: "Close", style: .done, target: self, action: #selector(closeSecondary))
        item.tag = ViewTags.splitNavCloseSecondaryButton
        viewController.navigationItem.leftBarButtonItem = item
    }
    
    private func removeSecondarySplitCloseButton(for viewController: UIViewController) {
        if viewController.navigationItem.leftBarButtonItem?.tag == ViewTags.splitNavCloseSecondaryButton {
            viewController.navigationItem.leftBarButtonItem = nil
        }
    }
    
    @objc private func closeSecondary() {
        closeSecondaryImpl()
    }
    
}

protocol NestedResponderProvider {
    var innerResponder: UIResponder? { get }
}

extension UIResponder {
    func innermostResponder() -> UIResponder? {
        if let nestedProvider = self as? NestedResponderProvider {
            return nestedProvider.innerResponder?.innermostResponder() ?? self
        } else {
            return self
        }
    }
}
