import Foundation
import UIKit
import AppBundle
import AsyncDisplayKit
import Display
import SolidRoundedButtonNode
import SwiftSignalKit
import OverlayStatusController
import AccountContext
import TelegramPresentationData
import PresentationDataUtils
import TelegramCore
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ActivityIndicator

public enum TwoFactorDataInputMode {
    public struct Recovery {
        public enum Mode {
            case notAuthorized(syncContacts: Bool)
            case authorized
        }

        public var code: String
        public var mode: Mode

        public init(code: String, mode: Mode) {
            self.code = code
            self.mode = mode
        }
    }

    public enum PasswordRecoveryEmailMode {
        case notAuthorized(syncContacts: Bool)
        case authorized
    }

    case password(phoneNumber: String?, doneText: String)
    case passwordRecoveryEmail(emailPattern: String, mode: PasswordRecoveryEmailMode, doneText: String)
    case passwordRecovery(recovery: Recovery, doneText: String)
    case emailAddress(password: String, hint: String, doneText: String)
    case updateEmailAddress(password: String, doneText: String)
    case emailConfirmation(passwordAndHint: (String, String)?, emailPattern: String, codeLength: Int?, doneText: String)
    case passwordHint(recovery: Recovery?, password: String, doneText: String)
    case rememberPassword(doneText: String)
}

public final class TwoFactorDataInputScreen: ViewController {
    private let sharedContext: SharedAccountContext
    private let engine: SomeTelegramEngine
    private var presentationData: PresentationData
    private let mode: TwoFactorDataInputMode
    private let stateUpdated: (SetupTwoStepVerificationStateUpdate) -> Void
    private let actionDisposable = MetaDisposable()
    
    private let forgotDataDisposable = MetaDisposable()
    private let forgotDataPromise = Promise<TwoStepVerificationConfiguration?>(nil)
    private var didSetupForgotData = false
    
    public var passwordRecoveryFailed: (() -> Void)?
    
    public var passwordRemembered: (() -> Void)?
    public var twoStepAuthSettingsController: ((TwoStepVerificationConfiguration) -> ViewController)?
    
