/*
 * Copyright 2019 Jeremy Jamet / Kunzisoft.
 *     
 * This file is part of KeePassDX.
 *
 *  KeePassDX is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  KeePassDX is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with KeePassDX.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.kunzisoft.keepass.activities

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.AdapterView
import android.widget.ProgressBar
import android.widget.Spinner
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.timepicker.MaterialTimePicker
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.ColorPickerDialogFragment
import com.kunzisoft.keepass.activities.dialogs.EntryCustomFieldDialogFragment
import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment
import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE
import com.kunzisoft.keepass.activities.dialogs.ReplaceFileDialogFragment
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
import com.kunzisoft.keepass.activities.fragments.EntryEditFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.adapters.TemplatesSelectorAdapter
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildSpecialModeResponseAndSetResult
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveRegisterInfo
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType
import com.kunzisoft.keepass.credentialprovider.UserVerificationData
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasswordHelper.buildPasswordResponseAndSetResult
import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.DataTime
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.services.AttachmentFileNotificationService
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getNewEntry
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.TimeUtil.datePickerToDataDate
import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import com.kunzisoft.keepass.utils.clear
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.view.ToolbarAction
import com.kunzisoft.keepass.view.WindowInsetPosition
import com.kunzisoft.keepass.view.applyWindowInsets
import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.setTransparentNavigationBar
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.view.showError
import com.kunzisoft.keepass.view.updateButtonPaddingEnd
import com.kunzisoft.keepass.view.updateButtonPaddingStart
import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
import kotlinx.coroutines.launch
import java.util.EnumSet
import java.util.UUID

class EntryEditActivity : DatabaseLockActivity(),
        EntryCustomFieldDialogFragment.EntryCustomFieldListener,
        SetOTPDialogFragment.CreateOtpListener,
        FileTooBigDialogFragment.ActionChooseListener,
        ReplaceFileDialogFragment.ActionChooseListener {

    // Views
    private var container: View? = null
    private var coordinatorLayout: CoordinatorLayout? = null
    private var scrollView: NestedScrollView? = null
    private var templateSelectorSpinner: Spinner? = null
    private var entryEditAddToolBar: ToolbarAction? = null
    private var validateButton: View? = null
    private var lockView: View? = null
    private var loadingView: ProgressBar? = null

    private val mEntryEditViewModel: EntryEditViewModel by viewModels()
    private var mTemplatesSelectorAdapter: TemplatesSelectorAdapter? = null

    private val mColorPickerViewModel: ColorPickerViewModel by viewModels()
    private val mUserVerificationViewModel: UserVerificationViewModel by viewModels()

    // To manage attachments
    private var mExternalFileHelper: ExternalFileHelper? = null
    private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
    // Education
    private var mEntryEditActivityEducation = EntryEditActivityEducation(this)

    private var mIconSelectionActivityResultLauncher = IconPickerActivity.registerIconSelectionForResult(this) { icon ->
        mEntryEditViewModel.selectIcon(icon)
    }

    private var mKeyGeneratorResultLauncher = KeyGeneratorActivity.registerForGeneratedKeyResult(this) { keyGenerated ->
        keyGenerated?.let {
            mEntryEditViewModel.passwordField?.let {
                it.protectedValue = ProtectedString(
                    it.protectedValue.isProtected,
                    keyGenerated
                )
                mEntryEditViewModel.selectPassword(it)
                keyGenerated.clear()
            }
        }
        mEntryEditViewModel.passwordField = null
        Handler(Looper.getMainLooper()).post {
            performedNextEducation()
        }
    }

    override fun manageDatabaseInfo(): Boolean = true

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_entry_edit)

        // Bottom Bar
        entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar)
        container = findViewById(R.id.activity_entry_edit_container)
        coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout)
        scrollView = findViewById(R.id.entry_edit_scroll)
        scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
        templateSelectorSpinner = findViewById(R.id.entry_edit_template_selector)
        lockView = findViewById(R.id.lock_button)
        validateButton = findViewById(R.id.entry_edit_validate)
        loadingView = findViewById(R.id.loading)

        setSupportActionBar(entryEditAddToolBar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setDisplayShowHomeEnabled(true)
        supportActionBar?.setDisplayShowTitleEnabled(false)

        // To apply fit window with transparency
        setTransparentNavigationBar(applyToStatusBar = true) {
            container?.applyWindowInsets(EnumSet.of(
                WindowInsetPosition.TOP_MARGINS,
                WindowInsetPosition.BOTTOM_MARGINS,
                WindowInsetPosition.START_MARGINS,
                WindowInsetPosition.END_MARGINS,
            ))
        }

        stopService(Intent(this, KeyboardEntryNotificationService::class.java))

        // Entry is retrieve, it's an entry to update
        var entryId: NodeId<UUID>? = null
        intent.getParcelableExtraCompat<NodeId<UUID>>(KEY_ENTRY)?.let { entryToUpdate ->
            intent.removeExtra(KEY_ENTRY)
            entryId = entryToUpdate
        }

        // Parent is retrieve, it's a new entry to create
        var parentId: NodeId<*>? = null
        intent.getParcelableExtraCompat<NodeId<*>>(KEY_PARENT)?.let { parent ->
            intent.removeExtra(KEY_PARENT)
            parentId = parent
        }

        mEntryEditViewModel.loadTemplateEntry(
            mDatabase,
            entryId,
            parentId,
            intent.retrieveRegisterInfo()
                ?: intent.retrieveSearchInfo()?.toRegisterInfo()
        )

        // To retrieve attachment
        mExternalFileHelper = ExternalFileHelper(this)
        mExternalFileHelper?.buildOpenDocument { uri ->
            uri?.let { attachmentToUploadUri ->
                attachmentToUploadUri.getDocumentFile(this)?.also { documentFile ->
                    documentFile.name?.let { fileName ->
                        if (documentFile.length() > MAX_WARNING_BINARY_FILE) {
                            FileTooBigDialogFragment.build(attachmentToUploadUri, fileName)
                                .show(supportFragmentManager, "fileTooBigFragment")
                        } else {
                            mEntryEditViewModel.buildNewAttachment(attachmentToUploadUri, fileName)
                        }
                    }
                }
            }
        }

        mAttachmentFileBinderManager = AttachmentFileBinderManager(this)

        // Lock button
        lockView?.setOnClickListener { lockAndExit() }
        // Save button
        validateButton?.setOnClickListener { validateEntry() }

        mEntryEditViewModel.templatesEntry.observe(this) { templatesEntry ->
            if (templatesEntry != null) {
                // Change template dynamically
                templatesEntry.templates.let { templates ->
                    templateSelectorSpinner?.apply {
                        // Build template selector
                        if (templates.isNotEmpty()) {
                            mTemplatesSelectorAdapter = TemplatesSelectorAdapter(
                                this@EntryEditActivity,
                                templates
                            ).apply {
                                iconDrawableFactory = mDatabase?.iconDrawableFactory
                            }
                            adapter = mTemplatesSelectorAdapter
                            val selectedTemplate = templatesEntry.template
                                ?: templatesEntry.defaultTemplate
                            setSelection(templates.indexOf(selectedTemplate))
                            onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
                                override fun onItemSelected(
                                    parent: AdapterView<*>?,
                                    view: View?,
                                    position: Int,
                                    id: Long
                                ) {
                                    mEntryEditViewModel.changeTemplate(templates[position])
                                }

                                override fun onNothingSelected(parent: AdapterView<*>?) {}
                            }
                        } else {
                            visibility = View.GONE
                        }
                    }
                }
                loadingView?.hideByFading()
            } else {
                finish()
            }
            invalidateOptionsMenu()
        }

        // View model listeners
        mEntryEditViewModel.requestIconSelection.observe(this) { iconImage ->
            IconPickerActivity.launch(this@EntryEditActivity, iconImage, mIconSelectionActivityResultLauncher)
        }

        mEntryEditViewModel.requestColorSelection.observe(this) { color ->
            ColorPickerDialogFragment.newInstance(color)
                .show(supportFragmentManager, "ColorPickerFragment")
        }

        mColorPickerViewModel.colorPicked.observe(this) { color ->
            mEntryEditViewModel.selectColor(color)
        }

        mEntryEditViewModel.requestDateTimeSelection.observe(this) { dateInstant ->
            if (dateInstant.type == DateInstant.Type.TIME) {
                // Launch the time picker
                MaterialTimePicker.Builder().build().apply {
                    addOnPositiveButtonClickListener {
                        mEntryEditViewModel.selectTime(DataTime(this.hour, this.minute))
                    }
                    show(supportFragmentManager, "TimePickerFragment")
                }
            } else {
                // Launch the date picker
                MaterialDatePicker.Builder.datePicker().build().apply {
                    addOnPositiveButtonClickListener {
                        mEntryEditViewModel.selectDate(datePickerToDataDate(it))
                    }
                    show(supportFragmentManager, "DatePickerFragment")
                }
            }
        }

        mEntryEditViewModel.requestPasswordSelection.observe(this) {
            KeyGeneratorActivity.launch(this, mKeyGeneratorResultLauncher)
        }

        mEntryEditViewModel.requestCustomFieldEdition.observe(this) { field ->
            editCustomField(field)
        }

        mEntryEditViewModel.onCustomFieldError.observe(this) {
            coordinatorLayout?.let {
                Snackbar.make(it, R.string.error_field_name_already_exists, Snackbar.LENGTH_LONG)
                        .asError()
                        .show()
            }
        }

        mEntryEditViewModel.onStartUploadAttachment.observe(this) {
            // Start uploading in service
            mAttachmentFileBinderManager?.startUploadAttachment(it.attachmentToUploadUri, it.attachment)
        }

        mEntryEditViewModel.onAttachmentAction.observe(this) { attachmentState ->
            when (attachmentState?.downloadState) {
                AttachmentState.ERROR -> {
                    coordinatorLayout?.let {
                        Snackbar.make(it, R.string.error_file_not_create, Snackbar.LENGTH_LONG).asError().show()
                    }
                }
                else -> {}
            }
        }

        mEntryEditViewModel.onBinaryPreviewLoaded.observe(this) {
            // Scroll to the attachment position
            when (it.entryAttachmentState.downloadState) {
                AttachmentState.START,
                AttachmentState.COMPLETE -> {
                    scrollView?.smoothScrollTo(0, it.viewPosition.toInt())
                }
                else -> {}
            }
        }

        mEntryEditViewModel.attachmentDeleted.observe(this) {
            mAttachmentFileBinderManager?.removeBinaryAttachment(it)
        }

        // Build new entry from the entry info retrieved
        mEntryEditViewModel.onEntrySaved.observe(this) { entrySave ->
            // Open a progress dialog and save entry
            entrySave.parent?.let { parent ->
                createEntry(entrySave.newEntry, parent)
            } ?: run {
                updateEntry(entrySave.oldEntry, entrySave.newEntry)
            }
        }

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                mEntryEditViewModel.entryEditState.collect { entryEditState ->
                    when (entryEditState) {
                        is EntryEditViewModel.EntryEditState.Loading -> {}
                        is EntryEditViewModel.EntryEditState.ShowOverwriteMessage -> {
                            AlertDialog.Builder(this@EntryEditActivity)
                                .setTitle(R.string.warning_overwrite_data_title)
                                .setMessage(R.string.warning_overwrite_data_description)
                                .setNegativeButton(android.R.string.cancel) { _, _ ->
                                    mEntryEditViewModel.backPressedAlreadyApproved = true
                                    onCancelSpecialMode()
                                }
                                .setPositiveButton(android.R.string.ok) { _, _ -> }
                                .create().show()
                            mEntryEditViewModel.actionPerformed()
                        }
                        is EntryEditViewModel.EntryEditState.OnChangeFieldProtectionRequested -> {
                            mDatabase?.let { database ->
                                val fieldProtection = entryEditState.fieldProtection
                                if (mDatabaseAllowUserVerification) {
                                    if (fieldProtection.isCurrentlyProtected) {
                                        checkUserVerification(
                                            userVerificationViewModel = mUserVerificationViewModel,
                                            dataToVerify = UserVerificationData(
                                                actionType = UserVerificationActionType.SHOW_PROTECTED_FIELD,
                                                database = database,
                                                fieldProtection = fieldProtection
                                            )
                                        )
                                        mEntryEditViewModel.actionPerformed()
                                    } else {
                                        mEntryEditViewModel.updateFieldProtection(
                                            fieldProtection = fieldProtection,
                                            value = true
                                        )
                                    }
                                } else {
                                    // Toggle field protection directly without user verification
                                    mEntryEditViewModel.updateFieldProtection(
                                        fieldProtection = fieldProtection,
                                        value = !fieldProtection.isCurrentlyProtected
                                    )
                                }
                            }
                        }
                        is EntryEditViewModel.EntryEditState.OnFieldProtectionUpdated -> {}
                        is EntryEditViewModel.EntryEditState.CloseEntry -> {
                            when(entryEditState.closeType) {
                                EntryEditViewModel.CloseType.DATABASE_BACK_PRESSED -> {
                                    super@EntryEditActivity.onDatabaseBackPressed()
                                }
                                EntryEditViewModel.CloseType.CANCEL_SPECIAL_MODE -> {
                                    super@EntryEditActivity.onCancelSpecialMode()
                                    finish()
                                }
                            }
                        }
                        is EntryEditViewModel.EntryEditState.RetrieveEntryInfoForClosing -> {
                            // Entry info retrieved in dedicated fragment
                        }
                        is EntryEditViewModel.EntryEditState.AskToDiscardChanges -> {
                            AlertDialog.Builder(this@EntryEditActivity)
                                .setMessage(R.string.discard_changes)
                                .setNegativeButton(android.R.string.cancel) { _, _ -> }
                                .setPositiveButton(R.string.discard) { _, _ ->
                                    mAttachmentFileBinderManager?.stopUploadAllAttachments()
                                    mEntryEditViewModel.approveDiscardChanges(entryEditState.closeType)
                                }.create().show()
                            mEntryEditViewModel.actionPerformed()
                        }
                    }
                }
            }
        }
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                mUserVerificationViewModel.userVerificationState.collect { uVState ->
                    when (uVState) {
                        is UserVerificationViewModel.UVState.Loading -> {}
                        is UserVerificationViewModel.UVState.OnUserVerificationCanceled -> {
                            coordinatorLayout?.showError(uVState.error)
                            mUserVerificationViewModel.onUserVerificationReceived()
                        }
                        is UserVerificationViewModel.UVState.OnUserVerificationSucceeded -> {
                            when (uVState.dataToVerify.actionType) {
                                UserVerificationActionType.SHOW_PROTECTED_FIELD -> {
                                    uVState.dataToVerify.fieldProtection?.let { fieldProtection ->
                                        mEntryEditViewModel.updateFieldProtection(
                                            fieldProtection = fieldProtection,
                                            value = false
                                        )
                                    }
                                }
                                else -> {}
                            }
                            mUserVerificationViewModel.onUserVerificationReceived()
                        }
                    }
                }
            }
        }
    }

    override fun viewToInvalidateTimeout(): View? = coordinatorLayout

    override fun finishActivityIfReloadRequested(): Boolean = true

    override fun onDatabaseRetrieved(database: ContextualDatabase) {
        super.onDatabaseRetrieved(database)
        mEntryEditViewModel.loadTemplateEntry(database)
        mTemplatesSelectorAdapter?.apply {
            iconDrawableFactory = database.iconDrawableFactory
            notifyDataSetChanged()
        }
    }

    override fun onDatabaseActionFinished(
        database: ContextualDatabase,
        actionTask: String,
        result: ActionRunnable.Result
    ) {
        super.onDatabaseActionFinished(database, actionTask, result)
        mEntryEditViewModel.unlockAction()
        when (actionTask) {
            ACTION_DATABASE_CREATE_ENTRY_TASK,
            ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
                try {
                    if (result.isSuccess) {
                        result.data?.getNewEntry(database)?.let { entry ->
                            EntrySelectionHelper.doSpecialAction(
                                intent = intent,
                                defaultAction = {
                                    // Finish naturally
                                    finishForEntryResult(entry)
                                },
                                searchAction = {
                                    // Nothing when search retrieved
                                },
                                selectionAction = { _, typeMode, _ ->
                                    when(typeMode) {
                                        TypeMode.DEFAULT -> {}
                                        TypeMode.MAGIKEYBOARD ->
                                            entryValidatedForKeyboardSelection(database, entry)
                                        TypeMode.AUTOFILL ->
                                            entryValidatedForAutofill(database, entry)
                                        TypeMode.PASSWORD ->
                                            entryValidatedForPassword(database, entry)
                                        TypeMode.PASSKEY ->
                                            entryValidatedForPasskey(database, entry)
                                    }
                                },
                                registrationAction = { _, typeMode, _ ->
                                    when(typeMode) {
                                        TypeMode.DEFAULT ->
                                            entryValidatedForSave(entry)
                                        TypeMode.MAGIKEYBOARD -> {}
                                        TypeMode.AUTOFILL ->
                                            entryValidatedForAutofill(database, entry)
                                        TypeMode.PASSWORD ->
                                            entryValidatedForPassword(database, entry)
                                        TypeMode.PASSKEY ->
                                            entryValidatedForPasskey(database, entry)
                                    }
                                }
                            )
                        }
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "Unable to retrieve entry after database action", e)
                }
            }
        }
        coordinatorLayout?.showActionErrorIfNeeded(result)
    }

    private fun entryValidatedForSave(entry: Entry) {
        onValidateSpecialMode()
        finishForEntryResult(entry)
    }

    private fun entryValidatedForKeyboardSelection(database: ContextualDatabase, entry: Entry) {
        // Build Magikeyboard response with the entry selected
        this.buildSpecialModeResponseAndSetResult(
            entryInfo = entry.getEntryInfo(database),
            extras = buildEntryResult(entry)
        )
        onValidateSpecialMode()
    }

    private fun entryValidatedForAutofill(database: ContextualDatabase, entry: Entry) {
        // Build Autofill response with the entry selected
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            this.buildSpecialModeResponseAndSetResult(
                entryInfo = entry.getEntryInfo(database),
                extras = buildEntryResult(entry)
            )
        }
        onValidateSpecialMode()
    }

    private fun entryValidatedForPassword(database: ContextualDatabase, entry: Entry) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            this.buildPasswordResponseAndSetResult(
                entryInfo = entry.getEntryInfo(database),
                extras = buildEntryResult(entry)
            )
        }
        onValidateSpecialMode()
    }

    private fun entryValidatedForPasskey(database: ContextualDatabase, entry: Entry) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            this.buildPasskeyResponseAndSetResult(
                entryInfo = entry.getEntryInfo(database),
                extras = buildEntryResult(entry) // To update the previous screen
            )
        }
        onValidateSpecialMode()
    }

    override fun onResume() {
        super.onResume()

        lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
            View.VISIBLE
        } else {
            View.GONE
        }

        // Padding if lock button visible
        entryEditAddToolBar?.updateButtonPaddingStart()
        entryEditAddToolBar?.updateButtonPaddingEnd()

        mAttachmentFileBinderManager?.apply {
            registerProgressTask()
            onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
                override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
                    mEntryEditViewModel.onAttachmentAction(entryAttachmentState)
                }
            }
        }

        // Keep the screen on
        if (PreferencesUtil.isKeepScreenOnEnabled(this)) {
            window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        }
    }

    override fun onPause() {
        mAttachmentFileBinderManager?.unregisterProgressTask()

        super.onPause()
    }

    /**
     * Add a new customized field
     */
    private fun addNewCustomField() {
        EntryCustomFieldDialogFragment.getInstance().show(supportFragmentManager, "customFieldDialog")
    }

    private fun editCustomField(field: Field) {
        EntryCustomFieldDialogFragment.getInstance(field).show(supportFragmentManager, "customFieldDialog")
    }

    override fun onNewCustomFieldApproved(newField: Field) {
        mEntryEditViewModel.addCustomField(newField)
    }

    override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
        mEntryEditViewModel.editCustomField(oldField, newField)
    }

    override fun onDeleteCustomFieldApproved(oldField: Field) {
        mEntryEditViewModel.removeCustomField(oldField)
    }

    /**
     * Add a new attachment
     */
    private fun addNewAttachment() {
        mExternalFileHelper?.openDocument()
    }

    override fun onValidateUploadFileTooBig(attachmentToUploadUri: Uri?, fileName: String?) {
        if (attachmentToUploadUri != null && fileName != null) {
            mEntryEditViewModel.buildNewAttachment(attachmentToUploadUri, fileName)
        }
    }

    override fun onValidateReplaceFile(attachmentToUploadUri: Uri?, attachment: Attachment?) {
        if (attachmentToUploadUri != null && attachment != null) {
            mEntryEditViewModel.startUploadAttachment(attachmentToUploadUri, attachment)
        }
    }

    /**
     * Set up OTP (HOTP or TOTP) and add it as extra field
     */
    private fun setupOtp() {
        mEntryEditViewModel.setupOtp()
    }

    override fun onOtpCreated(otpElement: OtpElement) {
        mEntryEditViewModel.createOtp(otpElement)
    }

    /**
     * Validate the new entry or update an existing entry in the database
     */
    private fun validateEntry() {
        mAttachmentFileBinderManager?.stopUploadAllAttachments()
        mEntryEditViewModel.requestEntryInfoUpdate()
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        if (mEntryEditViewModel.entryLoaded) {
            menuInflater.inflate(R.menu.entry_edit, menu)
            Handler(Looper.getMainLooper()).post {
                performedNextEducation()
            }
        }
        return true
    }

    override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
        menu?.findItem(R.id.menu_add_field)?.apply {
            isEnabled = mEntryEditViewModel.allowCustomFields
            isVisible = isEnabled
        }
        menu?.findItem(R.id.menu_add_attachment)?.apply {
            isEnabled = !mEntryEditViewModel.isTemplate
            isVisible = isEnabled
        }
        menu?.findItem(R.id.menu_add_otp)?.apply {
            isEnabled = mEntryEditViewModel.allowOTP && !mEntryEditViewModel.isTemplate
            isVisible = isEnabled
        }
        return super.onPrepareOptionsMenu(menu)
    }

    private fun performedNextEducation() {

        val entryEditFragment = supportFragmentManager.findFragmentById(R.id.entry_edit_content)
                as? EntryEditFragment?
        val generatePasswordView = entryEditFragment?.getActionImageView()
        val generatePasswordEductionPerformed = generatePasswordView != null
                && mEntryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
            generatePasswordView,
            {
                entryEditFragment.launchGeneratePasswordEductionAction()
            },
            {
                performedNextEducation()
            }
        )

        if (!generatePasswordEductionPerformed) {
            val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
            val addNewFieldEducationPerformed = mEntryEditViewModel.allowCustomFields
                    && addNewFieldView != null
                    && addNewFieldView.isVisible
                    && mEntryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
                    addNewFieldView,
                    {
                        addNewCustomField()
                    },
                    {
                        performedNextEducation()
                    }
            )
            if (!addNewFieldEducationPerformed) {
                val attachmentView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_attachment)
                val addAttachmentEducationPerformed = attachmentView != null
                        && attachmentView.isVisible
                        && mEntryEditActivityEducation.checkAndPerformedAttachmentEducation(
                        attachmentView,
                        {
                            addNewAttachment()
                        },
                        {
                            performedNextEducation()
                        }
                )
                if (!addAttachmentEducationPerformed) {
                    val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
                    val validateEntryEducationPerformed = setupOtpView != null
                            && setupOtpView.isVisible
                            && mEntryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
                            setupOtpView,
                            {
                                setupOtp()
                            },
                            {
                                performedNextEducation()
                            }
                    )
                    if (!validateEntryEducationPerformed) {
                        val entryValidateView = validateButton
                        mEntryEditViewModel.allowCustomFields
                                && entryValidateView != null
                                && entryValidateView.isVisible
                                && mEntryEditActivityEducation.checkAndPerformedValidateEntryEducation(
                                entryValidateView,
                                {
                                    validateEntry()
                                }
                        )
                    }
                }
            }
        }
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.menu_add_field -> {
                addNewCustomField()
                return true
            }
            R.id.menu_add_attachment -> {
                addNewAttachment()
                return true
            }
            R.id.menu_add_otp -> {
                setupOtp()
                return true
            }
            android.R.id.home -> {
                onDatabaseBackPressed()
            }
        }

        return super.onOptionsItemSelected(item)
    }

    override fun onDatabaseBackPressed() {
        mEntryEditViewModel.askToClose(
            EntryEditViewModel.CloseType.DATABASE_BACK_PRESSED
        )
    }

    override fun onCancelSpecialMode() {
        mEntryEditViewModel.askToClose(
            EntryEditViewModel.CloseType.CANCEL_SPECIAL_MODE
        )
    }

    private fun buildEntryResult(entry: Entry): Bundle {
        return Bundle().apply {
            putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry.nodeId)
        }
    }

    private fun finishForEntryResult(entry: Entry) {
        // Assign entry callback as a result
        try {
            val bundle = buildEntryResult(entry)
            val intentEntry = Intent()
            intentEntry.putExtras(bundle)
            setResult(RESULT_OK, intentEntry)
            super.finish()
        } catch (e: Exception) {
            // Exception when parcelable can't be done
            Log.e(TAG, "Cant add entry as result", e)
        }
    }

    enum class RegistrationType {
        UPDATE, CREATE
    }

    companion object {

        private val TAG = EntryEditActivity::class.java.name

        // Keys for current Activity
        const val KEY_ENTRY = "entry"
        const val KEY_PARENT = "parent"
        const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY"

        fun registerForEntryResult(
            activity: FragmentActivity,
            entryAddedOrUpdatedListener: (NodeId<UUID>?) -> Unit
        ): ActivityResultLauncher<Intent> {
            return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                if (result.resultCode == RESULT_OK) {
                    entryAddedOrUpdatedListener.invoke(
                        result.data?.getParcelableExtraCompat(ADD_OR_UPDATE_ENTRY_KEY)
                    )
                } else {
                    entryAddedOrUpdatedListener.invoke(null)
                }
            }
        }

        /**
         * Launch EntryEditActivity to update an existing entry or to add a new entry in an existing group
         */
        fun launch(
            activity: Activity,
            database: ContextualDatabase,
            registrationType: RegistrationType,
            nodeId: NodeId<*>,
            activityResultLauncher: ActivityResultLauncher<Intent>
        ) {
            if (database.loaded && !database.isReadOnly) {
                if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
                    val intent = Intent(activity, EntryEditActivity::class.java)
                    when (registrationType) {
                        RegistrationType.UPDATE -> intent.putExtra(KEY_ENTRY, nodeId)
                        RegistrationType.CREATE -> intent.putExtra(KEY_PARENT, nodeId)
                    }
                    activityResultLauncher.launch(intent)
                }
            }
        }

        /**
         * Launch EntryEditActivity to add a new entry in special selection
         */
        fun launchForSelection(
            context: Context,
            database: ContextualDatabase,
            typeMode: TypeMode,
            groupId: NodeId<*>,
            searchInfo: SearchInfo? = null,
            activityResultLauncher: ActivityResultLauncher<Intent>? = null,
        ) {
            if (database.loaded && !database.isReadOnly) {
                if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
                    val intent = Intent(context, EntryEditActivity::class.java)
                    intent.putExtra(KEY_PARENT, groupId)
                    EntrySelectionHelper.startActivityForSelectionModeResult(
                        context = context,
                        intent = intent,
                        typeMode = typeMode,
                        searchInfo = searchInfo,
                        activityResultLauncher = activityResultLauncher
                    )
                }
            }
        }

        /**
         * Launch EntryEditActivity to update an updated entry or register a new entry (from autofill)
         */
        fun launchForRegistration(
            context: Context,
            database: ContextualDatabase,
            nodeId: NodeId<*>,
            registerInfo: RegisterInfo? = null,
            typeMode: TypeMode,
            registrationType: RegistrationType,
            activityResultLauncher: ActivityResultLauncher<Intent>? = null,
        ) {
            if (database.loaded && !database.isReadOnly) {
                if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
                    val intent = Intent(context, EntryEditActivity::class.java)
                    when (registrationType) {
                        RegistrationType.UPDATE -> intent.putExtra(KEY_ENTRY, nodeId)
                        RegistrationType.CREATE -> intent.putExtra(KEY_PARENT, nodeId)
                    }
                    EntrySelectionHelper.startActivityForRegistrationModeResult(
                        context,
                        activityResultLauncher,
                        intent,
                        registerInfo,
                        typeMode
                    )
                }
            }
        }
    }
}
