//
//  SubNavManagerController.swift
//  MVMCoreUI
//
//  Created by Scott Pfeil on 10/22/21.
//  Copyright © 2021 Verizon Wireless. All rights reserved.
//

import Foundation
import MVMCore
import VDSColorTokens

open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, TabsDelegate, MVMCorePresentationDelegateProtocol, SubNavSwipeNavigationProtocol {
    /// The number of tabs count or less that will turn on the fillContainer
    public var fillContainerTabsCount = 2 { didSet { tabs.fillContainer = tabsModel.tabs.count <= fillContainerTabsCount }}

    /// The current managed view controller
    private var viewController: UIViewController
    
    /// A list of cached controllers.
    private var viewControllers: [UIViewController?]
    
    /// Used to layout the ui.
    public lazy var stackView: UIStackView = {
        let stackView = UIStackView(arrangedSubviews: [tabsWrapper, subNavigationController.view])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.isAccessibilityElement = false
        stackView.axis = .vertical
        return stackView
    }()

    private var tabsLeadingConstraint: NSLayoutConstraint?
    private var tabsTrailingConstraint: NSLayoutConstraint?
        
    private lazy var tabsWrapper: UIView = {
        // create the wrapper view for tabs
        let tabsWrapper = UIView()
        tabsWrapper.translatesAutoresizingMaskIntoConstraints = false
        tabsWrapper.addSubview(tabs)
        
        // tabs constraints on wrapper
        tabs.pinTop()
        tabs.pinBottom()
        tabsLeadingConstraint = tabs.pinLeading(anchor: tabsWrapper.leadingAnchor)?.activate()
        tabsTrailingConstraint = tabsWrapper.pinTrailing(anchor: tabs.trailingAnchor)?.activate()
        return tabsWrapper
    }()
    
    private var isFillContainer: Bool { tabsModel.tabs.count <= fillContainerTabsCount }
    
    private var tabsModel: TabsModel

    public lazy var tabs: Tabs = {
        let tabs = Tabs(model: tabsModel, delegateObjectIVar, nil)
        tabs.backgroundColor = (viewController as? PageProtocol)?.pageModel?.navigationBar?.backgroundColor?.uiColor ?? VDSColor.backgroundPrimaryLight
        tabs.delegate = self
        return tabs
    }()
    
    public lazy var subNavigationController: UINavigationController = {
        let subNavigationController = SubNavManagerNavigationController(rootViewController: viewController)
        subNavigationController.view.translatesAutoresizingMaskIntoConstraints = false
        return subNavigationController
    }()
        
    /// Interactor for swiping.
    public var customInteractor: SubNavInteractor?
    
    /// The index to go to.
    private var index: Int?
    
    /// Flag for if we need to call a track action.
    private var needToTrackTabSelect = false
    
    public init(viewController: UIViewController, tabsModel: TabsModel, loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) {
        self.viewController = viewController
        self.tabsModel = tabsModel
        viewControllers = [UIViewController?](repeating: nil, count: tabsModel.tabs.count)
        viewControllers[tabsModel.selectedIndex] = viewController
        super.init(nibName: nil, bundle: nil)
        setup(with: loadObject, shouldEnableSwipeGestures: shouldEnableSwipeGestures)
    }

    public init(viewControllers: [UIViewController], tabsModel: TabsModel, loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) {
        self.tabsModel = tabsModel
        self.viewControllers = viewControllers
        viewController = viewControllers[tabsModel.selectedIndex]
        super.init(nibName: nil, bundle: nil)
        setup(with: loadObject, shouldEnableSwipeGestures: shouldEnableSwipeGestures)
    }
    
    func setup(with loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) {
        self.loadObject = loadObject
        pageType = loadObject.pageType
        
        //update the tabs
        tabs.fillContainer = isFillContainer
        updateTabsMargin()
        
        if shouldEnableSwipeGestures {
            customInteractor = SubNavInteractor(viewController: self)
        }
        if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) {
            MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller)
        }
    }
    
    required public init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    open override func loadView() {
        let view = View()
        view.translatesAutoresizingMaskIntoConstraints = true

        addChild(subNavigationController)
        view.addSubview(stackView)
        subNavigationController.didMove(toParent: self)
        
        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            view.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
            stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            view.bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
        ])
        self.view = view
    }
    
    open override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        // Notify the view controller it is showing.
        if manager == nil {
            (viewController as? MVMCoreViewManagerViewControllerProtocol)?.viewControllerReady?(inManager: self)
        }
    }
    
    open override func pageShown() {
        // Currently not calling super until we can decouple page shown logics for managers.
        hideNavigationBarLine(true)
    }
    
    open override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // Notify showing view we will disappear.
        (viewController as? MVMCoreViewManagerViewControllerProtocol)?.managerWillDisappear?(self)
    }
    
    open override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        hideNavigationBarLine(true)
    }
    /// ensures margin for tabs are correct
    private func updateTabsMargin() {
        let tabsGutterSpacing = isFillContainer ? 0 : Padding.Component.horizontalPaddingForSize(view.bounds.size.width)
        tabsLeadingConstraint?.constant = tabsGutterSpacing
        tabsTrailingConstraint?.constant = tabsGutterSpacing
    }
    
    /// Hides/Shows the navigation bar for the page.
    open func hideNavigationBarLine(_ isHidden: Bool) {
        guard self == navigationController?.topViewController else { return }
        var model: LineModel?
        if isHidden {
            model = LineModel(type: .none)
        } else if let lineModel = (getCurrentViewController() as? PageProtocol)?.pageModel?.navigationBar?.line {
            model = lineModel
        }
        navigationController?.navigationBar.standardAppearance.setShadow(for: model)
        navigationController?.navigationBar.scrollEdgeAppearance?.setShadow(for: model)
    }
    
    open override func updateViews() {
        super.updateViews()
        if screenSizeChanged() {
            tabs.updateView(view.bounds.size.width)
            updateTabsMargin()
        }
    }
    
    // MARK: - MVMCoreLoadDelegateProtocol
    
    open func shouldContinue(toErrorPage loadObject: MVMCoreLoadObject, error: MVMCoreErrorObject?) -> Bool {
        // Push error screens so they do not replace the tab page.
        MVMCoreDispatchUtility.performSyncBlock(onMainThread: {
            loadObject.requestParameters?.navigationController = self.navigationController
        })
        loadObject.requestParameters?.loadStyle = .push
        return true
    }
    
    // MARK: - Action Related
    
    /// Logs the action for the selected tab.
    open func trackSelectTab() {
        guard let action = tabs.viewModel?.tabs[tabs.selectedIndex].action else { return }
        MVMCoreUIActionHandler.shared()?.logAction(with: action.toJSON(), additionalData: getAdditionalDataForNewTabLoad(indexPath: IndexPath(row: tabs.selectedIndex, section: 0)), delegateObject: delegateObjectIVar)
    }
    
    /// Allow override of additioonal data for tab press action
    open func getAdditionalDataForNewTabLoad(indexPath: IndexPath) -> [AnyHashable: Any]? {
        return MVMCoreUIActionHandler.add(sourceModel: tabsModel, to: ["tabBarPressed": true])
    }
    
    /// Allows modification of requestParameters object for openPage request
    open func getRequestParametersForNewTabLoad(requestParameters: MVMCoreRequestParameters, model: ActionOpenPageModel, additionalData: [AnyHashable : Any]?) -> MVMCoreRequestParameters {
        requestParameters.navigationController = subNavigationController
        requestParameters.loadStyle = .replaceCurrent
        requestParameters.tabWasPressed = true
        return requestParameters
    }
    
    open override func performAction(with model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) async throws {
        guard var model = model as? ActionOpenPageModel,
           let additionalData = additionalData,
           additionalData.boolForKey("tabBarPressed") else {
               return try await super.performAction(with: model, additionalData: additionalData, delegateObject: delegateObject)
           }
        // Allow opportunity to modify parameters.
        model.requestParameters = getRequestParametersForNewTabLoad(requestParameters: model.requestParameters, model: model, additionalData: additionalData)
        try await super.performAction(with: model, additionalData: additionalData, delegateObject: delegateObject)
    }
    
    // MARK: - MVMCorePresentationDelegateProtocol
    
    public func navigationController(_ navigationController: UINavigationController, interactiveTransitionWasCanceled canceled: Bool) {
        needToTrackTabSelect = false
        index = nil
    }
    
    public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        guard navigationController == subNavigationController,
              let index = index else { return nil }
        
        // Tab bars animate left and right navigation accordingly.
        if tabs.selectedIndex < index {
            return SubNavSwipeAnimator(swipingDirection: .left, controller: self)
        } else if tabs.selectedIndex > index {
            return SubNavSwipeAnimator(swipingDirection: .right, controller: self)
        } else {
            return nil
        }
    }
    
    public func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        // Only percent interact if we've already loaded the view controller
        guard let customInteractor = customInteractor,
              let index = index,
              customInteractor.panning,
              viewControllers[index] != nil else { return nil }
        customInteractor.interactive = true
        return customInteractor
    }
    
    /// Handles when the controller has committed to be changed.
    open func commitTo(controller: UIViewController) {
        guard let index = index,
              index != tabs.selectedIndex else { return }
        viewController = controller
        pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType
        if let viewController = getCurrentViewController() {
            manager?.willDisplay?(viewController)
        }
        tabs.selectIndex(index, animated: true)
        self.index = nil
        hideNavigationBarLine(true)
    }
    
    public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        guard navigationController == subNavigationController else { return }
        
        if let viewController = viewController as? UIViewController & MVMCoreViewManagerViewControllerProtocol & MVMCoreViewControllerProtocol {
            MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: viewController)

            // Cache the controller.
            if viewController.shouldCacheInManager?() ?? true,
               let index = index {
                viewControllers[index] = viewController
            }
        }
        
        // Wait to consider page loaded until after transition if we are custom animating swipe, otherwise do it now.
        guard customInteractor?.interactive != true else { return }
        commitTo(controller: viewController)
    }
    
    public func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
        guard navigationController == subNavigationController else { return }
        // Need to track swipe action.
        if needToTrackTabSelect {
            needToTrackTabSelect = false
            trackSelectTab()
        }
        
        // Consider the page loaded if we have not already
        commitTo(controller: viewController)
        (viewController as? MVMCoreViewManagerViewControllerProtocol)?.viewControllerReady?(inManager: self)
        if let viewController = getCurrentViewController() {
            manager?.displayedViewController?(viewController)
        }
    }
    
    // MARK: - TabsDelegate
    
    // We will manually manage tab selection due to possible swipe interactions.
    open func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool {
        guard tabs.selectedIndex != indexPath.row else { return false }
        index = indexPath.row
        if let controller = viewControllers[indexPath.row] {
            // Load controller from the cache
            needToTrackTabSelect = true
            Task(priority: .userInitiated) {
                await NavigationHandler.shared().replace(viewController: controller, navigationController:subNavigationController, delegateObject:delegateObject(), tryToReplace: false, animated: true)
            }
        } else if let tabsModel = tabs.viewModel,
                  let action = tabsModel.tabs[indexPath.row].action {
            // Perform the tab action
            MVMCoreUIActionHandler.performActionUnstructured(with: action, sourceModel: tabsModel, additionalData: getAdditionalDataForNewTabLoad(indexPath: indexPath), delegateObject: delegateObject())
        }
        return false
    }
    
    // Not currently used.
    open func didSelectItem(_ indexPath: IndexPath, tabs: Tabs) {}
    
    // MARK: - MVMCoreViewManagerViewControllerProtocol
    
    open override func viewControllerReady(inManager manager: UIViewController & MVMCoreViewManagerProtocol) {
        super.viewControllerReady(inManager: manager)
        // Pass on down
        (viewController as? MVMCoreViewManagerViewControllerProtocol)?.viewControllerReady?(inManager: self)
    }
    
    // MARK: - MVMCoreViewManagerProtocol
    
    open func getCurrentViewController() -> UIViewController? {
        MVMCoreUIUtility.getViewControllerTraversingManagers(viewController)
    }
    
    open func containsPage(withPageType pageType: String?) -> Bool {
        guard let pageType = pageType else { return false }
        return viewControllers.contains { controller in
           return (controller as? MVMCoreViewControllerProtocol)?.pageType == pageType
        }
    }
    
    @objc public func getAccessibilityElements() -> [Any]? {
        [tabs]
    }

    open func newDataReceived(in viewController: UIViewController) {
        manager?.newDataReceived?(in: viewController)
        hideNavigationBarLine(true)
    }
    
    public func willDisplay(_ viewController: UIViewController) {
        manager?.willDisplay?(viewController)
        hideNavigationBarLine(true)
    }
    
    public func displayedViewController(_ viewController: UIViewController) {
        manager?.displayedViewController?(viewController)
    }
    
    // MARK: - MVMCoreUISwipeNavigationProtocol
    
    public func swipeLeft() {
        guard tabs.selectedIndex < (tabs.viewModel?.tabs.count ?? 0) - 1 else { return }
        _ = shouldSelectItem(IndexPath(row: tabs.selectedIndex + 1, section: 0), tabs: tabs)
    }
    
    public func swipeRight() {
        guard tabs.selectedIndex != 0 else { return }
        _ = shouldSelectItem(IndexPath(row: tabs.selectedIndex - 1, section: 0), tabs: tabs)
    }
    
    public func update(percentage: CGFloat) {
        guard customInteractor?.interactive == true,
        let index = index else { return }
//        tabs.progress(from: tabs.selectedIndex, toIndex: index, percentage: percentage)
    }
}