    public init(sharedContext: SharedAccountContext, engine: SomeTelegramEngine, mode: TwoFactorDataInputMode, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate) -> Void, presentation: ViewControllerNavigationPresentation = .modalInLargeLayout) {
        self.sharedContext = sharedContext
        self.engine = engine
        self.mode = mode
        self.stateUpdated = stateUpdated
        
        self.presentationData = self.sharedContext.currentPresentationData.with { $0 }
        
        let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme)
        let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor)
        
        super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))
        
        self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
        self.navigationPresentation = presentation
        self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
        self.navigationBar?.intrinsicCanTransitionInline = false
        
        self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
    }
    
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    deinit {
        self.actionDisposable.dispose()
        self.forgotDataDisposable.dispose()
    }
    
    @objc private func backPressed() {
        self.dismiss()
    }
    
    public override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        switch self.mode {
        case .rememberPassword, .password, .passwordHint, .emailAddress:
            (self.displayNode as? TwoFactorDataInputScreenNode)?.focus()
        default:
            break
        }
    }
    
    override public func loadDisplayNode() {
        self.displayNode = TwoFactorDataInputScreenNode(sharedContext: self.sharedContext, presentationData: self.presentationData, mode: self.mode, action: { [weak self] in
            guard let strongSelf = self else {
                return
            }
            switch strongSelf.mode {
            case let .password(_, doneText):
                let values = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText
                if values.count != 2 {
                    return
                }
                if values[0] != values[1] {
                    strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [
                        TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
                    ]), in: .window(.root))
                    return
                }
                if values[0].isEmpty {
                    return
                }
                guard let navigationController = strongSelf.navigationController as? NavigationController else {
                    return
                }
                var controllers = navigationController.viewControllers.filter { controller in
                    if controller is TwoFactorAuthSplashScreen {
                        return false
                    }
                    if controller is TwoFactorDataInputScreen && controller !== strongSelf {
                        return false
                    }
                    return true
                }
                controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordHint(recovery: nil, password: values[0], doneText: doneText), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
                navigationController.setViewControllers(controllers, animated: true)
            case let .passwordRecoveryEmail(_, mode, doneText):
                guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
                    return
                }
                let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
                strongSelf.present(statusController, in: .window(.root))

                strongSelf.actionDisposable.set((strongSelf.engine.auth.checkPasswordRecoveryCode(code: text)
                |> deliverOnMainQueue).start(error: { [weak statusController] error in
                    statusController?.dismiss()
                    guard let strongSelf = self else {
                        return
                    }

                    let text: String
                    switch error {
                    case .limitExceeded:
                        text = strongSelf.presentationData.strings.LoginPassword_FloodError
                    case .invalidCode:
                        text = strongSelf.presentationData.strings.Login_InvalidCodeError
                    case .expired:
                        text = strongSelf.presentationData.strings.Login_CodeExpiredError
                    case .generic:
                        text = strongSelf.presentationData.strings.Login_UnknownError
                    }

                    strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                }, completed: { [weak statusController] in
                    statusController?.dismiss()

                    guard let strongSelf = self else {
                        return
                    }

                    let mappedMode: TwoFactorDataInputMode.Recovery.Mode
                    switch mode {
                    case .authorized:
                        mappedMode = .authorized
                    case let .notAuthorized(syncContacts):
                        mappedMode = .notAuthorized(syncContacts: syncContacts)
                    }

                    let setupController = TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordRecovery(recovery: TwoFactorDataInputMode.Recovery(code: text, mode: mappedMode), doneText: doneText), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation)

                    guard let navigationController = strongSelf.navigationController as? NavigationController else {
                        return
                    }
                    navigationController.replaceController(strongSelf, with: setupController, animated: true)
                }))
            case let .passwordRecovery(recovery, doneText):
                let values = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText
                if values.count != 2 {
                    return
                }
                if values[0] != values[1] {
                    strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [
                        TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
                    ]), in: .window(.root))
                    return
                }
                if values[0].isEmpty {
                    return
                }
                guard let navigationController = strongSelf.navigationController as? NavigationController else {
                    return
                }
                navigationController.replaceController(strongSelf, with: TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordHint(recovery: recovery, password: values[0], doneText: doneText), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation), animated: true)
            case let .emailAddress(password, hint, doneText):
                guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
                    (strongSelf.displayNode as? TwoFactorDataInputScreenNode)?.onAction(success: false)
                    return
                }
                let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
                strongSelf.present(statusController, in: .window(.root))
                
                let _ = (strongSelf.engine.auth.updateTwoStepVerificationPassword(currentPassword: "", updatedPassword: .password(password: password, hint: hint, email: text))
                |> deliverOnMainQueue).start(next: { [weak statusController] result in
                    statusController?.dismiss()
                    
                    guard let strongSelf = self else {
                        return
                    }
                    
                    switch result {
                    case .none:
                        break
                    case let .password(_, pendingEmail):
                        if let pendingEmail = pendingEmail {
                            guard let navigationController = strongSelf.navigationController as? NavigationController else {
                                return
                            }
                            var controllers = navigationController.viewControllers.filter { controller in
                                if controller is TwoFactorAuthSplashScreen {
                                    return false
                                }
                                if controller is TwoFactorDataInputScreen {
                                    return false
                                }
                                return true
                            }
                            controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailConfirmation(passwordAndHint: (password, hint), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init), doneText: doneText), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
                            navigationController.setViewControllers(controllers, animated: true)
                        } else {
                            guard let navigationController = strongSelf.navigationController as? NavigationController else {
                                return
                            }
                            var controllers = navigationController.viewControllers.filter { controller in
                                if controller is TwoFactorAuthSplashScreen {
                                    return false
                                }
                                if controller is TwoFactorDataInputScreen {
                                    return false
                                }
                                return true
                            }
                            controllers.append(TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .done(doneText: doneText)))
                            navigationController.setViewControllers(controllers, animated: true)
                        }
                    }
                }, error: { [weak statusController] error in
                    statusController?.dismiss()
                    
                    guard let strongSelf = self else {
                        return
                    }
                    
                    let presentationData = strongSelf.presentationData
                    let alertText: String
                    switch error {
                    case .generic:
                        alertText = presentationData.strings.Login_UnknownError
                    case .invalidEmail:
                        alertText = presentationData.strings.TwoStepAuth_EmailInvalid
                    }
                    strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                })
            case let .updateEmailAddress(password, doneText):
                guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
                    return
                }
                let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
                strongSelf.present(statusController, in: .window(.root))

                switch strongSelf.engine {
                case let .authorized(engine):
                    let _ = (engine.auth.updateTwoStepVerificationEmail(currentPassword: password, updatedEmail: text)
                    |> deliverOnMainQueue).start(next: { [weak statusController] result in
                        statusController?.dismiss()

                        guard let strongSelf = self else {
                            return
                        }

                        switch result {
                        case .none:
                            break
                        case let .password(_, pendingEmail):
                            if let pendingEmail = pendingEmail {
                                guard let navigationController = strongSelf.navigationController as? NavigationController else {
                                    return
                                }
                                var controllers = navigationController.viewControllers.filter { controller in
                                    if controller is TwoFactorAuthSplashScreen {
                                        return false
                                    }
                                    if controller is TwoFactorDataInputScreen {
                                        return false
                                    }
                                    return true
                                }
                                controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailConfirmation(passwordAndHint: (password, ""), emailPattern: text, codeLength: pendingEmail.codeLength.flatMap(Int.init), doneText: doneText), stateUpdated: strongSelf.stateUpdated))
                                navigationController.setViewControllers(controllers, animated: true)
                            } else {
                                guard let navigationController = strongSelf.navigationController as? NavigationController else {
                                    return
                                }
                                var controllers = navigationController.viewControllers.filter { controller in
                                    if controller is TwoFactorAuthSplashScreen {
                                        return false
                                    }
                                    if controller is TwoFactorDataInputScreen {
                                        return false
                                    }
                                    return true
                                }
                                controllers.append(TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .done(doneText: doneText)))
                                navigationController.setViewControllers(controllers, animated: true)
                            }
                        }
                    }, error: { [weak statusController] error in
                        statusController?.dismiss()

                        guard let strongSelf = self else {
                            return
                        }

                        let presentationData = strongSelf.presentationData
                        let alertText: String
                        switch error {
                        case .generic:
                            alertText = presentationData.strings.Login_UnknownError
                        case .invalidEmail:
                            alertText = presentationData.strings.TwoStepAuth_EmailInvalid
                        }
                        strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                    })
                case .unauthorized:
                    break
                }
            case let .emailConfirmation(_, _, _, doneText):
                guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else {
                    return
                }
                let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
                strongSelf.present(statusController, in: .window(.root))

                switch strongSelf.engine {
                case let .authorized(engine):
                    let _ = (engine.auth.confirmTwoStepRecoveryEmail(code: text)
                    |> deliverOnMainQueue).start(error: { [weak statusController] error in
                        statusController?.dismiss()

                        guard let strongSelf = self else {
                            return
                        }

                        let presentationData = strongSelf.presentationData
                        let text: String
                        switch error {
                        case .invalidEmail:
                            text = presentationData.strings.TwoStepAuth_EmailInvalid
                        case .invalidCode:
                            text = presentationData.strings.Login_InvalidCodeError
                        case .expired:
                            text = presentationData.strings.TwoStepAuth_EmailCodeExpired
                        case .flood:
                            text = presentationData.strings.TwoStepAuth_FloodError
                        case .generic:
                            text = presentationData.strings.Login_UnknownError
                        }
                        strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                    }, completed: { [weak statusController] in
                        statusController?.dismiss()

                        guard let strongSelf = self else {
                            return
                        }

                        guard let navigationController = strongSelf.navigationController as? NavigationController else {
                            return
                        }
                        var controllers = navigationController.viewControllers.filter { controller in
                            if controller is TwoFactorAuthSplashScreen {
                                return false
                            }
                            if controller is TwoFactorDataInputScreen {
                                return false
                            }
                            return true
                        }
                        controllers.append(TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .done(doneText: doneText)))
                        navigationController.setViewControllers(controllers, animated: true)
                    })
                case .unauthorized:
                    break
                }
            case let .passwordHint(recovery, password, doneText):
                guard let value = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !value.isEmpty else {
                    return
                }

                if let recovery = recovery {
                    strongSelf.performRecovery(recovery: recovery, password: password, hint: value)
                } else {
                    strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: value, doneText: doneText), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
                }
            case .rememberPassword:
                guard case let .authorized(engine) = strongSelf.engine else {
                    return
                }
                
                guard let value = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !value.isEmpty else {
                    return
                }
                
                (strongSelf.displayNode as? TwoFactorDataInputScreenNode)?.isLoading = true
                
                let _ = (engine.auth.requestTwoStepVerifiationSettings(password: value)
                |> deliverOnMainQueue).start(error: { [weak self] error in
                    guard let strongSelf = self else {
                        return
                    }
                    
                    (strongSelf.displayNode as? TwoFactorDataInputScreenNode)?.isLoading = false

                    let presentationData = strongSelf.presentationData
                    let text: String?
                    switch error {
                        case .limitExceeded:
                            text = presentationData.strings.LoginPassword_FloodError
                        case .invalidPassword:
                            text = nil
                        case .generic:
                            text = presentationData.strings.Login_UnknownError
                    }
                    if let text = text {
                        strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                    } else {
                        (strongSelf.displayNode as? TwoFactorDataInputScreenNode)?.onAction(success: false)
                    }
                }, completed: { [weak self] in
                    guard let strongSelf = self else {
                        return
                    }
                    
                    (strongSelf.displayNode as? TwoFactorDataInputScreenNode)?.isLoading = false
                    strongSelf.passwordRemembered?()
                    
                    guard let navigationController = strongSelf.navigationController as? NavigationController else {
                        return
                    }
                    var controllers = navigationController.viewControllers.filter { controller in
                        if controller is TwoFactorAuthSplashScreen {
                            return false
                        }
                        if controller is TwoFactorDataInputScreen {
                            return false
                        }
                        return true
                    }
                    controllers.append(TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .remember))
                    navigationController.setViewControllers(controllers, animated: true)
                })
            }
        }, skipAction: { [weak self] in
            guard let strongSelf = self else {
                return
            }
            switch strongSelf.mode {
            case let .emailAddress(password, hint, doneText):
                strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.TwoFactorSetup_Email_SkipConfirmationTitle, text: strongSelf.presentationData.strings.TwoFactorSetup_Email_SkipConfirmationText, actions: [
                    TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.TwoFactorSetup_Email_SkipConfirmationSkip, action: {
                        guard let strongSelf = self else {
                            return
                        }
                        let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
                        strongSelf.present(statusController, in: .window(.root))
                        
                        let _ = (strongSelf.engine.auth.updateTwoStepVerificationPassword(currentPassword: "", updatedPassword: .password(password: password, hint: hint, email: nil))
                        |> deliverOnMainQueue).start(next: { [weak statusController] result in
                            statusController?.dismiss()
                            
                            guard let strongSelf = self else {
                                return
                            }
                            
                            switch result {
                            case .none:
                                break
                            case .password:
                                guard let navigationController = strongSelf.navigationController as? NavigationController else {
                                    return
                                }
                                var controllers = navigationController.viewControllers.filter { controller in
                                    if controller is TwoFactorAuthSplashScreen {
                                        return false
                                    }
                                    if controller is TwoFactorDataInputScreen {
                                        return false
                                    }
                                    return true
                                }
                                controllers.append(TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .done(doneText: doneText)))
                                navigationController.setViewControllers(controllers, animated: true)
                            }
                        }, error: { [weak statusController] error in
                            statusController?.dismiss()
                            
                            guard let strongSelf = self else {
                                return
                            }
                            
                            let presentationData = strongSelf.presentationData
                            let alertText: String
                            switch error {
                            case .generic:
                                alertText = presentationData.strings.Login_UnknownError
                            case .invalidEmail:
                                alertText = presentationData.strings.TwoStepAuth_EmailInvalid
                            }
                            strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                        })
                    }),
                    TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})
                ]), in: .window(.root))
            case let .passwordHint(recovery, password, doneText):
                if let recovery = recovery {
                    strongSelf.performRecovery(recovery: recovery, password: password, hint: "")
                } else {
                    strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: "", doneText: doneText), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
                }
            case let .passwordRecovery(recovery, _):
                strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertTitle, text: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertText, actions: [
                    TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertAction, action: {
                        guard let strongSelf = self else {
                            return
                        }
                        strongSelf.performRecovery(recovery: recovery, password: "", hint: "")
                    }),
                    TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})
                ]), in: .window(.root))
            case let .rememberPassword(doneText):
                guard case let .authorized(engine) = strongSelf.engine else {
                    return
                }
                
                strongSelf.view.endEditing(true)
                
                let sharedContext = strongSelf.sharedContext
                let presentationData = sharedContext.currentPresentationData.with { $0 }
                let navigationController = strongSelf.navigationController as? NavigationController
                
                if !strongSelf.didSetupForgotData {
                    strongSelf.didSetupForgotData = true
                    strongSelf.forgotDataPromise.set(engine.auth.twoStepVerificationConfiguration() |> map(Optional.init))
                }
                let disposable = strongSelf.forgotDataDisposable
                
                disposable.set((strongSelf.forgotDataPromise.get()
                |> take(1)
                |> deliverOnMainQueue).start(next: { [weak self] configuration in
                    if let strongSelf = self, let configuration = configuration {
                        switch configuration {
                            case let .set(_, hasRecoveryEmail, _, _, pendingResetTimestamp):
                                if hasRecoveryEmail {
                                    disposable.set((engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
                                    |> deliverOnMainQueue).start(next: { emailPattern in
                                        var stateUpdated: ((SetupTwoStepVerificationStateUpdate) -> Void)?
                                        let controller = TwoFactorDataInputScreen(sharedContext: sharedContext, engine: .authorized(engine), mode: .passwordRecoveryEmail(emailPattern: emailPattern, mode: .authorized, doneText: doneText), stateUpdated: { state in
                                            stateUpdated?(state)
                                        })
                                        stateUpdated = { [weak controller] state in
                                            controller?.view.endEditing(true)
                                            controller?.dismiss()
                                            
                                            switch state {
                                            case .noPassword, .awaitingEmailConfirmation, .passwordSet:
                                                controller?.dismiss()
                                                
                                                navigationController?.filterController(strongSelf, animated: true)
                                            case .pendingPasswordReset:
                                                let _ = (engine.auth.twoStepVerificationConfiguration()
                                                |> deliverOnMainQueue).start(next: { [weak self] configuration in
                                                    if let strongSelf = self {
                                                        if let navigationController = navigationController, let twoStepAuthSettingsController = strongSelf.twoStepAuthSettingsController?(configuration) {
                                                            var controllers = navigationController.viewControllers.filter { controller in
                                                                if controller is TwoFactorDataInputScreen {
                                                                    return false
                                                                }
                                                                return true
                                                            }
                                                            controllers.append(twoStepAuthSettingsController)
                                                            navigationController.setViewControllers(controllers, animated: true)
                                                        }
                                                    }
                                                })
                                            }
                                        }
                                        strongSelf.push(controller)
                                    }, error: { _ in
                                        strongSelf.present(textAlertController(sharedContext: sharedContext, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                                    }))
                                } else {
                                    if let pendingResetTimestamp = pendingResetTimestamp {
                                        let remainingSeconds = pendingResetTimestamp - Int32(Date().timeIntervalSince1970)
                                        if remainingSeconds <= 0 {
                                            let _ = (engine.auth.requestTwoStepPasswordReset()
                                            |> deliverOnMainQueue).start(next: { result in
                                                switch result {
                                                case .done, .waitingForReset:
                                                    let _ = (engine.auth.twoStepVerificationConfiguration()
                                                    |> deliverOnMainQueue).start(next: { [weak self] configuration in
                                                        if let strongSelf = self {
                                                            if let navigationController = navigationController, let twoStepAuthSettingsController = strongSelf.twoStepAuthSettingsController?(configuration) {
                                                                var controllers = navigationController.viewControllers.filter { controller in
                                                                    if controller is TwoFactorDataInputScreen {
                                                                        return false
                                                                    }
                                                                    return true
                                                                }
                                                                controllers.append(twoStepAuthSettingsController)
                                                                navigationController.setViewControllers(controllers, animated: true)
                                                            }
                                                        }
                                                    })
                                                case .declined:
                                                    break
                                                case let .error(reason):
                                                    let text: String
                                                    switch reason {
                                                    case let .limitExceeded(retryAtTimestamp):
                                                        if let retryAtTimestamp = retryAtTimestamp {
                                                            let remainingSeconds = retryAtTimestamp - Int32(Date().timeIntervalSince1970)
                                                            text = presentationData.strings.TwoFactorSetup_ResetFloodWait(timeIntervalString(strings: presentationData.strings, value: remainingSeconds)).string
                                                        } else {
                                                            text = presentationData.strings.TwoStepAuth_FloodError
                                                        }
                                                    case .generic:
                                                        text = presentationData.strings.Login_UnknownError
                                                    }
                                                    strongSelf.present(textAlertController(sharedContext: sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                                                }
                                            })
                                        }
                                    } else {
                                        strongSelf.present(textAlertController(sharedContext: sharedContext, title: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetTitle, text: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.TwoStepAuth_RecoveryUnavailableResetAction, action: {
                                            let _ = (engine.auth.requestTwoStepPasswordReset()
                                            |> deliverOnMainQueue).start(next: { result in
                                                switch result {
                                                case .done, .waitingForReset:
                                                    let _ = (engine.auth.twoStepVerificationConfiguration()
                                                    |> deliverOnMainQueue).start(next: { [weak self] configuration in
                                                        if let strongSelf = self {
                                                            if let navigationController = navigationController, let twoStepAuthSettingsController = strongSelf.twoStepAuthSettingsController?(configuration) {
                                                                var controllers = navigationController.viewControllers.filter { controller in
                                                                    if controller is TwoFactorDataInputScreen {
                                                                        return false
                                                                    }
                                                                    return true
                                                                }
                                                                controllers.append(twoStepAuthSettingsController)
                                                                navigationController.setViewControllers(controllers, animated: true)
                                                            }
                                                        }
                                                    })
                                                case .declined:
                                                    break
                                                case let .error(reason):
                                                    let text: String
                                                    switch reason {
                                                    case let .limitExceeded(retryAtTimestamp):
                                                        if let retryAtTimestamp = retryAtTimestamp {
                                                            let remainingSeconds = retryAtTimestamp - Int32(Date().timeIntervalSince1970)
                                                            text = presentationData.strings.TwoFactorSetup_ResetFloodWait(timeIntervalString(strings: presentationData.strings, value: remainingSeconds)).string
                                                        } else {
                                                            text = presentationData.strings.TwoStepAuth_FloodError
                                                        }
                                                    case .generic:
                                                        text = presentationData.strings.Login_UnknownError
                                                    }
                                                    strongSelf.present(textAlertController(sharedContext: sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                                                }
                                            })
                                        })]), in: .window(.root))
                                    }
                                }
                            case .notSet:
                                break
                        }
                    }
                }))
            default:
                break
            }
        }, changeEmailAction: { [weak self] in
            guard let strongSelf = self else {
                return
            }
            switch strongSelf.mode {
            case let .emailConfirmation(passwordAndHint, _, _, doneText):
                if let (password, hint) = passwordAndHint {
                    guard let navigationController = strongSelf.navigationController as? NavigationController else {
                        return
                    }
                    var controllers = navigationController.viewControllers.filter { controller in
                        if controller is TwoFactorAuthSplashScreen {
                            return false
                        }
                        if controller is TwoFactorDataInputScreen {
                            return false
                        }
                        return true
                    }
                    controllers.append(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: hint, doneText: doneText), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation))
                    navigationController.setViewControllers(controllers, animated: true)
                } else {
                }
            case .passwordRecoveryEmail:
                switch strongSelf.engine {
                case let .authorized(engine):
                    strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailableResetTitle, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryEmailResetText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailableResetAction, action: {
                        let _ = (engine.auth.requestTwoStepPasswordReset()
                        |> deliverOnMainQueue).start(next: { result in
                            guard let strongSelf = self else {
                                return
                            }
                            switch result {
                            case .done, .waitingForReset:
                                strongSelf.stateUpdated(.pendingPasswordReset)
                            case .declined:
                                break
                            case let .error(reason):
                                let text: String
                                switch reason {
                                case let .limitExceeded(retryAtTimestamp):
                                    if let retryAtTimestamp = retryAtTimestamp {
                                        let remainingSeconds = retryAtTimestamp - Int32(Date().timeIntervalSince1970)
                                        text = strongSelf.presentationData.strings.TwoFactorSetup_ResetFloodWait(timeIntervalString(strings: strongSelf.presentationData.strings, value: remainingSeconds)).string
                                    } else {
                                        text = strongSelf.presentationData.strings.TwoStepAuth_FloodError
                                    }
                                case .generic:
                                    text = strongSelf.presentationData.strings.Login_UnknownError
                                }
                                strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                            }
                        })
                    })]), in: .window(.root))
                case .unauthorized:
                    strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryFailed, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {
                        guard let strongSelf = self else {
                            return
                        }
                        strongSelf.passwordRecoveryFailed?()
                    })]), in: .window(.root))
                }
            default:
                break
            }
        }, resendCodeAction: { [weak self] in
            guard let strongSelf = self else {
                return
            }
            switch strongSelf.mode {
            case .passwordRecoveryEmail:
                let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
                strongSelf.present(statusController, in: .window(.root))

                let _ = (strongSelf.engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
                |> deliverOnMainQueue).start(error: { [weak statusController] error in
                    statusController?.dismiss()

                    guard let strongSelf = self else {
                        return
                    }

                    let text: String
                    switch error {
                    case .limitExceeded:
                        text = strongSelf.presentationData.strings.TwoStepAuth_FloodError
                    case .generic:
                        text = strongSelf.presentationData.strings.Login_UnknownError
                    }
                    strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                }, completed: { [weak statusController] in
                    statusController?.dismiss()
                })
            default:
                guard case let .authorized(engine) = strongSelf.engine else {
                    return
                }
                let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
                strongSelf.present(statusController, in: .window(.root))

                let _ = (engine.auth.resendTwoStepRecoveryEmail()
                |> deliverOnMainQueue).start(error: { [weak statusController] error in
                    statusController?.dismiss()

                    guard let strongSelf = self else {
                        return
                    }

                    let text: String
                    switch error {
                    case .flood:
                        text = strongSelf.presentationData.strings.TwoStepAuth_FloodError
                    case .generic:
                        text = strongSelf.presentationData.strings.Login_UnknownError
                    }
                    strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                }, completed: { [weak statusController] in
                    statusController?.dismiss()
                })
            }
        })
        
        self.displayNodeDidLoad()
    }
    
    override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        super.containerLayoutUpdated(layout, transition: transition)
        
        (self.displayNode as! TwoFactorDataInputScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
    }

    private func performRecovery(recovery: TwoFactorDataInputMode.Recovery, password: String, hint: String) {
        let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: nil))
        self.present(statusController, in: .window(.root))

        switch self.engine {
        case let .unauthorized(engine):
            var syncContacts = false
            switch recovery.mode {
            case let .notAuthorized(syncContactsValue):
                syncContacts = syncContactsValue
            case .authorized:
                break
            }
            let _ = (engine.auth.performPasswordRecovery(code: recovery.code, updatedPassword: password.isEmpty ? .none : .password(password: password, hint: hint, email: nil))
            |> deliverOnMainQueue).start(next: { [weak self, weak statusController] recoveredAccountData in
                statusController?.dismiss()

                guard let strongSelf = self else {
                    return
                }

                if password.isEmpty {
                    strongSelf.stateUpdated(.noPassword)
                } else {
                    strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: false))
                }

                (strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: TwoFactorAuthSplashScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .recoveryDone(recoveredAccountData: recoveredAccountData, syncContacts: syncContacts, isPasswordSet: !password.isEmpty)), animated: true)
            }, error: { [weak self, weak statusController] error in
                statusController?.dismiss()

                guard let strongSelf = self else {
                    return
                }

                let text: String
                switch error {
                case .limitExceeded:
                    text = strongSelf.presentationData.strings.LoginPassword_FloodError
                case .invalidCode:
                    text = strongSelf.presentationData.strings.Login_InvalidCodeError
                case .expired:
                    text = strongSelf.presentationData.strings.Login_CodeExpiredError
                case .generic:
                    text = strongSelf.presentationData.strings.Login_UnknownError
                }

                strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
            }, completed: { [weak statusController] in
                statusController?.dismiss()
            })
        case let .authorized(engine):
            let _ = (engine.auth.performPasswordRecovery(code: recovery.code, updatedPassword: password.isEmpty ? .none : .password(password: password, hint: hint, email: nil))
            |> deliverOnMainQueue).start(error: { [weak self, weak statusController] error in
                statusController?.dismiss()

                guard let strongSelf = self else {
                    return
                }

                let text: String
                switch error {
                case .limitExceeded:
                    text = strongSelf.presentationData.strings.LoginPassword_FloodError
                case .invalidCode:
                    text = strongSelf.presentationData.strings.Login_InvalidCodeError
                case .expired:
                    text = strongSelf.presentationData.strings.Login_CodeExpiredError
                case .generic:
                    text = strongSelf.presentationData.strings.Login_UnknownError
                }

                strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
            }, completed: { [weak self, weak statusController] in
                statusController?.dismiss()

                guard let strongSelf = self else {
                    return
                }

                if password.isEmpty {
                    strongSelf.stateUpdated(.noPassword)
                } else {
                    strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: false))
                }
                strongSelf.dismiss()
            })
        }
    }
}

