//
//  MVMCoreUISplitViewController+Extension.swift
//  MVMCoreUI
//
//  Created by Scott Pfeil on 6/18/20.
//  Copyright © 2020 Verizon Wireless. All rights reserved.
//

import Foundation
import UIKit
import Combine
import MVMCore

/// Allows overrides of the status bar color and style.
public protocol StatusBarUI {
    /// Returns the background color of the status bar view and the style of the status bar.
    func getStatusBarUI() -> (color: UIColor, style: UIStatusBarStyle)
}

// Navigation bar update functions
public extension MVMCoreUISplitViewController {
    
    /// Updates the state for various controls (top navigation controller item, tab, progress) for the controller.
    @MainActor
    func updateState(with viewController: UIViewController) {
        guard let navigationController = navigationController,
              navigationController.isDisplayed(viewController: viewController) else { return }
        updateNavigationBarFor(viewController: viewController)
        updateProgressBar(for: viewController)
        updateTabBar(for: viewController)
    }
    
    // MARK: - Progress Bar
    /** Updates the progress bar based on the page json for the view controller.
     Uses a string value between 0 and 100 from key progressPercent in the MVMCoreViewControllerProtocol.loadObject.pageJSON.
     */
    func updateProgressBar(for viewController: UIViewController) {
        guard let viewController = viewController as? MVMCoreViewControllerProtocol else { return }
        var progress: Float = 0.0
        if let progressString = viewController.loadObject??.pageJSON?.optionalStringForKey(KeyProgressPercent),
            let floatValue = Float(progressString) {
            progress = floatValue/100
        }
        setBottomProgressBarProgress(progress)
    }
    
    // MARK: - Tab Bar
    /** Updates the tab bar based on the page json for the view controller.
     For the index: checks the view controller's pageModel (``PageProtocol``) property ``TabPageModelProtocol/tabBarIndex``, else tabBarIndex in action map that led to this page, else the previous tab bar index of this page.
     For hidden: checks the view controller's pageModel (``PageProtocol``) property ``TabPageModelProtocol/tabBarHidden``, else tabBarHidden in action map that led to this page, else it is visibile.
     */
    @MainActor
    func updateTabBar(for viewController: UIViewController) {
        let mvmViewController = viewController as? MVMCoreViewControllerProtocol
        tabBar?.delegateObject = mvmViewController?.delegateObject?() as? MVMCoreUIDelegateObject
        
        let navigationIndex = (navigationController != nil ? NavigationHandler.shared().getViewControllers(for: navigationController!).count : 1) - 1
        
        // Set the highlighted index. In terms of priority, Page > Action > Previous.
        if let index = ((viewController as? PageProtocol)?.pageModel as? TabPageModelProtocol)?.tabBarIndex {
            tabBar?.highlightTab(at: index)
        } else if let index = mvmViewController?.loadObject??.requestParameters?.actionMap?["tabBarIndex"] as? Int {
            tabBar?.highlightTab(at: index)
        } else if navigationIndex < tabBarIndices.count,
                  let index = (tabBarIndices[navigationIndex] as? NSNumber)?.intValue {
            tabBar?.highlightTab(at: index)
        }
        
        // Store current tab index, so we can switch back when going back in hierarchy.
        if tabBarIndices.count > navigationIndex {
            tabBarIndices.removeObjects(in: NSRange(location: navigationIndex, length: tabBarIndices.count - navigationIndex))
        }
        if let currentIndex = tabBar?.currentTabIndex() {
            tabBarIndices.add(NSNumber(integerLiteral: currentIndex))
        } else {
            tabBarIndices.add(NSNull())
        }

        // Show/Hide. In terms of priority, Page > Action > Always Show.
        if let hidden = ((viewController as? PageProtocol)?.pageModel as? TabPageModelProtocol)?.tabBarHidden {
            updateTabBarShowing(!hidden)
        } else if let hidden = mvmViewController?.loadObject??.requestParameters?.actionMap?["tabBarHidden"] as? Bool {
            updateTabBarShowing(!hidden)
        } else {
            updateTabBarShowing(true)
        }
    }
    
