//
//  NavigationController.swift
//  MVMCoreUI
//
//  Created by Scott Pfeil on 10/24/19.
//  Copyright © 2019 Verizon Wireless. All rights reserved.
//

import UIKit
import MVMCore
import Combine

@objcMembers open class NavigationController: UINavigationController, MVMCoreViewManagerViewControllerProtocol {
    public weak var manager: (UIViewController & MVMCoreViewManagerProtocol)?
    private var cancellables: Set<AnyCancellable> = []

    /// Getter for the main navigation controller
    public static func navigationController() -> Self? {
        return MVMCoreActionUtility.initializerClassCheck(MVMCoreUISession.sharedGlobal()?.navigationController, classToVerify: self) as? Self
    }
    
    /// Sets up the application with a navigation controller
    public static func setupNavigationController() -> Self? {
        let navigationController = self.init()
        MVMCoreUISession.sharedGlobal()?.navigationController = navigationController
        NavigationHandler.shared().viewControllerToPresentOn = navigationController
        NavigationHandler.shared().navigationController = navigationController
        navigationController.subscribe()
        navigationController.setNavigationBarUI(with: NavigationItemModel())
        return navigationController
    }
    
    /// Sets up the application with a navigation controller as the main container.
    public static func setupNavigationControllerAsMainController() -> Self? {
        guard let navigationController = setupNavigationController() else { return nil }
        MVMCoreUISession.sharedGlobal()?.setup(asStandardLoadViewDelegate: navigationController)
        MVMCoreObject.sharedInstance()?.viewControllerManager = navigationController
        return navigationController
    }
    
    /** Subscribes for events.
     Updates the navigation item of the new view controller when one is pushed.
     Based on ``NavigationItemModelProtocol`` of  ``PageProtocol/pageModel``. Traverses the manager for the view controller if necessary.
    */
    @MainActor
    public func subscribe() {
        NavigationHandler.shared().onNavigation.sink { [weak self] (event, operation) in
            guard let self = self,
                  self == operation.navigationType.getNavigationController(),
                  let viewController = NavigationHandler.shared().getViewControllers(for: self).last,
                  let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return }
            switch event {
            case .willNavigate:
                if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) {
                    MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller)
                }
                if let model = getNavigationModel(from: newViewController) {
                    self.setNavigationItem(with: model, for: viewController)
                    self.setNavigationBarUI(with: model)
                }
                self.manager?.willDisplay?(newViewController)
            case .didNavigate:
                self.manager?.displayedViewController?(newViewController)
                if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) {
                    controller.viewControllerReady?(inManager: self)
                }
            @unknown default: break
            }
        }.store(in: &cancellables)
    }
    
    /// Convenience function to return the navigation model of the view controller.
    public func getNavigationModel(from viewController: UIViewController) -> NavigationItemModelProtocol? {
        return (viewController as? PageProtocol)?.pageModel?.navigationBar
    }
    
    /// Verifies the controller is the currently displayed controller.
    public func isDisplayed(viewController: UIViewController) -> Bool {
        guard viewController == getViewController() else {
            return false
        }
        return true
    }
}

extension NavigationController: MVMCoreViewManagerProtocol {
    public func getAccessibilityElements() -> [Any]? {
        nil
    }
    
    public func getCurrentViewController() -> UIViewController? {
        guard let topViewController = topViewController else { return nil }
        return MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController)
    }
    
    public func containsPage(withPageType pageType: String?) -> Bool {
        for controller in viewControllers {
            if let manager = controller as? MVMCoreViewManagerProtocol,
               manager.containsPage(withPageType: pageType) {
                return true
            } else if let controller = controller as? MVMCoreViewControllerProtocol,
                      controller.pageType == pageType {
                return true
            }
        }
        return false
    }
    
    public func newDataReceived(in viewController: UIViewController) {
        if isDisplayed(viewController: viewController),
           let model = getNavigationModel(from: viewController) {
            updateNavigationView(with: model, for: viewController)
        }
        manager?.newDataReceived?(in: viewController)
    }
    
    public func willDisplay(_ viewController: UIViewController) {
        if isDisplayed(viewController: viewController),
           let model = getNavigationModel(from: viewController) {
            updateNavigationView(with: model, for: viewController)
        }
        manager?.willDisplay?(viewController)
    }
    
    /// Updates the navigation item/bar of the current view controller based on the passed in model and view controller.
    @MainActor
    private func updateNavigationView(with model: NavigationItemModelProtocol, for viewController: UIViewController) {
        guard let topViewController = NavigationHandler.shared().getViewControllers(for: self).last else { return }
        
        setNavigationItem(with: model, for: topViewController, coordinatingWith: viewController as? PageBehaviorHandlerProtocol)
        setNavigationBarUI(with: model)
        
        navigationBar.setNeedsLayout()
        navigationBar.layoutIfNeeded()
    }
    
    public func displayedViewController(_ viewController: UIViewController) {
        manager?.displayedViewController?(viewController)
    }
    
    private func go(to index: Int) async {
        guard index != viewControllers.count - 1 else { return }
        await NavigationHandler.shared().set(viewControllers: Array(viewControllers[0...index]), navigationController: self)
    }
    
    public func navigate(toViewControllerOfPageType pageType: String, controllerType: AnyClass?) async -> UIViewController? {
        for (index, controller) in viewControllers.enumerated() {
            if let manager = controller as? MVMCoreViewManagerProtocol,
               let viewController = await manager.navigate(toViewControllerOfPageType: pageType, controllerType: controllerType) {
                await go(to: index)
                return viewController
            } else if let controller = controller as? MVMCoreViewControllerProtocol & UIViewController,
                      controller.pageType == pageType {
                guard let controllerType = controllerType else {
                    await go(to: index)
                    return controller
                }
                guard (type(of: controller) == controllerType) else { continue }
                await go(to: index)
                return controller
            }
        }
        return nil
    }
}

extension UIColor {
    func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
        return UIGraphicsImageRenderer(size: size).image { rendererContext in
            self.setFill()
            rendererContext.fill(CGRect(origin: .zero, size: size))
        }
    }
}