private enum TwoFactorDataInputTextNodeType {
    case password(phoneNumber: String?, confirmation: Bool)
    case email
    case code
    case hint
}

private func generateClearImage(color: UIColor) -> UIImage? {
    return generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in
        context.clear(CGRect(origin: CGPoint(), size: size))
        context.setFillColor(color.cgColor)
        context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
        context.setBlendMode(.copy)
        context.setStrokeColor(UIColor.clear.cgColor)
        context.setLineCap(.round)
        context.setLineWidth(1.66)
        context.move(to: CGPoint(x: 5.5, y: 5.5))
        context.addLine(to: CGPoint(x: 10.5, y: 10.5))
        context.strokePath()
        context.move(to: CGPoint(x: size.width - 5.5, y: 5.5))
        context.addLine(to: CGPoint(x: size.width - 10.5, y: 10.5))
        context.strokePath()
    })
}

private func generateTextHiddenImage(color: UIColor, on: Bool) -> UIImage? {
    return generateImage(CGSize(width: 20.0, height: 18.0), rotatedContext: { size, context in
        context.clear(CGRect(origin: CGPoint(), size: size))
        guard let image = generateTintedImage(image: UIImage(bundleImageName: "PasswordSetup/TextHidden"), color: color) else {
            return
        }
        context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - size.width) / 2.0), y: floor((size.height - size.height) / 2.0)), size: size))
        if !on {
            context.setLineCap(.round)
            
            context.setBlendMode(.copy)
            context.setStrokeColor(UIColor.clear.cgColor)
            context.setLineWidth(4.0)
            context.move(to: CGPoint(x: 2.0, y: 3.0))
            context.addLine(to: CGPoint(x: 18.0, y: 17.0))
            context.strokePath()
            
            context.setBlendMode(.normal)
            context.setStrokeColor(color.cgColor)
            context.setLineWidth(1.5)
            context.move(to: CGPoint(x: 2.0, y: 3.0))
            context.addLine(to: CGPoint(x: 18.0, y: 17.0))
            context.strokePath()
        }
    })
}