    // MARK: - Navigation Bar
    /** Convenience function. Sets the navigation and split view properties for the view controller.
     Panel access is determined if view controller is a ``MVMCoreUIDetailViewProtocol``
     */
    @MainActor
    func setNavigationBar(for viewController: UIViewController, navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) {
        guard navigationController == self.navigationController,
              self.navigationController?.isDisplayed(viewController: viewController) == true else {
            /// Not the split view navigation controller, skip split functions.
            return
        }

        setLeftPanelIsAccessible((viewController as? MVMCoreUIDetailViewProtocol)?.isLeftPanelAccessible?() ?? false, for: viewController, updateNavigationButtons: false)
        setRightPanelIsAccessible((viewController as? MVMCoreUIDetailViewProtocol)?.isRightPanelAccessible?() ?? false, for: viewController, updateNavigationButtons: false)
        
        setLeftNavigationButtons(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController)
        setRightNavigationButtons(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController)
        
        setNavigationIconColor(navigationItemModel.tintColor.uiColor)
    }
    
    /** Sets the left navigation items for the top view controller based on the model and viewController.
     Panel access is determined if view controller is a ``MVMCoreUIDetailViewProtocol``
     */
    @MainActor
    func setLeftNavigationButtons(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol?, viewController: UIViewController) {
        let viewControllers = NavigationHandler.shared().getViewControllers(for: navigationController)
        guard let topViewController = viewControllers.last else { return }

        var leftItems: [UIBarButtonItem] = []
        let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject

        // Add back button first.
        if navigationItemModel?.hidesSystemBackButton == true {
            var showBackButton: Bool
            if let forceBackButton = navigationItemModel?.alwaysShowBackButton {
                showBackButton = forceBackButton
            } else {
                showBackButton = viewControllers.count > 1
            }
            if showBackButton {
                if let backButtonModel = navigationItemModel?.backButton {
                    leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil))
                } else if let backButton = backButton {
                    // Default to legacy if we have default back button.
                    leftItems.append(backButton)
                }
            }
        }
        
        // Add the panel button after the back button.
        if let panelButton = leftPanelButton,
            leftPanelIsAccessible,
            !leftPanelStaysExtended() {
            leftItems.append(panelButton)
        }
        
        // Add other model buttons
        if let leftItemModels = navigationItemModel?.additionalLeftButtons {
            for item in leftItemModels {
                leftItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil))
            }
        }
        
        // Add any buttons added by the splitview.
        if let additionalLeftButtons = additionalLeftButtons(for: viewController) {
            leftItems.append(contentsOf: additionalLeftButtons)
        }
        
        topViewController.navigationItem.setLeftBarButtonItems(leftItems.count > 0 ? leftItems : nil, animated: !DisableAnimations.boolValue)
    }
    
    /** Sets the right navigation items for the top view controller based on the model and viewController.
     Panel access is determined if view controller is a ``MVMCoreUIDetailViewProtocol``
     */
    @MainActor
    func setRightNavigationButtons(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol?, viewController: UIViewController) {
        guard let topViewController = NavigationHandler.shared().getViewControllers(for: navigationController).last else { return }

        let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject
        var rightItems: [UIBarButtonItem] = []
        
        // Add the panel button first.
        if let panelButton = rightPanelButton,
            rightPanelIsAccessible,
            !rightPanelStaysExtended() {
            rightItems.append(panelButton)
        }
        
        // Add any buttons added by the splitview.
        if let additionalRightButtons = additionalRightButtons(for: viewController) {
            rightItems.append(contentsOf: additionalRightButtons)
        }
        
        // Add other model buttons
        if let rightItemModels = navigationItemModel?.additionalRightButtons {
            for item in rightItemModels {
                rightItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil))
            }
        }
        
        topViewController.navigationItem.setRightBarButtonItems(rightItems.count > 0 ? rightItems : nil, animated: !DisableAnimations.boolValue)
    }
    
    /** If the current detail view controller has a navigation model.
     ``NavigationController/getNavigationModel(from:)`` from ``getCurrentDetailViewController()``
     */
    @objc func navigationBarModelExists() -> Bool {
        // Legacy Navigation
        guard let currentViewController = getCurrentDetailViewController(),
              let _ = navigationController?.getNavigationModel(from: currentViewController) else { return false }
        return true
    }
    
    /// Convenience function to update the navigation bar if the controller is the current detail controller.
    @MainActor @objc
    func updateNavigationBarFor(viewController: UIViewController) {
        guard let navigationController = navigationController,
              navigationController.isDisplayed(viewController: viewController),
              let model = navigationController.getNavigationModel(from: viewController) else { return }
        setNavigationBar(for: viewController, navigationController: navigationController, navigationItemModel: model)
        Task {
            guard (await NotificationHandler.shared()?.getCurrentNotification()?.0 as? StatusBarUI) == nil || topAlertView == nil else { return }
            setStatusBarForCurrentViewController()
        }
    }
    
    // MARK: - Status Bar
    /// Returns the bar style for the background color. Light if on a dark background, otherwise default.
    func getStatusBarStyle(for backgroundColor: UIColor?) -> UIStatusBarStyle {
        var greyScale: CGFloat = 0
        if backgroundColor?.getWhite(&greyScale, alpha: nil) == true,
              greyScale < 0.5 { return .lightContent }
        return .default
    }
    
    /** Updates the status bar background color and style for the passed view controller.
     The background color is fetched from ``MVMCoreUIDetailViewProtocol/defaultStatusBarBackgroundColor()``, else the current navigation bar background, else the current status bar background color.
     The backgroundStytle is fetched from ``MVMCoreUIDetailViewProtocol/defaultStatusBarStyle()`` else ``getStatusBarStyle(for:)``
     */
    @MainActor
    func setStatusBar(for viewController: UIViewController?) {
        let viewController = viewController as? MVMCoreUIDetailViewProtocol
        let backgroundColor = viewController?.defaultStatusBarBackgroundColor?() ??
        navigationController?.navigationBar.standardAppearance.backgroundColor ??
        statusBarView?.backgroundColor

        let style = viewController?.defaultStatusBarStyle?() ??
        getStatusBarStyle(for: backgroundColor)

        setStatusBarBackgroundColor(backgroundColor, style: style)
    }
    
    /** Updates the status bar background color and style for the current view controller.
     See ``setStatusBar(for:)``
     */
    @MainActor
    @objc func setStatusBarForCurrentViewController() {
        setStatusBar(for: getCurrentViewController())
    }
}

