//
//  ViewController.swift
//  MVMCoreUI
//
//  Created by Scott Pfeil on 11/5/19.
//  Copyright © 2019 Verizon Wireless. All rights reserved.
//

import UIKit
import MVMCore

@objc open class ViewController: UIViewController, MVMCoreViewControllerProtocol, MVMCoreViewManagerViewControllerProtocol, MoleculeDelegateProtocol, FormHolderProtocol, MVMCoreActionDelegateProtocol, ActionDelegateProtocol, MVMCoreLoadDelegateProtocol, UITextFieldDelegate, UITextViewDelegate, ObservingTextFieldDelegate, MVMCoreUIDetailViewProtocol, PageProtocol, PageBehaviorHandlerProtocol {
    
    //--------------------------------------------------
    // MARK: - Properties
    //--------------------------------------------------
    
    @objc public var pageType: String?
    @objc public var loadObject: MVMCoreLoadObject?
    public var model: MVMControllerModelProtocol?
    public var pageModel: PageModelProtocol? {
        get { model }
        set { model = newValue as? MVMControllerModelProtocol }
    }
    
    /// Set if this page is containted in a manager.
    public weak var manager: (UIViewController & MVMCoreViewManagerProtocol)?
    
    /// A temporary iVar backer for delegateObject() until we change the protocol
    open lazy var delegateObjectIVar: MVMCoreUIDelegateObject = {
        MVMCoreUIDelegateObject.create(withDelegateForAll: self)
    }()
    
    public func delegateObject() -> DelegateObject? { delegateObjectIVar }
    
    public var formValidator: FormValidator?
    
    public var behaviors: [PageBehaviorProtocol]?
    
    public var needsUpdateUI = false
    private var observingForResponses: NSObjectProtocol?
    private var initialLoadFinished = false
    public var previousScreenSize = CGSize.zero
    
    public var selectedField: UIView?
    
    public var pageUpdateQueue: OperationQueue = {
        let pageUpdateQueue = OperationQueue()
        pageUpdateQueue.maxConcurrentOperationCount = 1
        pageUpdateQueue.qualityOfService = .userInteractive
        return pageUpdateQueue
    }()
    
    /// Checks if the screen width has changed
    open func screenSizeChanged() -> Bool {
        !MVMCoreGetterUtility.cgfequalwiththreshold(previousScreenSize.width, view.bounds.size.width, 0.1)
    }
    
    //--------------------------------------------------
    // MARK: - Response handling
    //--------------------------------------------------
    