private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelegate {
    private let theme: PresentationTheme
    let mode: TwoFactorDataInputTextNodeType
    private let focusUpdated: (TwoFactorDataInputTextNode, Bool) -> Void
    private let next: (TwoFactorDataInputTextNode) -> Void
    private let updated: (TwoFactorDataInputTextNode) -> Void
    private let toggleTextHidden: (TwoFactorDataInputTextNode) -> Void
    
    private let backgroundNode: ASImageNode
    private var shadowInputNode: TextFieldNode?
    private let inputNode: TextFieldNode
    private let hideButtonNode: HighlightableButtonNode
    private let clearButtonNode: HighlightableButtonNode
    
    private var activityIndicator: ActivityIndicator?
    
    fileprivate var ignoreTextChanged: Bool = false
    
    var isFocused: Bool {
        get {
            return self.inputNode.textField.isFirstResponder
        }
        set {
            let _ = self.inputNode.textField.becomeFirstResponder()
        }
    }
    
    var text: String {
        get {
            return self.inputNode.textField.text ?? ""
        } set(value) {
            self.inputNode.textField.text = value
            self.textFieldChanged(self.inputNode.textField)
        }
    }
    
    var isFailed: Bool = false {
        didSet {
            if self.isFailed != oldValue {
                UIView.transition(with: self.view, duration: 0.2, options: [.transitionCrossDissolve, .curveEaseInOut]) {
                    self.inputNode.textField.textColor = self.isFailed ? self.theme.list.itemDestructiveColor : self.theme.list.freePlainInputField.primaryColor
                    self.hideButtonNode.setImage(generateTextHiddenImage(color: self.isFailed ? self.theme.list.itemDestructiveColor : self.theme.list.freePlainInputField.controlColor, on: !self.inputNode.textField.isSecureTextEntry), for: [])
                    self.backgroundNode.image = self.isFailed ? generateStretchableFilledCircleImage(diameter: 20.0, color: self.theme.list.itemDestructiveColor.withAlphaComponent(0.1)) : generateStretchableFilledCircleImage(diameter: 20.0, color: self.theme.list.freePlainInputField.backgroundColor)
                } completion: { _ in
                    
                }
            }
        }
    }
    
    var isLoading: Bool = false {
        didSet {
            if self.isLoading != oldValue {
                if self.isLoading {
                    if self.activityIndicator == nil {
                        let activityIndicator = ActivityIndicator(type: .custom(self.theme.actionSheet.inputClearButtonColor, 24.0, 1.0, false))
                        self.activityIndicator = activityIndicator
                        self.addSubnode(activityIndicator)
                        if let size = self.validLayout {
                            self.updateLayout(size: size, transition: .immediate)
                        }
                    }
                } else if let activityIndicator = self.activityIndicator {
                    self.activityIndicator = nil
                    activityIndicator.removeFromSupernode()
                }
                self.hideButtonNode.isHidden = self.isLoading
            }
        }
    }
    
    private var validLayout: CGSize?
    
    init(theme: PresentationTheme, mode: TwoFactorDataInputTextNodeType, placeholder: String, focusUpdated: @escaping (TwoFactorDataInputTextNode, Bool) -> Void, next: @escaping (TwoFactorDataInputTextNode) -> Void, updated: @escaping (TwoFactorDataInputTextNode) -> Void, toggleTextHidden: @escaping (TwoFactorDataInputTextNode) -> Void) {
        self.theme = theme
        self.mode = mode
        self.focusUpdated = focusUpdated
        self.next = next
        self.updated = updated
        self.toggleTextHidden = toggleTextHidden
        
        self.backgroundNode = ASImageNode()
        self.backgroundNode.displaysAsynchronously = false
        self.backgroundNode.displayWithoutProcessing = true
        self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: theme.list.freePlainInputField.backgroundColor)
        
        self.inputNode = TextFieldNode()
        self.inputNode.textField.font = Font.regular(17.0)
        self.inputNode.textField.textColor = theme.list.freePlainInputField.primaryColor
        self.inputNode.textField.attributedPlaceholder = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: theme.list.freePlainInputField.placeholderColor)
        
        self.hideButtonNode = HighlightableButtonNode()
        
        switch mode {
        case let .password(phoneNumber, confirmation):
            self.inputNode.textField.keyboardType = .default
            self.inputNode.textField.isSecureTextEntry = true
            if confirmation {
                self.inputNode.textField.returnKeyType = .done
            } else {
                self.inputNode.textField.returnKeyType = .next
            }
            if #available(iOS 12.0, *) {
                #if DEBUG
                if !confirmation, let phoneNumber {
                    let shadowInputNode = TextFieldNode()
                    shadowInputNode.textField.font = Font.regular(17.0)
                    shadowInputNode.textField.textColor = theme.list.freePlainInputField.primaryColor
                    shadowInputNode.textField.attributedPlaceholder = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: theme.list.freePlainInputField.placeholderColor)
                    self.shadowInputNode = shadowInputNode
                    
                    
                    shadowInputNode.textField.textContentType = .username
                    shadowInputNode.textField.text = phoneNumber
                }
                #endif
                
                #if DEBUG
                self.inputNode.textField.textContentType = .newPassword
                self.inputNode.textField.passwordRules = UITextInputPasswordRules(descriptor: "minlength: 8;")
                #endif
            }
            self.hideButtonNode.isHidden = confirmation
        case .email:
            self.inputNode.textField.keyboardType = .emailAddress
            self.inputNode.textField.returnKeyType = .next
            self.hideButtonNode.isHidden = true
            
            if #available(iOS 12.0, *) {
                self.inputNode.textField.textContentType = .emailAddress
            }
            
            self.inputNode.textField.autocorrectionType = .no
            self.inputNode.textField.autocapitalizationType = .none
            self.inputNode.textField.spellCheckingType = .no
            if #available(iOS 11.0, *) {
                self.inputNode.textField.smartQuotesType = .no
                self.inputNode.textField.smartDashesType = .no
                self.inputNode.textField.smartInsertDeleteType = .no
            }
        case .code:
            self.inputNode.textField.keyboardType = .numberPad
            self.inputNode.textField.returnKeyType = .done
            self.hideButtonNode.isHidden = true
            
            self.inputNode.textField.autocorrectionType = .no
            self.inputNode.textField.autocapitalizationType = .none
            self.inputNode.textField.spellCheckingType = .no
            if #available(iOS 11.0, *) {
                self.inputNode.textField.smartQuotesType = .no
                self.inputNode.textField.smartDashesType = .no
                self.inputNode.textField.smartInsertDeleteType = .no
            }
        case .hint:
            self.inputNode.textField.keyboardType = .asciiCapable
            self.inputNode.textField.returnKeyType = .next
            self.hideButtonNode.isHidden = true
            
            self.inputNode.textField.autocorrectionType = .no
            self.inputNode.textField.autocapitalizationType = .none
            self.inputNode.textField.spellCheckingType = .no
            if #available(iOS 11.0, *) {
                self.inputNode.textField.smartQuotesType = .no
                self.inputNode.textField.smartDashesType = .no
                self.inputNode.textField.smartInsertDeleteType = .no
            }
        }
        
        self.inputNode.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
        
        self.hideButtonNode.setImage(generateTextHiddenImage(color: theme.list.freePlainInputField.controlColor, on: false), for: [])
        
        self.clearButtonNode = HighlightableButtonNode()
        self.clearButtonNode.setImage(generateClearImage(color: theme.list.freePlainInputField.controlColor), for: [])
        self.clearButtonNode.isHidden = true
        
        super.init()
        
        self.addSubnode(self.backgroundNode)
        if let shadowInputNode = self.shadowInputNode {
            shadowInputNode.alpha = 0.001
            self.addSubnode(shadowInputNode)
        }
        self.addSubnode(self.inputNode)
        self.addSubnode(self.hideButtonNode)
        
        self.inputNode.textField.delegate = self
        self.inputNode.textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
        
        self.hideButtonNode.addTarget(self, action: #selector(self.hidePressed), forControlEvents: .touchUpInside)
    }
    
    func textFieldDidBeginEditing(_ textField: UITextField) {
        let text = self.text
        
        if self.inputNode.textField.isSecureTextEntry {
            let previousIgnoreTextChanged = self.ignoreTextChanged
            self.ignoreTextChanged = true
            self.inputNode.textField.text = ""
            self.inputNode.textField.insertText(text + " ")
            self.inputNode.textField.deleteBackward()
            self.ignoreTextChanged = previousIgnoreTextChanged
        }
        
        self.focusUpdated(self, true)
    }
    
    func textFieldDidEndEditing(_ textField: UITextField) {
        self.focusUpdated(self, false)
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        self.next(self)
        return false
    }
    
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if self.isLoading {
            return false
        }
        return true
    }
    
    @objc private func textFieldChanged(_ textField: UITextField) {
        if !self.ignoreTextChanged {
            switch self.mode {
            case .password:
                if self.isFailed {
                    self.isFailed = false
                }
            default:
                self.clearButtonNode.isHidden = self.text.isEmpty
            }
            self.updated(self)
        }
    }
    
    @objc private func hidePressed() {
        switch self.mode {
        case .password:
            self.toggleTextHidden(self)
        default:
            break
        }
    }
    
    func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
        self.validLayout = size
        
        let leftInset: CGFloat = 16.0
        let rightInset: CGFloat = 38.0
        
        if let shadowInputNode = self.shadowInputNode {
            transition.updateFrame(node: shadowInputNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: size.width - leftInset - rightInset, height: size.height)))
        }
        
        transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
        transition.updateFrame(node: self.inputNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: size.width - leftInset - rightInset, height: size.height)))
        transition.updateFrame(node: self.hideButtonNode, frame: CGRect(origin: CGPoint(x: size.width - rightInset - 4.0, y: 0.0), size: CGSize(width: rightInset + 4.0, height: size.height)))
        transition.updateFrame(node: self.clearButtonNode, frame: CGRect(origin: CGPoint(x: size.width - rightInset - 4.0, y: 0.0), size: CGSize(width: rightInset + 4.0, height: size.height)))
        
        if let activityIndicator = self.activityIndicator {
            let indicatorSize = activityIndicator.measure(CGSize(width: 24.0, height: 24.0))
            transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: size.width - rightInset + 6.0, y: floorToScreenPixels((size.height - indicatorSize.height) / 2.0) + 1.0), size: indicatorSize))
        }
    }
    
    func focus() {
        self.inputNode.textField.becomeFirstResponder()
    }
    
    func updateTextHidden(_ value: Bool) {
        self.hideButtonNode.setImage(generateTextHiddenImage(color: self.isFailed ? self.theme.list.itemDestructiveColor : self.theme.list.freePlainInputField.controlColor, on: !value), for: [])
        let text = self.inputNode.textField.text ?? ""
        self.inputNode.textField.isSecureTextEntry = value
        if value {
            if self.inputNode.textField.isFirstResponder {
                let previousIgnoreTextChanged = self.ignoreTextChanged
                self.ignoreTextChanged = true
                self.inputNode.textField.text = ""
                self.inputNode.textField.becomeFirstResponder()
                self.inputNode.textField.insertText(text + " ")
                self.inputNode.textField.deleteBackward()
                self.ignoreTextChanged = previousIgnoreTextChanged
            }
        }
    }
}