extension MVMCoreUISplitViewController: MVMCoreViewManagerProtocol {
    public func getAccessibilityElements() -> [Any]? {
        nil
    }
    
    public func getCurrentViewController() -> UIViewController? {
        navigationController?.getCurrentViewController()
    }
    
    public func containsPage(withPageType pageType: String?) -> Bool {
        navigationController?.containsPage(withPageType: pageType) ?? false
    }
    
    public func willDisplay(_ viewController: UIViewController) {
        setupPanels(viewController)
        updateState(with: viewController)
    }
    
    public func newDataReceived(in viewController: UIViewController) {
        updateState(with: viewController)
    }
}

@objc public extension MVMCoreUISplitViewController {
    @objc func goBack() {
        Task(priority: .userInitiated) { @MainActor in
            if let viewController = getCurrentDetailViewController() as? MVMCoreUIDetailViewProtocol,
               let backButtonPressed = viewController.backButtonPressed {
                backButtonPressed()
            } else {
                await NavigationHandler.shared().popTopViewController()
            }
        }
    }
    
    /// Subscribes for notification events.
    @objc func subscribeForNotifications() {
        guard cancellables == nil else { return }
        var cancellables = Set<AnyCancellable>()
        
        // Ensure the status bar background color and tint are proper for the notification.
        NotificationHandler.shared()?.onNotificationWillShow.receive(on: DispatchQueue.main)
            .sink { [weak self] (notification, model) in
            guard let conformer = notification as? StatusBarUI else { return }
            let statusBarUI = conformer.getStatusBarUI()
            self?.setStatusBarBackgroundColor(statusBarUI.color, style: statusBarUI.style)
        }.store(in: &cancellables)
        
        NotificationHandler.shared()?.onNotificationUpdated.receive(on: DispatchQueue.main)
            .sink { [weak self] (notification, model) in
            guard let conformer = notification as? StatusBarUI else { return }
            let statusBarUI = conformer.getStatusBarUI()
            self?.setStatusBarBackgroundColor(statusBarUI.color, style: statusBarUI.style)
        }.store(in: &cancellables)
        
        // Ensure the status bar background color and tint are proper for the view controller.
        NotificationHandler.shared()?.onNotificationDismissed.receive(on: DispatchQueue.main)
            .sink { (notification, model) in
            guard notification is StatusBarUI else { return }
            MVMCoreUISplitViewController.main()?.setStatusBarForCurrentViewController()
        }.store(in: &cancellables)
        self.cancellables = cancellables
    }
}