    open func observeForResponseJSONUpdates() {
        guard observingForResponses == nil,
              (pagesToListenFor()?.count ?? 0 > 0 || modulesToListenFor()?.count ?? 0 > 0)
        else { return }
        
        observingForResponses = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil, queue: pageUpdateQueue, using: responseJSONUpdated(notification:))
    }
    
    open func stopObservingForResponseJSONUpdates() {
        guard let observingForResponses = observingForResponses else { return }
        NotificationCenter.default.removeObserver(observingForResponses)
        self.observingForResponses = nil
    }
    
    open func pagesToListenFor() -> [String]? {
        guard let pageType = loadObject?.pageType else { return nil }
        return [pageType]
    }
    
    open func modulesToListenFor() -> [String]? {
        let requestModules = loadObject?.requestParameters?.allModules() ?? []
        let behaviorModules = behaviors?.flatMap { $0.modulesToListenFor() } ?? []
        return requestModules + behaviorModules
    }
    
    @objc open func responseJSONUpdated(notification: Notification) {
        // Checks for a page we are listening for.
        var newData = false
        if let pagesLoaded = notification.userInfo?.optionalDictionaryForKey(KeyPageMap),
           let pageType = pagesToListenFor()?.first(where: { (pageTypeListened) -> Bool in
            guard let page = pagesLoaded.optionalDictionaryForKey(pageTypeListened),
                  let pageType = page.optionalStringForKey(KeyPageType),
                  pageType == pageTypeListened
            else { return false }
            
            return true
           }) {
            newData = true
            loadObject?.pageJSON = pagesLoaded.optionalDictionaryForKey(pageType)
        }
        
        // Checks for modules we are listening for.
        if let modulesLoaded = notification.userInfo?.optionalDictionaryForKey(KeyModuleMap),
           let modulesListened = modulesToListenFor() {
            for moduleName in modulesListened {
                if let module = modulesLoaded.optionalDictionaryForKey(moduleName) {
                    newData = true
                    var currentModules = loadObject?.modulesJSON ?? [:]
                    currentModules.updateValue(module, forKey: moduleName)
                    loadObject?.modulesJSON = currentModules
                }
            }
        }
        
        guard newData else { return }
        
        do {
            // TODO: Parse parsePageJSON modifies the page model on a different thread than
            // the UI update which could cause discrepancies. Parse should return the resulting
            // object and assignment should be synchronized on handleNewData(model: ).
            try parsePageJSON()
            MVMCoreDispatchUtility.performBlock(onMainThread: {
                self.handleNewData()
            })
        } catch {
            if let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: "updateJSON for pageType: \(String(describing: pageType))") {
                MVMCoreLoggingHandler.shared()?.addError(toLog: coreError)
            }
        }
    }
    
    open func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>) -> Bool {
        pageType = loadObject.pageType
        self.loadObject = loadObject
        
        // Verifies all modules needed are loaded.
        guard ViewController.verifyRequiredModulesLoaded(for: loadObject, error: error) else { return false }
        
        // Parse the model for the page.
        do {
            try parsePageJSON()
        } catch let parsingError {
            // Log all parsing errors and fail load.
            if let errorObject = MVMCoreLoadHandler.sharedGlobal()?.error(for: loadObject, causedBy: parsingError) {
                errorObject.messageToDisplay = MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess)
                errorObject.messageToLog = describe(parsingError: parsingError)
                error.pointee = errorObject
            }
            return false
        }
        
        if let pageData = loadObject.dataForPage {
            executeBehaviors { (behavior: PageLocalDataShareBehavior) in
                behavior.receiveLocalPageData(pageData, delegateObjectIVar)
            }
        }
        
        ///Check with behavior if it can continue
        do{
            let allFinishedProcessingLoad = try executeThrowingBehaviors {(behavior: PageMoleculeTransformationBehavior) in
                return try behavior.shouldFinishProcessingLoad(loadObject)
            }
            guard allFinishedProcessingLoad else { return false }
        } catch let behaviorError {
            if let errorObject = MVMCoreErrorObject.createErrorObject(for: behaviorError, location: MVMCoreLoadHandler.sharedGlobal()?.errorLocation(forRequest: loadObject)) {
                error.pointee = errorObject
            }
            return false
        }
        
        return true
    }
    
    func describe(parsingError: Error) -> String {
        if let error = parsingError as? HumanReadableDecodingErrorProtocol {
            return "Error parsing template. \(error.readableDescription)"
        }
        return "Error parsing template. \((parsingError as NSError).localizedFailureReason ?? parsingError.localizedDescription)"
    }
    
    open func parsePageJSON() throws {
        if let backgroundRequest = loadObject?.requestParameters?.backgroundRequest, !backgroundRequest, let pageType, let identifier = loadObject?.identifier {
            MVMCoreLoggingHandler.shared()?.logCoreEvent(.pageProcessingComplete(pageType: pageType, requestUUID: identifier, webUrl: nil))
        }
    }
    
    open class func verifyRequiredModulesLoaded(for loadObject: MVMCoreLoadObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>) -> Bool {
        guard let pageType = loadObject?.pageType,
              var modulesRequired = MVMCoreUIViewControllerMappingObject.shared()?.modulesRequired(forPageType: pageType),
              !modulesRequired.isEmpty
        else { return true }
        
        guard let loadedModules = loadObject?.modulesJSON else { return false }
        
        for case let key as String in Array(loadedModules.keys) {
            guard modulesRequired.count > 0 else { break }
            if let index = modulesRequired.firstIndex(where: {($0 as? String) == key}) {
                modulesRequired.remove(at: index)
            }
        }
        
        guard modulesRequired.count == 0 else {
            if let loadObject = loadObject, let errorObject = MVMCoreLoadHandler.sharedGlobal()?.error(for: loadObject, withTitle:nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorCritical), code: ErrorCode.requiredModuleNotPresent.rawValue, domain: ErrorDomainNative) {
                errorObject.messageToLog = modulesRequired.description
                error.pointee = errorObject
            }
            return false
        }
        return true
    }
    
    /// Creates a legacy navigation model.
    open func createDefaultLegacyNavigationModel() -> NavigationItemModel {
        let navigationModel = NavigationItemModel()
        navigationModel.title = model?.screenHeading
        return navigationModel
    }
    
    /// Processes any new data. Called after the page is loaded the first time and on response updates for this page, Triggers a render refresh.
    @MainActor
    open func handleNewData() {
        if model?.navigationBar == nil {
            let navigationItem = createDefaultLegacyNavigationModel()
            model?.navigationBar = navigationItem
        }
        
        executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in
            behavior.onPageNew(rootMolecules: getRootMolecules(), delegateObjectIVar)
        }
        
        if formValidator == nil {
            let rules = model?.formRules
            formValidator = FormValidator(rules)
        }
        
        updateUI()
        
        // Notify the manager of new data.
        // Warning: Some flows cause table reloads. Until the UI update is decoupled, should be after the updateUI.
        manager?.newDataReceived?(in: self)
    }
    
    /// Applies the latest model to the UI.
    open func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
        guard molecules == nil else { return }
        
        executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in
            behavior.willRender(rootMolecules: getRootMolecules(), delegateObjectIVar)
        }
        
        if let backgroundColor = model?.backgroundColor {
            view.backgroundColor = backgroundColor.uiColor
        }
        
        needsUpdateUI = true
        view.setNeedsLayout()
    }
    
    public func generateMoleculeView(from model: MoleculeModelProtocol) -> MoleculeViewProtocol? {
        executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in
            behavior.willSetupMolecule(with: model, updating: nil)
        }
        guard let moleculeView = ModelRegistry.createMolecule(model, delegateObject: delegateObjectIVar) else { return nil }
        executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in
            behavior.didSetupMolecule(view: moleculeView, withModel: model)
        }
        return moleculeView
    }
    
    public func updateMoleculeView(_ view: MoleculeViewProtocol, from model: MoleculeModelProtocol) {
        executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in
            behavior.willSetupMolecule(with: model, updating: view)
        }
        view.set(with: model, delegateObjectIVar, nil)
        executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in
            behavior.didSetupMolecule(view: view, withModel: model)
        }
    }
    
    //--------------------------------------------------
    // MARK: - Navigation Item
    //--------------------------------------------------
    
    open func getNavigationModel() -> NavigationItemModelProtocol? {
        // TODO: remove legacy. Temporary, convert legacy to navigation model.
        if model?.navigationBar == nil {
            let navigationItem = createDefaultLegacyNavigationModel()
            model?.navigationBar = navigationItem
        }
        return model?.navigationBar
    }
    
    //--------------------------------------------------
    // MARK: - View Lifecycle
    //--------------------------------------------------
    
    /// Called only once in viewDidLoad
    open func initialLoad() {
        observeForResponseJSONUpdates()
    }
    
    /// Called on screen size update.
    open func updateViews() {
        _ = formValidator?.validate()
    }
    
    override open func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view.
        MVMCoreLoggingHandler.logDebugMessage(withDelegate: "View Controller Loaded : \(self)")
        
        // We use our own margins.
        viewRespectsSystemMinimumLayoutMargins = false
        
        // Presents from the bottom.
        modalPresentationStyle = MVMCoreGetterUtility.isOnIPad() ? .formSheet : .overCurrentContext
        
        // Do some initial loading.
        if !initialLoadFinished {
            initialLoadFinished = true
            initialLoad()
        }
        
        handleNewData()
    }
    
    open override func viewDidLayoutSubviews() {
        // Add to fix a constraint bug where the width is zero and things get messed up.
        guard isViewLoaded, view.bounds.width > 1 else {
            super.viewDidLayoutSubviews()
            return
        }
        
        // First update should be explicit (hence the zero check)
        if needsUpdateUI || (previousScreenSize != .zero && screenSizeChanged()) {
            needsUpdateUI = false
            updateViews()
        }
        
        previousScreenSize = view.bounds.size;
        
        super.viewDidLayoutSubviews()
    }
    
    open func pageShown() {
        // Track.
        MVMCoreUISession.sharedGlobal()?.currentPageType = pageType
        MVMCoreUILoggingHandler.shared()?.defaultLogPageState(forController: self)
        
        executeBehaviors { [weak self] (behavior: PageVisibilityBehavior) in
            behavior.onPageShown(self?.delegateObjectIVar)
        }
        
        if let backgroundRequest = loadObject?.requestParameters?.backgroundRequest, !backgroundRequest, let pageType, let identifier = loadObject?.identifier {
            MVMCoreLoggingHandler.shared()?.logCoreEvent(.pageRenderComplete(pageType: pageType, requestUUID: identifier, templateName: loadObject?.pageJSON?.optionalStringForKey("template"), controllerName: "\(type(of: self))", error: loadObject?.responseInfoMap?.optionalStringForKey("message")))
        }
    }
    
    open override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        executeBehaviors { [weak self] (behavior: PageVisibilityBehavior) in
            behavior.willShowPage(self?.delegateObjectIVar)
        }
    }
    
    open override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        if manager == nil {
            pageShown()
        }
    }
    
    open override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        executeBehaviors { [weak self] (behavior: PageVisibilityBehavior) in
            behavior.willHidePage(self?.delegateObjectIVar)
        }
    }
    
    open override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        executeBehaviors { [weak self] (behavior: PageVisibilityBehavior) in
            behavior.onPageHidden(self?.delegateObjectIVar)
        }
    }
    
    deinit {
        stopObservingForResponseJSONUpdates()
        MVMCoreLoggingHandler.logDebugMessage(withDelegate: "View Controller Deallocated : \(self)")
    }
    
    open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        MVMCoreGetterUtility.isOnIPad() ? UIInterfaceOrientationMask.all : UIInterfaceOrientationMask.portrait
    }
    
    open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        
        // Updates the detail view width
        coordinator.animate(alongsideTransition: { UIViewControllerTransitionCoordinatorContext in
        }) { UIViewControllerTransitionCoordinatorContext in
            self.view.setNeedsLayout()
        }
    }
    //--------------------------------------------------
    // MARK: - MVMCoreViewManagerViewControllerProtocol
    //--------------------------------------------------
    
    open func viewControllerReady(inManager manager: UIViewController & MVMCoreViewManagerProtocol) {
        pageShown()
    }
    
    //--------------------------------------------------
    // MARK: - MVMCoreLoadDelegateProtocol
    //--------------------------------------------------
    
    // TODO: Move this function out of here after architecture cleanup.
    open func loadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: (UIViewController & MVMCoreViewControllerProtocol)?, error: MVMCoreErrorObject?) {
        
        MVMCoreUILoggingHandler.log(withDelegateLoadFinished: loadObject, loadedViewController: loadedViewController, error: error)
        
        // Open the support panel
        if error == nil,
           (loadObject?.requestParameters?.openSupportPanel ?? false) || (loadObject?.systemParametersJSON?.boolForKey(KeyOpenSupport) ?? false) {
            MVMCoreUISession.sharedGlobal()?.splitViewController?.showRightPanel(animated: true)
        }
    }
    
    /// Override this method to avoid adding form params.
    open func addFormParams(requestParameters: MVMCoreRequestParameters, additionalData: [AnyHashable: Any]?) {
        formValidator?.addFormParams(requestParameters: requestParameters, model: MVMCoreUIActionHandler.getSourceModel(from: additionalData) as? MoleculeModelProtocol & FormItemProtocol)
    }
    
    public func handleFieldErrors(_ fieldErrors: [Any]?, loadObject: MVMCoreLoadObject) {
        
        for case let fieldError as [AnyHashable: Any] in fieldErrors ?? [] {
            
            guard let fieldName = fieldError["fieldName"] as? String,
                  let userError = fieldError["userMessage"] as? String,
                  let entryFieldModel = formValidator?.fields[fieldName] as? EntryFieldModel
            else { continue }
            
            entryFieldModel.shouldClearText = fieldError["clearText"] as? Bool ?? true
            entryFieldModel.dynamicErrorMessage = userError
        }
    }
    
    //--------------------------------------------------
    // MARK: - MVMCoreActionDelegateProtocol
    //--------------------------------------------------
    
    open func logAction(withActionInformation actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
        MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: self, actionInformation: actionInformation, additionalData: additionalData)
    }
    
    open func performAction(with model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) async throws {
        var additionalData = additionalData
        if let model = model as? ActionOpenPageModel {
            addFormParams(requestParameters: model.requestParameters, additionalData: additionalData)
            model.requestParameters.parentPageType = loadObject?.pageJSON?.optionalStringForKey("parentPageType")
            
            var pageForwardedData = additionalData ?? [:]
            executeBehaviors { (behavior: PageLocalDataShareBehavior) in
                let dataMap = behavior.compileLocalPageDataForTransfer(delegateObjectIVar)
                pageForwardedData.merge(dataMap) { current, _ in current }
            }
            additionalData = pageForwardedData
        }
        
        do {
            if let behavior = behaviors?.compactMap({ $0 as? PageCustomActionHandlerBehavior }).first(where: { $0.canHandleAction(with: model, additionalData: additionalData) }) {
                logAction(withActionInformation: model.toJSON(), additionalData: additionalData)
                try await behavior.handleAction(with: model, additionalData: additionalData)
            } else {
                try await MVMCoreUIActionHandler.shared()?.handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject)
            }
        } catch {
            handleAction(error: error, model: model, additionalData: additionalData, delegateObject: delegateObject)
            throw error
        }
    }
    
    open func handleAction(error: Error, model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) {
        let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType))!
       
        switch (model) {
        case let model as ActionOpenPageModel:
            errorObject.silentError = model.background ?? false
        default:
            errorObject.silentError = false
        }
        
        MVMCoreUIActionHandler.shared()?.defaultHandleActionError(errorObject, additionalData: additionalData, delegateObject: delegateObject)
    }
    
    //--------------------------------------------------
    // MARK: - MoleculeDelegateProtocol
    //--------------------------------------------------
    
    open func getTemplateModel() -> TemplateModelProtocol? { model }
    
    open func getRootMolecules() -> [MoleculeModelProtocol] { model?.rootMolecules ?? [] }
    
    // Needed otherwise when subclassed, the extension gets called.
    open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { }
    
    public func replaceMoleculeData(_ moleculeModels: [MoleculeModelProtocol]) {
        pageUpdateQueue.addOperation {
            let replacedModels:[MoleculeModelProtocol] = moleculeModels.compactMap { model in
                guard self.attemptToReplace(with: model) else {
                    return nil
                }
                return model
            }
            if replacedModels.count > 0 {
                DispatchQueue.main.sync {
                    self.updateUI(for: replacedModels)
                }
            }
        }
    }
    
    open func attemptToReplace(with replacementModel: MoleculeModelProtocol) -> Bool {
        guard var templateModel = getTemplateModel() else { return false }
        var didReplace = false
        do {
            didReplace = try templateModel.replaceMolecule(with: replacementModel)
            if !didReplace {
                MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Failed to find '\(replacementModel.id)' in the current screen.", code: ErrorCode.viewControllerProcessingJSON.rawValue, domain: ErrorDomainSystem, location: String(describing: type(of: self)))!)
            }
        } catch {
            let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: String(describing: type(of: self)))!
            if let error = error as? HumanReadableDecodingErrorProtocol {
                coreError.messageToLog = "Error replacing molecule \"\(replacementModel.id)\": \(error.readableDescription)"
            }
            MVMCoreLoggingHandler.shared()?.addError(toLog: coreError)
        }
        return didReplace
    }
    
    //--------------------------------------------------
    // MARK: - MVMCoreUIDetailViewProtocol
    //--------------------------------------------------
    
    public func isLeftPanelAccessible() -> Bool {
        // TODO: Remove when hamburger menu is fully phased out.
        if loadObject?.pageJSON?.boolForKey(KeyHideMainMenu) ?? false {
            return false
        }
        return MVMCoreUISession.sharedGlobal()?.launchAppLoadedSuccessfully ?? false
    }
    
    public func isRightPanelAccessible() -> Bool {
        // TODO: Remove when FAB is 100%.
        if let hideRightPanel = model?.hideRightPanel, hideRightPanel == true {
            return false
        }
        return (MVMCoreUISession.sharedGlobal()?.launchAppLoadedSuccessfully ?? false) || showRightPanelForScreenBeforeLaunchApp()
    }
    
    open func showRightPanelForScreenBeforeLaunchApp() -> Bool {
        loadObject?.pageJSON?.lenientBoolForKey("showRightPanel") ?? false
    }
    
    // TODO: make molecular
    open func isOverridingRightButton() -> Bool {
        guard let rightPanelLink = loadObject?.pageJSON?.optionalDictionaryForKey("rightPanelButtonLink")
        else { return false }
        MVMCoreActionHandler.shared()?.handleAction(with: rightPanelLink, additionalData: nil, delegateObject: delegateObject())
        return true
    }
    
    // TODO: make molecular
    open func isOverridingLeftButton() -> Bool {
        guard let leftPanelLink = loadObject?.pageJSON?.optionalDictionaryForKey("leftPanelButtonLink")
        else { return false }
        MVMCoreActionHandler.shared()?.handleAction(with: leftPanelLink, additionalData: nil, delegateObject: delegateObject())
        return true
    }
    
    // Eventually will be moved to Model
    open func bottomProgress() -> Float? {
        guard let progressString = loadObject?.pageJSON?.optionalStringForKey(KeyProgressPercent),
              let progress = Float(progressString)
        else { return nil }
        
        return progress / Float(100)
    }
    
    //--------------------------------------------------
    // MARK: - UITextFieldDelegate
    //--------------------------------------------------
    
    // To Remove TextFields Bug: Keyboard is not dismissing after reaching textfield max length limit
    open func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    
    open func textFieldDidBeginEditing(_ textField: UITextField) {
        selectedField = textField
        
        // TODO: Make this into a protocol
        if UIAccessibility.isVoiceOverRunning {
            if let toolBar = textField.inputAccessoryView as? UIToolbar,
               let _ = toolBar.items?.last,
               let pickerView = textField.inputView as? UIPickerView {
                
                view.accessibilityElements = [pickerView, toolBar]
            }
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                UIAccessibility.post(notification: .layoutChanged, argument: textField.inputView)
            }
        }
    }
    
    open func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
        
        if textField === selectedField {
            if UIAccessibility.isVoiceOverRunning {
                view.accessibilityElements = nil
                UIAccessibility.post(notification: .layoutChanged, argument: textField)
            }
            
            selectedField = nil
        }
    }
    
    @objc open func dismissFieldInput(_ sender: Any?) {
        selectedField?.resignFirstResponder()
    }
    
    //--------------------------------------------------
    // MARK: - UITextViewDelegate
    //--------------------------------------------------
    
    public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
        selectedField = textView
        return true
    }
    
    open func textViewDidBeginEditing(_ textView: UITextView) {
        selectedField = textView
    }
    
    open func textViewDidEndEditing(_ textView: UITextView) {
        if textView === selectedField {
            selectedField = nil
        }
    }
}