private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, ASScrollViewDelegate {
    private var presentationData: PresentationData
    private let mode: TwoFactorDataInputMode
    private let action: () -> Void
    private let skipAction: () -> Void
    private let changeEmailAction: () -> Void
    private let resendCodeAction: () -> Void
    
    private let navigationBackgroundNode: ASDisplayNode
    private let navigationSeparatorNode: ASDisplayNode
    private let scrollNode: ASScrollNode
    private var animatedStickerNode: AnimatedStickerNode?
    private var monkeyNode: ManagedMonkeyAnimationNode?
    private let titleNode: ImmediateTextNode
    private let textNode: ImmediateTextNode
    private let skipActionTitleNode: ImmediateTextNode
    private let skipActionButtonNode: HighlightTrackingButtonNode
    private let changeEmailActionTitleNode: ImmediateTextNode
    private let changeEmailActionButtonNode: HighlightTrackingButtonNode
    private let resendCodeActionTitleNode: ImmediateTextNode
    private let resendCodeActionButtonNode: HighlightTrackingButtonNode
    private let inputNodes: [TwoFactorDataInputTextNode]
    private let buttonNode: SolidRoundedButtonNode
    
    private var validLayout: (ContainerViewLayout, CGFloat)?
    
    var inputText: [String] {
        return self.inputNodes.map { $0.text }
    }
    
    var isLoading: Bool = false {
        didSet {
            self.inputNodes.first?.isLoading = self.isLoading
        }
    }
    
    private var skipIsHiddenUntilError = false
    
    init(sharedContext: SharedAccountContext, presentationData: PresentationData, mode: TwoFactorDataInputMode, action: @escaping () -> Void, skipAction: @escaping () -> Void, changeEmailAction: @escaping () -> Void, resendCodeAction: @escaping () -> Void) {
        self.presentationData = presentationData
        self.mode = mode
        self.action = action
        self.skipAction = skipAction
        self.changeEmailAction = changeEmailAction
        self.resendCodeAction = resendCodeAction
        
        self.navigationBackgroundNode = ASDisplayNode()
        self.navigationBackgroundNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
        self.navigationBackgroundNode.alpha = 0.0
        self.navigationSeparatorNode = ASDisplayNode()
        self.navigationSeparatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
        
        self.scrollNode = ASScrollNode()
        self.scrollNode.canCancelAllTouchesInViews = true
        
        switch mode {
        case .password, .passwordRecovery, .emailAddress, .updateEmailAddress:
            self.monkeyNode = ManagedMonkeyAnimationNode()
        case .emailConfirmation, .passwordRecoveryEmail:
            let animatedStickerNode = DefaultAnimatedStickerNodeImpl()
            animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "TwoFactorSetupMail"), width: 272, height: 272, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
                animatedStickerNode.visibility = true
            self.animatedStickerNode = animatedStickerNode
        case .passwordHint:
            let animatedStickerNode = DefaultAnimatedStickerNodeImpl()
            animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "TwoFactorSetupHint"), width: 272, height: 272, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
            animatedStickerNode.visibility = true
            self.animatedStickerNode = animatedStickerNode
        case .rememberPassword:
            let animatedStickerNode = DefaultAnimatedStickerNodeImpl()
            animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "TwoFactorSetupRemember"), width: 272, height: 272, playbackMode: .count(3), mode: .direct(cachePathPrefix: nil))
            animatedStickerNode.visibility = true
            self.animatedStickerNode = animatedStickerNode
        }
        
        let title: String
        let text: NSAttributedString
        let buttonText: String
        let skipActionText: String
        let changeEmailActionText: String
        let resendCodeActionText: String
        
        var inputNodes: [TwoFactorDataInputTextNode] = []
        var next: ((TwoFactorDataInputTextNode) -> Void)?
        var focusUpdated: ((TwoFactorDataInputTextNode, Bool) -> Void)?
        var updated: ((TwoFactorDataInputTextNode) -> Void)?
        var toggleTextHidden: ((TwoFactorDataInputTextNode) -> Void)?
        
        switch mode {
        case let .password(phoneNumber, _):
            title = presentationData.strings.TwoFactorSetup_Password_Title
            text = NSAttributedString(string: "", font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
            buttonText = presentationData.strings.TwoFactorSetup_Password_Action
            skipActionText = ""
            changeEmailActionText = ""
            resendCodeActionText = ""
            inputNodes = [
                TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(phoneNumber: phoneNumber, confirmation: false), placeholder: presentationData.strings.TwoFactorSetup_Password_PlaceholderPassword, focusUpdated: { node, focused in
                    focusUpdated?(node, focused)
                }, next: { node in
                    next?(node)
                }, updated: { node in
                    updated?(node)
                }, toggleTextHidden: { node in
                    toggleTextHidden?(node)
                }),
                TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(phoneNumber: phoneNumber, confirmation: true), placeholder: presentationData.strings.TwoFactorSetup_Password_PlaceholderConfirmPassword, focusUpdated: { node, focused in
                    focusUpdated?(node, focused)
                }, next: { node in
                    next?(node)
                }, updated: { node in
                    updated?(node)
                }, toggleTextHidden: { node in
                    toggleTextHidden?(node)
                })
            ]
        case .passwordRecovery:
            title = presentationData.strings.TwoFactorSetup_PasswordRecovery_Title
            text = NSAttributedString(string: presentationData.strings.TwoFactorSetup_PasswordRecovery_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
            buttonText = presentationData.strings.TwoFactorSetup_PasswordRecovery_Action
            skipActionText = presentationData.strings.TwoFactorSetup_PasswordRecovery_Skip
            changeEmailActionText = ""
            resendCodeActionText = ""
            inputNodes = [
                TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(phoneNumber: nil, confirmation: false), placeholder: presentationData.strings.TwoFactorSetup_PasswordRecovery_PlaceholderPassword, focusUpdated: { node, focused in
                    focusUpdated?(node, focused)
                }, next: { node in
                    next?(node)
                }, updated: { node in
                    updated?(node)
                }, toggleTextHidden: { node in
                    toggleTextHidden?(node)
                }),
                TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(phoneNumber: nil, confirmation: true), placeholder: presentationData.strings.TwoFactorSetup_PasswordRecovery_PlaceholderConfirmPassword, focusUpdated: { node, focused in
                    focusUpdated?(node, focused)
                }, next: { node in
                    next?(node)
                }, updated: { node in
                    updated?(node)
                }, toggleTextHidden: { node in
                    toggleTextHidden?(node)
                })
            ]
        case .emailAddress, .updateEmailAddress:
            title = presentationData.strings.TwoFactorSetup_Email_Title
            text = NSAttributedString(string: presentationData.strings.TwoFactorSetup_Email_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
            buttonText = presentationData.strings.TwoFactorSetup_Email_Action
            skipActionText = presentationData.strings.TwoFactorSetup_Email_SkipAction
            self.skipIsHiddenUntilError = true
            changeEmailActionText = ""
            resendCodeActionText = ""
            inputNodes = [
                TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .email, placeholder: presentationData.strings.TwoFactorSetup_Email_Placeholder, focusUpdated: { node, focused in
                    focusUpdated?(node, focused)
                }, next: { node in
                    next?(node)
                }, updated: { node in
                    updated?(node)
                }, toggleTextHidden: { node in
                    toggleTextHidden?(node)
                }),
            ]
        case let .passwordRecoveryEmail(emailPattern, _, _):
            title = presentationData.strings.TwoFactorSetup_EmailVerification_Title
            let formattedString = presentationData.strings.TwoFactorSetup_EmailVerification_Text(emailPattern)

            let string = NSMutableAttributedString()
            string.append(NSAttributedString(string: formattedString.string, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor))
            for range in formattedString.ranges {
                string.addAttribute(.font, value: Font.semibold(16.0), range: range.range)
            }

            text = string

            buttonText = presentationData.strings.TwoFactorSetup_EmailVerification_Action
            skipActionText = ""
            changeEmailActionText = presentationData.strings.TwoStepAuth_RecoveryEmailResetNoAccess
            resendCodeActionText = presentationData.strings.TwoFactorSetup_EmailVerification_ResendAction
            inputNodes = [
                TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .code, placeholder: presentationData.strings.TwoFactorSetup_EmailVerification_Placeholder, focusUpdated: { node, focused in
                    focusUpdated?(node, focused)
                }, next: { node in
                    next?(node)
                }, updated: { node in
                    updated?(node)
                }, toggleTextHidden: { node in
                    toggleTextHidden?(node)
                }),
            ]
        case let .emailConfirmation(_, emailPattern, _, _):
            title = presentationData.strings.TwoFactorSetup_EmailVerification_Title
            let formattedString = presentationData.strings.TwoFactorSetup_EmailVerification_Text(emailPattern)

            let string = NSMutableAttributedString()
            string.append(NSAttributedString(string: formattedString.string, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor))
            for range in formattedString.ranges {
                string.addAttribute(.font, value: Font.semibold(16.0), range: range.range)
            }
            
            text = string
            
            buttonText = presentationData.strings.TwoFactorSetup_EmailVerification_Action
            skipActionText = ""
            changeEmailActionText = presentationData.strings.TwoFactorSetup_EmailVerification_ChangeAction
            resendCodeActionText = presentationData.strings.TwoFactorSetup_EmailVerification_ResendAction
            inputNodes = [
                TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .code, placeholder: presentationData.strings.TwoFactorSetup_EmailVerification_Placeholder, focusUpdated: { node, focused in
                    focusUpdated?(node, focused)
                }, next: { node in
                    next?(node)
                }, updated: { node in
                    updated?(node)
                }, toggleTextHidden: { node in
                    toggleTextHidden?(node)
                }),
            ]
        case .passwordHint:
            title = presentationData.strings.TwoFactorSetup_Hint_Title
            
            text = NSAttributedString(string: presentationData.strings.TwoFactorSetup_Hint_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
            
            buttonText = presentationData.strings.TwoFactorSetup_Hint_Action
            skipActionText = presentationData.strings.TwoFactorSetup_Hint_SkipAction
            changeEmailActionText = ""
            resendCodeActionText = ""
            inputNodes = [
                TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .hint, placeholder: presentationData.strings.TwoFactorSetup_Hint_Placeholder, focusUpdated: { node, focused in
                    focusUpdated?(node, focused)
                }, next: { node in
                    next?(node)
                }, updated: { node in
                    updated?(node)
                }, toggleTextHidden: { node in
                    toggleTextHidden?(node)
                }),
            ]
        case .rememberPassword:
            title = presentationData.strings.TwoFactorRemember_Title
            text = NSAttributedString(string: presentationData.strings.TwoFactorRemember_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
            buttonText = presentationData.strings.TwoFactorRemember_CheckPassword
            skipActionText = presentationData.strings.TwoFactorRemember_Forgot
            changeEmailActionText = ""
            resendCodeActionText = ""
            inputNodes = [
                TwoFactorDataInputTextNode(theme: presentationData.theme, mode: .password(phoneNumber: nil, confirmation: false), placeholder: presentationData.strings.TwoFactorRemember_Placeholder, focusUpdated: { node, focused in
                    focusUpdated?(node, focused)
                }, next: { node in
                    next?(node)
                }, updated: { node in
                    updated?(node)
                }, toggleTextHidden: { node in
                    toggleTextHidden?(node)
                })
            ]
        }
        
        self.titleNode = ImmediateTextNode()
        self.titleNode.displaysAsynchronously = false
        self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(28.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
        self.titleNode.maximumNumberOfLines = 0
        self.titleNode.textAlignment = .center
        
        self.textNode = ImmediateTextNode()
        self.textNode.displaysAsynchronously = false
        self.textNode.attributedText = text
        self.textNode.maximumNumberOfLines = 0
        self.textNode.lineSpacing = 0.1
        self.textNode.textAlignment = .center
        
        self.skipActionTitleNode = ImmediateTextNode()
        self.skipActionTitleNode.isUserInteractionEnabled = false
        self.skipActionTitleNode.displaysAsynchronously = false
        self.skipActionTitleNode.attributedText = NSAttributedString(string: skipActionText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemAccentColor)
        self.skipActionButtonNode = HighlightTrackingButtonNode()
        self.skipActionTitleNode.isHidden = skipActionText.isEmpty || self.skipIsHiddenUntilError
        self.skipActionButtonNode.isHidden = skipActionText.isEmpty || self.skipIsHiddenUntilError
        
        self.changeEmailActionTitleNode = ImmediateTextNode()
        self.changeEmailActionTitleNode.isUserInteractionEnabled = false
        self.changeEmailActionTitleNode.displaysAsynchronously = false
        self.changeEmailActionTitleNode.attributedText = NSAttributedString(string: changeEmailActionText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemAccentColor)
        self.changeEmailActionButtonNode = HighlightTrackingButtonNode()
        self.changeEmailActionButtonNode.isHidden = changeEmailActionText.isEmpty
        
        self.resendCodeActionTitleNode = ImmediateTextNode()
        self.resendCodeActionTitleNode.isUserInteractionEnabled = false
        self.resendCodeActionTitleNode.displaysAsynchronously = false
        self.resendCodeActionTitleNode.attributedText = NSAttributedString(string: resendCodeActionText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemAccentColor)
        self.resendCodeActionButtonNode = HighlightTrackingButtonNode()
        self.resendCodeActionTitleNode.isHidden = resendCodeActionText.isEmpty
        self.resendCodeActionButtonNode.isHidden = resendCodeActionText.isEmpty
        
        self.inputNodes = inputNodes
        
        self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 11.0, gloss: false)
        
        super.init()
        
        if case .rememberPassword = mode {
            self.buttonNode.alpha = 0.0
        }
        
        self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
        
        self.addSubnode(self.scrollNode)
        
        self.animatedStickerNode.flatMap(self.scrollNode.addSubnode)
        self.monkeyNode.flatMap(self.scrollNode.addSubnode)
        self.scrollNode.addSubnode(self.titleNode)
        self.scrollNode.addSubnode(self.textNode)
        self.scrollNode.addSubnode(self.skipActionTitleNode)
        self.scrollNode.addSubnode(self.skipActionButtonNode)
        self.scrollNode.addSubnode(self.changeEmailActionTitleNode)
        self.scrollNode.addSubnode(self.changeEmailActionButtonNode)
        self.scrollNode.addSubnode(self.resendCodeActionTitleNode)
        self.scrollNode.addSubnode(self.resendCodeActionButtonNode)
        self.scrollNode.addSubnode(self.buttonNode)
        
        for (inputNode) in self.inputNodes {
            self.scrollNode.addSubnode(inputNode)
        }
        
        self.navigationBackgroundNode.addSubnode(self.navigationSeparatorNode)
        self.addSubnode(self.navigationBackgroundNode)
        
        self.buttonNode.pressed = {
            action()
        }
        
        self.skipActionButtonNode.highligthedChanged = { [weak self] highlighted in
            guard let strongSelf = self else {
                return
            }
            if highlighted {
                strongSelf.skipActionTitleNode.layer.removeAnimation(forKey: "opacity")
                strongSelf.skipActionTitleNode.alpha = 0.4
            } else {
                strongSelf.skipActionTitleNode.alpha = 1.0
                strongSelf.skipActionTitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
            }
        }
        self.skipActionButtonNode.addTarget(self, action: #selector(self.skipActionPressed), forControlEvents: .touchUpInside)
        
        self.changeEmailActionButtonNode.highligthedChanged = { [weak self] highlighted in
            guard let strongSelf = self else {
                return
            }
            if highlighted {
                strongSelf.changeEmailActionTitleNode.layer.removeAnimation(forKey: "opacity")
                strongSelf.changeEmailActionTitleNode.alpha = 0.4
            } else {
                strongSelf.changeEmailActionTitleNode.alpha = 1.0
                strongSelf.changeEmailActionTitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
            }
        }
        self.changeEmailActionButtonNode.addTarget(self, action: #selector(self.changeEmailActionPressed), forControlEvents: .touchUpInside)
        
        self.resendCodeActionButtonNode.highligthedChanged = { [weak self] highlighted in
            guard let strongSelf = self else {
                return
            }
            if highlighted {
                strongSelf.resendCodeActionTitleNode.layer.removeAnimation(forKey: "opacity")
                strongSelf.resendCodeActionTitleNode.alpha = 0.4
            } else {
                strongSelf.resendCodeActionTitleNode.alpha = 1.0
                strongSelf.resendCodeActionTitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
            }
        }
        self.resendCodeActionButtonNode.addTarget(self, action: #selector(self.resendCodeActionPressed), forControlEvents: .touchUpInside)
        
        next = { [weak self] node in
            guard let strongSelf = self else {
                return
            }
            if let index = strongSelf.inputNodes.firstIndex(where: { $0 === node }) {
                if index == strongSelf.inputNodes.count - 1 {
                    strongSelf.action()
                } else if strongSelf.buttonNode.isUserInteractionEnabled {
                    strongSelf.inputNodes[index + 1].focus()
                }
            }
        }
        var textHidden = true
        let updateAnimations: () -> Void = { [weak self] in
            guard let strongSelf = self else {
                return
            }
            switch strongSelf.mode {
            case .password, .passwordRecovery:
                if strongSelf.inputNodes[1].isFocused {
                    let maxWidth = strongSelf.inputNodes[1].bounds.width
                    
                    let textNode = ImmediateTextNode()
                    textNode.attributedText = NSAttributedString(string: strongSelf.inputNodes[1].text, font: Font.regular(17.0), textColor: .black)
                    let textSize = textNode.updateLayout(CGSize(width: 1000.0, height: 100.0))

                    var trackingOffset = textSize.width / maxWidth
                    trackingOffset = max(0.0, min(1.0, trackingOffset))
                    strongSelf.monkeyNode?.setState(.tracking(trackingOffset))
                } else if strongSelf.inputNodes[0].isFocused {
                    let hasText = !strongSelf.inputNodes[0].text.isEmpty
                    if !hasText {
                        strongSelf.monkeyNode?.setState(.idle(.still))
                    } else if textHidden {
                        strongSelf.monkeyNode?.setState(.eyesClosed)
                    } else {
                        strongSelf.monkeyNode?.setState(.peeking)
                    }
                } else {
                    strongSelf.monkeyNode?.setState(.idle(.still))
                }
            case .emailAddress:
                if strongSelf.inputNodes[0].isFocused {
                    let maxWidth = strongSelf.inputNodes[0].bounds.width
                    
                    let textNode = ImmediateTextNode()
                    textNode.attributedText = NSAttributedString(string: strongSelf.inputNodes[0].text, font: Font.regular(17.0), textColor: .black)
                    let textSize = textNode.updateLayout(CGSize(width: 1000.0, height: 100.0))

                    var trackingOffset = textSize.width / maxWidth
                    trackingOffset = max(0.0, min(1.0, trackingOffset))
                    strongSelf.monkeyNode?.setState(.tracking(trackingOffset))
                } else {
                    strongSelf.monkeyNode?.setState(.idle(.still))
                }
            default:
                break
            }
        }
        focusUpdated = { node, _ in
            DispatchQueue.main.async {
                updateAnimations()
            }
        }
        updated = { [weak self] _ in
            guard let strongSelf = self else {
                return
            }
            switch strongSelf.mode {
            case .emailAddress, .updateEmailAddress, .passwordRecovery:
                let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
                strongSelf.buttonNode.isHidden = !hasText
                strongSelf.skipActionTitleNode.isHidden = hasText || strongSelf.skipIsHiddenUntilError
                strongSelf.skipActionButtonNode.isHidden = hasText || strongSelf.skipIsHiddenUntilError
            case let .emailConfirmation(_, _, codeLength, _):
                let text = strongSelf.inputNodes[0].text
                let hasText = !text.isEmpty
                strongSelf.buttonNode.isHidden = !hasText
                strongSelf.changeEmailActionTitleNode.isHidden = hasText
                strongSelf.changeEmailActionButtonNode.isHidden = hasText
                strongSelf.resendCodeActionTitleNode.isHidden = hasText
                strongSelf.resendCodeActionButtonNode.isHidden = hasText
                
                if let codeLength = codeLength, text.count == codeLength {
                    action()
                }
            case .passwordRecoveryEmail:
                let text = strongSelf.inputNodes[0].text
                let hasText = !text.isEmpty
                strongSelf.buttonNode.isHidden = !hasText
                strongSelf.changeEmailActionTitleNode.isHidden = hasText
                strongSelf.changeEmailActionButtonNode.isHidden = hasText
                strongSelf.resendCodeActionTitleNode.isHidden = hasText
                strongSelf.resendCodeActionButtonNode.isHidden = hasText

                if text.count == 6 {
                    action()
                }
            case .passwordHint:
                let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
                strongSelf.buttonNode.isHidden = !hasText
                strongSelf.skipActionTitleNode.isHidden = hasText
                strongSelf.skipActionButtonNode.isHidden = hasText
            case .password:
                break
            case .rememberPassword:
                let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty })
                let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
                transition.updateAlpha(node: strongSelf.buttonNode, alpha: hasText ? 1.0 : 0.0)
                transition.updateAlpha(node: strongSelf.skipActionTitleNode, alpha: hasText ? 0.0 : 1.0)
                strongSelf.skipActionButtonNode.isHidden = hasText
                
                if strongSelf.textNode.attributedText?.string != strongSelf.presentationData.strings.TwoFactorRemember_Text {
                    if let snapshotView = strongSelf.textNode.view.snapshotContentTree() {
                        snapshotView.frame = strongSelf.textNode.view.frame
                        strongSelf.textNode.view.superview?.addSubview(snapshotView)
                        
                        snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion:  { [weak snapshotView] _ in
                            snapshotView?.removeFromSuperview()
                        })
                        
                        strongSelf.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                    }
                    
                    strongSelf.textNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.TwoFactorRemember_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
                    if let (layout, navigationHeight) = strongSelf.validLayout {
                        strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
                    }
                }
            }
            updateAnimations()
        }
        toggleTextHidden = { [weak self] _ in
            guard let strongSelf = self else {
                return
            }
            switch strongSelf.mode {
                case .password, .passwordRecovery, .rememberPassword:
                textHidden = !textHidden
                for node in strongSelf.inputNodes {
                    node.updateTextHidden(textHidden)
                }
            default:
                break
            }
            updateAnimations()
        }
        self.inputNodes.first.flatMap { updated?($0) }
    }
    
    func focus() {
        self.inputNodes.first?.isFocused = true
    }
    
    @objc private func skipActionPressed() {
        self.skipAction()
    }
    
    @objc private func changeEmailActionPressed() {
        self.changeEmailAction()
    }
    
    @objc private func resendCodeActionPressed() {
        self.resendCodeAction()
    }
    
    override func didLoad() {
        super.didLoad()
        
        self.scrollNode.view.keyboardDismissMode = .none
        self.scrollNode.view.delaysContentTouches = false
        self.scrollNode.view.canCancelContentTouches = true
        //self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
        self.scrollNode.view.alwaysBounceVertical = false
        self.scrollNode.view.showsVerticalScrollIndicator = false
        self.scrollNode.view.showsHorizontalScrollIndicator = false
        if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
            self.scrollNode.view.contentInsetAdjustmentBehavior = .never
        }
        self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
    }
    
    func onAction(success: Bool) {
        switch self.mode {
        case .emailAddress:
            if !success {
                self.inputNodes.first?.layer.addShakeAnimation()
                HapticFeedback().error()
                
                if self.skipIsHiddenUntilError {
                    self.skipIsHiddenUntilError = false
                    self.skipActionTitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                    self.skipActionTitleNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
                    self.skipActionTitleNode.isHidden = false
                    self.skipActionButtonNode.isHidden = false
                }
            }
        case .rememberPassword:
            if !success {
                self.skipActionTitleNode.isHidden = false
                self.skipActionButtonNode.isHidden = false
                
                let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
                transition.updateAlpha(node: self.buttonNode, alpha: 0.0)
                transition.updateAlpha(node: self.skipActionTitleNode, alpha: 1.0)
                
                if let snapshotView = self.textNode.view.snapshotContentTree() {
                    snapshotView.frame = self.textNode.view.frame
                    self.textNode.view.superview?.addSubview(snapshotView)
                    
                    snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion:  { [weak snapshotView] _ in
                        snapshotView?.removeFromSuperview()
                    })
                    
                    self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                }
                
                self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.TwoFactorRemember_WrongPassword, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemDestructiveColor)
                self.inputNodes.first?.isFailed = true
                
                if let (layout, navigationHeight) = self.validLayout {
                    self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
                }
            }
        default:
            break
        }
    }
    
    func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
        self.validLayout = (layout, navigationHeight)
        
        let contentAreaSize = layout.size
        let availableAreaSize = CGSize(width: layout.size.width, height: layout.size.height - layout.insets(options: [.input]).bottom)
        
        let sideInset: CGFloat = 32.0
        let buttonSideInset: CGFloat = 48.0
        let iconSpacing: CGFloat
        switch self.mode {
        case .passwordHint, .emailConfirmation:
            iconSpacing = 6.0
        default:
            iconSpacing = 2.0
        }
        let titleSpacing: CGFloat = 19.0
        let titleInputSpacing: CGFloat = 26.0
        let textSpacing: CGFloat = 30.0
        let buttonHeight: CGFloat = 50.0
        let buttonSpacing: CGFloat = 20.0
        let rowSpacing: CGFloat = 20.0
        
        transition.updateFrame(node: self.navigationBackgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: contentAreaSize.width, height: navigationHeight)))
        transition.updateFrame(node: self.navigationSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: contentAreaSize.width, height: UIScreenPixel)))
        
        transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: contentAreaSize))
        
        let iconSize: CGSize
        if let _ = self.animatedStickerNode {
            iconSize = CGSize(width: 136.0, height: 136.0)
        } else if let monkeyNode = self.monkeyNode {
            iconSize = monkeyNode.intrinsicSize
        } else {
            iconSize = CGSize(width: 100.0, height: 100.0)
        }
        
        let titleSize = self.titleNode.updateLayout(CGSize(width: contentAreaSize.width - sideInset * 2.0, height: contentAreaSize.height))
        let textSize = self.textNode.updateLayout(CGSize(width: contentAreaSize.width - sideInset * 2.0, height: contentAreaSize.height))
        let skipActionSize = self.skipActionTitleNode.updateLayout(CGSize(width: contentAreaSize.width - sideInset * 2.0, height: contentAreaSize.height))
        let changeEmailActionSize = self.changeEmailActionTitleNode.updateLayout(CGSize(width: contentAreaSize.width - sideInset * 2.0, height: contentAreaSize.height))
        let resendCodeActionSize = self.resendCodeActionTitleNode.updateLayout(CGSize(width: contentAreaSize.width - sideInset * 2.0, height: contentAreaSize.height))
        
        var calculatedContentHeight = iconSize.height + iconSpacing + titleSize.height
        if textSize.width.isZero {
            calculatedContentHeight += titleInputSpacing
        } else {
            calculatedContentHeight += titleSpacing + textSize.height + textSpacing
        }
        for i in 0 ..< self.inputNodes.count {
            if i != 0 {
                calculatedContentHeight += rowSpacing
            }
            calculatedContentHeight += 50.0
        }
        calculatedContentHeight += buttonHeight + buttonSpacing
        
        var contentHeight: CGFloat = 0.0
        
        let insets = layout.insets(options: [.input])
        let areaHeight = layout.size.height - insets.top - insets.bottom
        let contentVerticalOrigin = max(layout.statusBarHeight ?? 0.0, floor((areaHeight - calculatedContentHeight) / 2.0))
        
        let iconFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize)
        if let animatedStickerNode = self.animatedStickerNode {
            animatedStickerNode.updateLayout(size: iconFrame.size)
            transition.updateFrame(node: animatedStickerNode, frame: iconFrame)
        } else if let monkeyNode = self.monkeyNode {
            transition.updateFrame(node: monkeyNode, frame: iconFrame)
        }
        let titleFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize)
        transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
        let textFrame: CGRect
        if textSize.width.isZero {
            textFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.width - textSize.width) / 2.0), y: titleFrame.maxY), size: textSize)
        } else {
            textFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
        }
        transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
        
        contentHeight = textFrame.maxY
        if textSize.width.isZero {
            contentHeight += titleInputSpacing
        } else {
            contentHeight += textSpacing
        }
        
        let rowWidth = contentAreaSize.width - buttonSideInset * 2.0
        
        for i in 0 ..< self.inputNodes.count {
            let inputNode = self.inputNodes[i]
            if i != 0 {
                contentHeight += rowSpacing
            }
            let inputNodeSize = CGSize(width: rowWidth, height: 50.0)
            transition.updateFrame(node: inputNode, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: contentHeight), size: inputNodeSize))
            inputNode.updateLayout(size: inputNodeSize, transition: transition)
            contentHeight += inputNodeSize.height
        }
        
        let minimalBottomInset: CGFloat = 74.0
        let buttonBottomInset = layout.intrinsicInsets.bottom + minimalBottomInset
        let bottomInset = layout.intrinsicInsets.bottom + buttonSpacing
        
        let buttonWidth = contentAreaSize.width - buttonSideInset * 2.0
        
        let maxButtonY = min(areaHeight - buttonSpacing, layout.size.height - buttonBottomInset) - buttonHeight * 2.0
        
        let buttonFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.width - buttonWidth) / 2.0), y: max(contentHeight + buttonSpacing, maxButtonY)), size: CGSize(width: buttonWidth, height: buttonHeight))
        transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
        let _ = self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)

        var skipButtonFrame = buttonFrame
        if case .rememberPassword = self.mode {
        } else if !self.buttonNode.isHidden {
            skipButtonFrame.origin.y += skipButtonFrame.height
        }

        transition.updateFrame(node: self.skipActionButtonNode, frame: skipButtonFrame)
        transition.updateFrame(node: self.skipActionTitleNode, frame: CGRect(origin: CGPoint(x: skipButtonFrame.minX + floor((skipButtonFrame.width - skipActionSize.width) / 2.0), y: skipButtonFrame.minY + floor((skipButtonFrame.height - skipActionSize.height) / 2.0)), size: skipActionSize))
        
        let changeEmailActionFrame: CGRect
        let changeEmailActionButtonFrame: CGRect
        let resendCodeActionFrame: CGRect
        let resendCodeActionButtonFrame: CGRect
        if changeEmailActionSize.width + resendCodeActionSize.width > layout.size.width - buttonFrame.minX * 2.0 - 32.0 {
            changeEmailActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.minX, y: buttonFrame.minY), size: CGSize(width: buttonFrame.width, height: buttonFrame.height))
            changeEmailActionFrame = CGRect(origin: CGPoint(x: changeEmailActionButtonFrame.minX + floor((changeEmailActionButtonFrame.width - changeEmailActionSize.width) / 2.0), y: changeEmailActionButtonFrame.minY + floor((changeEmailActionButtonFrame.height - changeEmailActionSize.height) / 2.0)), size: changeEmailActionSize)
            resendCodeActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.minX, y: buttonFrame.maxY), size: CGSize(width: buttonFrame.width, height: buttonFrame.height))
            resendCodeActionFrame = CGRect(origin: CGPoint(x: resendCodeActionButtonFrame.minX + floor((resendCodeActionButtonFrame.width - resendCodeActionSize.width) / 2.0), y: resendCodeActionButtonFrame.minY + floor((resendCodeActionButtonFrame.height - resendCodeActionSize.height) / 2.0)), size: resendCodeActionSize)
        } else {
            changeEmailActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.minX, y: buttonFrame.minY), size: CGSize(width: floor(buttonFrame.width / 2.0), height: buttonFrame.height))
            changeEmailActionFrame = CGRect(origin: CGPoint(x: changeEmailActionButtonFrame.minX, y: changeEmailActionButtonFrame.minY + floor((changeEmailActionButtonFrame.height - changeEmailActionSize.height) / 2.0)), size: changeEmailActionSize)
            resendCodeActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.maxX - floor(buttonFrame.width / 2.0), y: buttonFrame.minY), size: CGSize(width: floor(buttonFrame.width / 2.0), height: buttonFrame.height))
            resendCodeActionFrame = CGRect(origin: CGPoint(x: resendCodeActionButtonFrame.maxX - resendCodeActionSize.width, y: resendCodeActionButtonFrame.minY + floor((resendCodeActionButtonFrame.height - resendCodeActionSize.height) / 2.0)), size: resendCodeActionSize)
        }
        
        transition.updateFrame(node: self.changeEmailActionButtonNode, frame: changeEmailActionButtonFrame)
        transition.updateFrame(node: self.resendCodeActionButtonNode, frame: resendCodeActionButtonFrame)
        
        transition.updateFrame(node: self.changeEmailActionTitleNode, frame: changeEmailActionFrame)
        transition.updateFrame(node: self.resendCodeActionTitleNode, frame: resendCodeActionFrame)
        
        transition.animateView {
            self.scrollNode.view.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.insets(options: [.input]).bottom, right: 0.0)
            self.scrollNode.view.contentSize = CGSize(width: contentAreaSize.width, height: max(availableAreaSize.height, skipButtonFrame.maxY + bottomInset))
        }
    }
}