/*
 * Copyright (c) 2022 Proton AG
 *
 * This file is part of Proton Mail.
 *
 * Proton Mail 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.
 *
 * Proton Mail 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 Proton Mail. If not, see https://www.gnu.org/licenses/.
 */
package ch.protonmail.android.activities.messageDetails

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.os.Build
import android.text.method.LinkMovementMethod
import android.text.util.Linkify
import android.view.Gravity
import android.view.LayoutInflater
import android.view.ScaleGestureDetector
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup
import android.webkit.WebSettings
import android.webkit.WebView
import android.widget.Button
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewFeature
import ch.protonmail.android.R
import ch.protonmail.android.activities.messageDetails.attachments.MessageDetailsAttachmentListAdapter
import ch.protonmail.android.activities.messageDetails.body.MessageBodyScaleListener
import ch.protonmail.android.activities.messageDetails.body.MessageBodyTouchListener
import ch.protonmail.android.core.Constants
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.data.local.model.Attachment
import ch.protonmail.android.data.local.model.Message
import ch.protonmail.android.details.domain.model.SignatureVerification
import ch.protonmail.android.details.presentation.mapper.MessageEncryptionUiModelMapper
import ch.protonmail.android.details.presentation.mapper.MessageToMessageDetailsListItemMapper
import ch.protonmail.android.details.presentation.model.ConversationUiModel
import ch.protonmail.android.details.presentation.model.MessageDetailsListItem
import ch.protonmail.android.details.presentation.ui.MessageDetailsActivity
import ch.protonmail.android.details.presentation.view.MessageDetailsActionsView
import ch.protonmail.android.labels.domain.model.Label
import ch.protonmail.android.settings.data.AccountSettingsRepository
import ch.protonmail.android.ui.model.LabelChipUiModel
import ch.protonmail.android.util.ProtonCalendarUtil
import ch.protonmail.android.utils.DateUtil
import ch.protonmail.android.utils.redirectToChrome
import ch.protonmail.android.utils.ui.ExpandableRecyclerAdapter
import ch.protonmail.android.utils.ui.TYPE_HEADER
import ch.protonmail.android.utils.ui.TYPE_ITEM
import ch.protonmail.android.utils.webview.SetUpWebViewDarkModeHandlingIfSupported
import ch.protonmail.android.views.PmWebViewClient
import ch.protonmail.android.views.messageDetails.MessageDetailsAttachmentsView
import ch.protonmail.android.views.messageDetails.MessageDetailsHeaderView
import ch.protonmail.android.views.messageDetails.ReplyActionsView
import kotlinx.android.synthetic.main.layout_message_details.view.*
import kotlinx.android.synthetic.main.layout_message_details_body.view.*
import kotlinx.coroutines.runBlocking
import org.apache.http.protocol.HTTP
import timber.log.Timber

/**
 * The delay after which we check if we can switch from a fixed message content height to WRAP_CONTENT. This behaviour
 * is needed in order to keep the message header attached to the top of the screen while the content is being loaded.
 */
private const val WRAP_MESSAGE_CONTENT_DELAY_MS = 100L

private const val ITEM_NOT_FOUND_INDEX = -1

internal class MessageDetailsAdapter(
    private val context: Context,
    private var messages: List<Message>,
    private val messageDetailsRecyclerView: RecyclerView,
    private val messageToMessageDetailsListItemMapper: MessageToMessageDetailsListItemMapper,
    private val userManager: UserManager,
    private val accountSettingsRepository: AccountSettingsRepository,
    private val messageEncryptionUiModelMapper: MessageEncryptionUiModelMapper,
    private val setUpWebViewDarkModeHandlingIfSupported: SetUpWebViewDarkModeHandlingIfSupported,
    private val protonCalendarUtil: ProtonCalendarUtil,
    private val onLoadEmbeddedImagesClicked: (Message, List<String>) -> Unit,
    private val onDisplayRemoteContentClicked: (Message) -> Unit,
    private val onLoadMessageBody: (Message) -> Unit,
    private val onAttachmentDownloadCallback: (Attachment) -> Unit,
    private val onOpenInProtonCalendarClicked: (Message) -> Unit,
    private val onEditDraftClicked: (Message) -> Unit,
    private val onReplyMessageClicked: (Constants.MessageActionType, Message) -> Unit,
    private val onMoreMessageActionsClicked: (Message) -> Unit
) : ExpandableRecyclerAdapter<MessageDetailsListItem>(context) {

    private var exclusiveLabelsPerMessage: HashMap<String, List<Label>> = hashMapOf()
    private var nonExclusiveLabelsPerMessage: HashMap<String, List<LabelChipUiModel>> = hashMapOf()

    private val messageLoadingSpinnerTopMargin by lazy {
        context.resources.getDimension(R.dimen.padding_m).toInt()
    }

    private val messageContentFixedLoadingHeight by lazy {
        context.resources.getDimension(R.dimen.constrained_message_content_view_size).toInt()
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        if (getItemViewType(position) == TYPE_HEADER) {
            (holder as HeaderViewHolder).bind(
                visibleItems[position].message
            )
        } else {
            val isLastNonDraftItemInTheList = position == visibleItems.indexOfLast { !it.message.isDraft() }
            (holder as ItemViewHolder).bind(
                position,
                visibleItems[position] as MessageDetailsListItem.Body,
                isLastNonDraftItemInTheList
            )
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return if (viewType == TYPE_HEADER) {
            HeaderViewHolder(
                LayoutInflater.from(context).inflate(
                    R.layout.layout_message_details,
                    parent,
                    false
                )
            )
        } else {
            val itemView = LayoutInflater.from(context).inflate(
                R.layout.layout_message_details_body,
                parent,
                false
            )

            val webView = setupMessageBodyWebView(itemView)
            itemView.messageWebViewContainer.removeAllViews()
            itemView.messageWebViewContainer.addView(webView)

            val messageBodyProgress = createMessageBodyProgressBar()
            itemView.messageWebViewContainer.addView(messageBodyProgress)

            val detailsMessageActions = createInMessageActionsView()
            itemView.messageWebViewContainer.addView(detailsMessageActions)

            val replyActionsView = createReplyActionsView()
            itemView.messageWebViewContainer.addView(replyActionsView)

            setInitialMessageBodyHeight(itemView.messageWebViewContainer)

            ItemViewHolder(itemView)
        }
    }

    private fun setInitialMessageBodyHeight(messageWebViewContainer: LinearLayout) {
        // For single message conversations, we don't need to artificially expand the height while loading, as the
        // top of the only message will always be attached to the top of the screen. For multi-message conversations,
        // we need to expand it to make sure it occupies enough space on the screen to stay attached to the top
        // until the content is loaded.
        if (showingMoreThanOneMessage()) {
            setMessageContentExpandedLoadingHeight(messageWebViewContainer)
        } else {
            setMessageContentFixedLoadingHeight(messageWebViewContainer)
        }
    }

    private fun createMessageBodyProgressBar(): ProgressBar {
        return ProgressBar(context).apply {
            id = R.id.item_message_body_progress_view_id
            layoutParams = LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT
            ).apply {
                gravity = Gravity.CENTER_HORIZONTAL
                setMargins(0, messageLoadingSpinnerTopMargin, 0, 0)
            }
        }
    }

    private fun showingMoreThanOneMessage() = messages.size > 1

    private fun createInMessageActionsView(): MessageDetailsActionsView {
        val detailsMessageActions = MessageDetailsActionsView(context)
        detailsMessageActions.id = R.id.item_message_body_actions_layout_id
        detailsMessageActions.isVisible = false
        return detailsMessageActions
    }

    private fun createReplyActionsView(): ReplyActionsView {
        val replyActionsView = ReplyActionsView(context)
        replyActionsView.id = R.id.item_message_body_reply_actions_layout_id
        replyActionsView.isVisible = false
        return replyActionsView
    }

    private fun setupMessageBodyWebView(itemView: View): WebView? {
        val context = context as MessageDetailsActivity
        // Looks like some devices are not able to create a WebView in some conditions.
        // Show Toast and redirect to the proper page.
        val webView = try {
            WebView(context)
        } catch (ignored: Throwable) {
            (context as FragmentActivity).redirectToChrome()
            return null
        }
        webView.id = R.id.item_message_body_web_view_id

        val webViewClient = MessageDetailsPmWebViewClient(
            userManager, accountSettingsRepository, context, itemView, shouldShowRemoteImages()
        )
        configureWebView(webView, webViewClient)
        setUpScrollListener(webView, itemView.messageWebViewContainer)

        webView.invalidate()
        context.registerForContextMenu(webView)

        return webView
    }

    override fun onViewRecycled(holder: ViewHolder) {
        super.onViewRecycled(holder)

        holder.itemView.lastConversationMessageCollapsedDivider?.let { it.isVisible = false }
        holder.itemView.headerView?.forbidExpandingHeaderView()
        holder.itemView.headerView?.hideRecipientsCollapsedView()
    }

    fun showMessageDetails(
        parsedBody: String?,
        messageId: String,
        showLoadEmbeddedImagesButton: Boolean,
        showDecryptionError: Boolean,
        attachments: List<Attachment>,
        embeddedImageIds: List<String>,
        hasValidSignature: Boolean,
        hasInvalidSignature: Boolean
    ) {
        val item: MessageDetailsListItem? = visibleItems.firstOrNull {
            it.itemType == TYPE_ITEM && it.message.messageId == messageId
        }
        if (item == null) {
            Timber.d("Trying to show $messageId details but message is not in visibleItems list")
            return
        }

        Timber.d("Show message details: $messageId")
        val validParsedBody = parsedBody ?: return
        val message = item.message.apply {
            setAttachmentList(attachments)
            // Mark the message as read optimistically to reflect the change on the UI right away.
            // Note that this message is being referenced to from both the header and the item.
            this.Unread = false
            this.hasInvalidSignature = hasInvalidSignature
            this.hasValidSignature = hasValidSignature
        }
        val newItem = messageToMessageDetailsListItemMapper.toMessageDetailsListItem(
            message,
            validParsedBody,
            showDecryptionError,
            false,
            showLoadEmbeddedImagesButton
        ).copy(embeddedImageIds = embeddedImageIds)

        visibleItems.indexOf(item).let { changedItemIndex ->
            visibleItems[changedItemIndex] = newItem
            // Update both the message and its header to ensure the "read" status is shown
            val messageHeaderIndex = changedItemIndex - 1
            notifyItemRangeChanged(messageHeaderIndex, 2)
        }
    }

    fun setMessageData(conversation: ConversationUiModel) {
        messages = conversation.messages
        exclusiveLabelsPerMessage = conversation.exclusiveLabels
        nonExclusiveLabelsPerMessage = conversation.nonExclusiveLabels
        val items = ArrayList<MessageDetailsListItem>()
        messages.forEach { message ->
            val header = MessageDetailsListItem.Header(message)
            val body = MessageDetailsListItem.Body(
                message = message,
                messageFormattedHtml = message.decryptedHTML,
                messageFormattedHtmlWithQuotedHistory = message.decryptedHTML,
                showOpenInProtonCalendar = protonCalendarUtil.hasCalendarAttachment(message),
                showLoadEmbeddedImagesButton = false,
                showDecryptionError = false,
                showScheduledInfo = true
            )
            items.add(header)
            items.add(body)
        }

        setItems(items)
        expandLastNonDraftItem()
    }

    fun reloadMessage(messageId: String) {
        val item = visibleItems
            .filterIsInstance<MessageDetailsListItem.Body>()
            .find { it.message.messageId == messageId }
            ?: return

        val itemIndex = visibleItems.indexOf(item as MessageDetailsListItem)
        // Set message formatted html to null, in order to trigger loading
        //  and switch to light/dark mode in the web view
        val newItem = item.copy(messageFormattedHtml = null)
        visibleItems[itemIndex] = newItem
        notifyItemChanged(itemIndex)
    }

    private fun expandLastNonDraftItem() {
        val lastNonDraftHeaderIndex = visibleItems.indexOfLast {
            !it.message.isDraft() && it.itemType == TYPE_HEADER
        }

        if (lastNonDraftHeaderIndex == ITEM_NOT_FOUND_INDEX) {
            return
        }

        if (!isExpanded(lastNonDraftHeaderIndex)) {
            toggleExpandedItems(lastNonDraftHeaderIndex, true)
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    private fun setUpScrollListener(webView: WebView, directParent: LinearLayout) {
        val mScaleDetector = ScaleGestureDetector(
            context,
            MessageBodyScaleListener(
                messageDetailsRecyclerView,
                webView,
                directParent
            )
        )

        val scaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
        val touchListener = MessageBodyTouchListener(messageDetailsRecyclerView, mScaleDetector, scaledTouchSlop)
        messageDetailsRecyclerView.setOnTouchListener(touchListener)
        webView.setOnTouchListener(touchListener)
    }

    private fun displayAttachmentInfo(
        attachments: List<Attachment>?,
        attachmentsView: MessageDetailsAttachmentsView
    ) {
        if (attachments == null) {
            attachmentsView.visibility = View.GONE
            return
        }
        val attachmentsCount = attachments.size
        val totalAttachmentSize = attachments.map { it.fileSize }.sum()

        val attachmentsListAdapter = MessageDetailsAttachmentListAdapter(
            context,
            onAttachmentDownloadCallback
        )
        attachmentsListAdapter.setList(attachments)
        attachmentsView.bind(attachmentsCount, totalAttachmentSize, attachmentsListAdapter)
        attachmentsView.isVisible = attachmentsCount > 0
    }

    private fun configureWebView(webView: WebView, pmWebViewClient: PmWebViewClient) {
        // The web view seems to cache images sometimes even though it shouldn't.
        // That's why we clear the cache here when configuring it.
        webView.clearCache(true)
        webView.isScrollbarFadingEnabled = false
        webView.isVerticalScrollBarEnabled = false
        webView.isHorizontalScrollBarEnabled = false
        val webViewParams = LinearLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
        webViewParams.setMargins(0, 0, 0, 0)
        webView.layoutParams = webViewParams
        webView.webViewClient = pmWebViewClient
        webView.tag = "messageWebView"
        val webSettings = webView.settings
        webSettings.layoutAlgorithm = WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
        webSettings.useWideViewPort = true
        webSettings.loadWithOverviewMode = true
        webSettings.allowFileAccess = false
        webSettings.displayZoomControls = false
        webSettings.setGeolocationEnabled(false)
        webSettings.savePassword = false
        webSettings.javaScriptEnabled = false
        webSettings.setSupportZoom(true)
        webSettings.builtInZoomControls = true
        webSettings.pluginState = WebSettings.PluginState.OFF
        webSettings.setNeedInitialFocus(false)
        webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH)
        webSettings.cacheMode = WebSettings.LOAD_NO_CACHE
        webSettings.saveFormData = false
        webView.setOnLongClickListener {
            val messageBodyWebView = it as WebView
            val result = messageBodyWebView.hitTestResult
            if (result.type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
                (context as Activity).openContextMenu(messageBodyWebView)
                true
            } else {
                false
            }
        }
    }

    private fun setMessageContentFixedLoadingHeight(messageWebViewContainer: LinearLayout) {
        val params = messageWebViewContainer.layoutParams
        params.height = messageContentFixedLoadingHeight
        messageWebViewContainer.layoutParams = params
    }

    private fun setMessageContentExpandedLoadingHeight(messageWebViewContainer: LinearLayout) {
        messageWebViewContainer.layoutParams.height = (context.resources.displayMetrics.heightPixels * 0.7).toInt()
    }

    private fun wrapMessageContentHeight(messageWebViewContainer: LinearLayout) {
        val params = messageWebViewContainer.layoutParams
        params.height = ViewGroup.LayoutParams.WRAP_CONTENT
        messageWebViewContainer.layoutParams = params
    }

    private fun wrapMessageContentHeightWhenContentLoaded(messageWebViewContainer: LinearLayout) {
        messageWebViewContainer.postDelayed(WRAP_MESSAGE_CONTENT_DELAY_MS) {
            val webView = messageWebViewContainer.findViewById<WebView>(R.id.item_message_body_web_view_id)
            // We want to keep waiting until the content is actually loaded before wrapping the height to
            // avoid wrapping to quickly (this could result in the message being scrolled behind the screen instead
            // of staying in the user's view)
            if (webView.contentHeight != 0) {
                wrapMessageContentHeight(messageWebViewContainer)
            } else {
                wrapMessageContentHeightWhenContentLoaded(messageWebViewContainer)
            }
        }
    }

    private fun setUpSpamScoreView(spamScore: Int, spamScoreView: TextView) {
        val spamScoreVisibility: Int
        if (listOf(100, 101, 102).contains(spamScore)) {
            val spamScoreText = getSpamScoreText(spamScore)
            spamScoreView.setText(spamScoreText)
            Linkify.addLinks(spamScoreView, Linkify.ALL)
            spamScoreView.movementMethod = LinkMovementMethod.getInstance()
            spamScoreVisibility = View.VISIBLE
        } else {
            spamScoreVisibility = View.GONE
        }
        spamScoreView.visibility = spamScoreVisibility
    }

    private fun getSpamScoreText(spamScore: Int): Int {
        return when (spamScore) {
            100 -> R.string.spam_score_100
            101 -> R.string.spam_score_101
            102 -> R.string.spam_score_102
            else -> throw IllegalArgumentException("Unknown spam score.")
        }
    }

    private class MessageDetailsPmWebViewClient(
        userManager: UserManager,
        accountSettingsRepository: AccountSettingsRepository,
        activity: Activity,
        private val itemView: View,
        private val isAutoShowRemoteImages: Boolean
    ) : PmWebViewClient(userManager, accountSettingsRepository, activity, isAutoShowRemoteImages) {

        override fun onPageFinished(view: WebView, url: String) {
            if (amountOfRemoteResourcesBlocked() > 0) {
                itemView.displayRemoteContentButton.isVisible = true
            }

            this.showRemoteResources(isAutoShowRemoteImages)

            super.onPageFinished(view, url)
        }
    }

    private fun shouldShowRemoteImages(): Boolean {
        val mailSettings = userManager.getCurrentUserMailSettingsBlocking()
        val isAutoShowRemoteImages = mailSettings?.showImagesFrom?.includesRemote() ?: false

        // When android API < 26 we automatically show remote images because the `getWebViewClient` method
        // that we use to access the webView and load them later on was only introduced with API 26
        return isAutoShowRemoteImages || isAndroidApiLevelLowerThan26()
    }

    private fun isAndroidApiLevelLowerThan26() = Build.VERSION.SDK_INT < Build.VERSION_CODES.O

    private fun setUpWebViewDarkModeBlocking(webView: WebView, messageId: String) = runBlocking {
        setUpWebViewDarkModeHandlingIfSupported(context, userManager.requireCurrentUserId(), webView, messageId)
    }

    inner class HeaderViewHolder(
        view: View
    ) : ExpandableRecyclerAdapter<MessageDetailsListItem>.HeaderViewHolder(view) {

        fun bind(message: Message) {
            val messageDetailsHeaderView = itemView.headerView
            val signatureVerification = when {
                message.hasValidSignature -> SignatureVerification.SUCCESSFUL
                message.hasInvalidSignature -> SignatureVerification.FAILED
                else -> SignatureVerification.UNKNOWN
            }
            val messageEncryptionStatus = messageEncryptionUiModelMapper.messageEncryptionToUiModel(
                message.messageEncryption,
                signatureVerification,
                message.isSent
            )
            messageDetailsHeaderView.bind(
                message,
                messageEncryptionStatus,
                exclusiveLabelsPerMessage[message.messageId] ?: listOf(),
                nonExclusiveLabelsPerMessage[message.messageId] ?: listOf(),
                ::onHeaderCollapsed
            )

            messageDetailsHeaderView.setOnClickListener { view ->
                // For single message we don't want to allow collapsing the message
                if (!showingMoreThanOneMessage()) return@setOnClickListener
                val headerView = view as MessageDetailsHeaderView

                if (isMessageBodyExpanded()) {
                    // Message Body is expended - will collapse
                    headerView.collapseHeader()
                }
                toggleExpandedItems(layoutPosition, false)
                notifyItemChanged(layoutPosition)
            }

            if (isMessageBodyExpanded()) {
                messageDetailsHeaderView.allowExpandingHeaderView()
                messageDetailsHeaderView.showRecipientsCollapsedView()
                messageDetailsHeaderView.hideCollapsedMessageViews()
            } else {
                messageDetailsHeaderView.hideRecipientsCollapsedView()
                messageDetailsHeaderView.showCollapsedMessageViews()
            }

            if (message.isRead) {
                messageDetailsHeaderView.showMessageAsRead()
            } else {
                messageDetailsHeaderView.showMessageAsUnread()
            }

            if (isLastItemHeader()) {
                itemView.lastConversationMessageCollapsedDivider.isVisible = !isMessageBodyExpanded()
            }
        }

        private fun onHeaderCollapsed() {
            notifyItemChanged(layoutPosition)
        }

        private fun isMessageBodyExpanded() = isExpanded(layoutPosition)

        private fun isLastItemHeader(): Boolean {
            val lastHeaderItem = visibleItems.last { it.itemType == TYPE_HEADER }
            return layoutPosition == visibleItems.indexOf(lastHeaderItem)
        }
    }

    inner class ItemViewHolder(view: View) : ExpandableRecyclerAdapter<MessageDetailsListItem>.ViewHolder(view) {

        fun bind(position: Int, listItem: MessageDetailsListItem.Body, isLastNonDraftItemInTheList: Boolean) {
            val message = listItem.message
            Timber.v("Bind item: ${message.messageId}, isDownloaded: ${message.isDownloaded}")
            val attachmentsView = itemView.attachmentsView
            attachmentsView.visibility = View.GONE

            val expirationInfoView = itemView.expirationInfoView
            val decryptionErrorView = itemView.decryptionErrorView
            val scheduledInfoView = itemView.scheduledInfoView
            val displayRemoteContentButton = itemView.displayRemoteContentButton
            val loadEmbeddedImagesButton = itemView.loadEmbeddedImagesButton
            val openInProtonCalendarView = itemView.include_open_in_proton_calendar
            val editDraftButton = itemView.editDraftButton
            val webView = itemView.messageWebViewContainer
                .findViewById<WebView>(R.id.item_message_body_web_view_id) ?: return
            val messageBodyProgress = itemView.messageWebViewContainer
                .findViewById<ProgressBar>(R.id.item_message_body_progress_view_id) ?: return

            message.messageId?.let {
                setUpWebViewDarkModeBlocking(webView, it)
            }

            messageBodyProgress.isVisible = listItem.messageFormattedHtml.isNullOrEmpty()
            displayRemoteContentButton.isVisible = false
            loadEmbeddedImagesButton.isVisible = listItem.showLoadEmbeddedImagesButton
            openInProtonCalendarView.isVisible = listItem.showOpenInProtonCalendar
            editDraftButton.isVisible = message.isDraft()
            decryptionErrorView.bind(listItem.showDecryptionError)
            scheduledInfoView.bind(
                message.isScheduled, DateUtil.formatDateMonthYearAtTime(context, listItem.message.timeMs)
            )

            expirationInfoView.bind(message.expirationTime)
            setUpSpamScoreView(message.spamScore, itemView.spamScoreView)

            if (listItem.messageFormattedHtml == null) {
                Timber.v("Load body for message: ${message.messageId} at position $position, loc: ${message.location}")
                onLoadMessageBody(message)
            }

            listItem.messageFormattedHtml?.let {
                loadHtmlDataIntoWebView(webView, it)
            }

            displayAttachmentInfo(listItem.message.attachments, attachmentsView)
            setUpViewDividers()

            val hideMoreActionsButton = listItem.messageFormattedHtml.isNullOrEmpty() ||
                message.isDraft() ||
                !showingMoreThanOneMessage()
            setupMessageActionsView(
                message,
                listItem.messageFormattedHtmlWithQuotedHistory,
                webView,
                hideMoreActionsButton
            )
            // TODO: To be decided whether we will need these actions moving forward or they can be removed completely
            setupReplyActionsView(message, true)
            setupMessageContentActions(
                position = position,
                loadEmbeddedImagesContainer = loadEmbeddedImagesButton,
                displayRemoteContentButton = displayRemoteContentButton,
                openInProtonCalenderView = openInProtonCalendarView,
                editDraftButton = editDraftButton
            )
            setMessageContentHeight(listItem, isLastNonDraftItemInTheList)
            setHyperlinkCheck(webView, message)
        }

        private fun setupMessageActionsView(
            message: Message,
            messageHtmlWithQuotedHistory: String?,
            webView: WebView,
            shouldHideMoreActionsButton: Boolean
        ) {
            val messageActionsView: MessageDetailsActionsView =
                itemView.messageWebViewContainer.findViewById(R.id.item_message_body_actions_layout_id) ?: return

            val uiModel = MessageDetailsActionsView.UiModel(
                hideShowHistory = messageHtmlWithQuotedHistory.isNullOrEmpty(),
                hideMoreActions = shouldHideMoreActionsButton
            )
            messageActionsView.bind(uiModel)

            messageActionsView.onShowHistoryClicked { showHistoryButton ->
                loadHtmlDataIntoWebView(webView, messageHtmlWithQuotedHistory.orEmpty())
                showHistoryButton.isVisible = false
            }
            messageActionsView.onMoreActionsClicked { onMoreMessageActionsClicked(message) }
        }

        private fun setupReplyActionsView(
            message: Message,
            shouldHideAllActions: Boolean
        ) {
            val replyActionsView: ReplyActionsView =
                itemView.messageWebViewContainer.findViewById(R.id.item_message_body_reply_actions_layout_id) ?: return

            replyActionsView.bind(
                shouldShowReplyAllAction = message.toList.size + message.ccList.size > 1,
                shouldHideAllActions = shouldHideAllActions
            )

            replyActionsView.onReplyActionClicked {
                onReplyMessageClicked(Constants.MessageActionType.REPLY, message)
            }
            replyActionsView.onReplyAllActionClicked {
                onReplyMessageClicked(Constants.MessageActionType.REPLY_ALL, message)
            }
            replyActionsView.onForwardActionClicked {
                onReplyMessageClicked(Constants.MessageActionType.FORWARD, message)
            }
        }

        private fun loadHtmlDataIntoWebView(webView: WebView, htmlContent: String) {
            webView.loadDataWithBaseURL(
                Constants.DUMMY_URL_PREFIX,
                htmlContent,
                "text/html",
                HTTP.UTF_8,
                ""
            )
        }

        private fun setupMessageContentActions(
            position: Int,
            loadEmbeddedImagesContainer: Button,
            displayRemoteContentButton: Button,
            openInProtonCalenderView: View,
            editDraftButton: Button
        ) {
            loadEmbeddedImagesContainer.setOnClickListener { view ->
                view.visibility = View.GONE
                // Once images were loaded for one message, we automatically load them for all the others, so:
                //  the 'load embedded images' button will be hidden for all messages
                //  the 'formatted html' gets reset so that messages which were already rendered without images
                //  go through the rendering again (through `onLoadMessageBody` callback) and load them
                allItems.map { item ->
                    when (item) {
                        is MessageDetailsListItem.Header -> item
                        is MessageDetailsListItem.Body -> item.copy(
                            showLoadEmbeddedImagesButton = false,
                            showDecryptionError = false,
                            messageFormattedHtml = null
                        )
                    }
                }
                val item = visibleItems[position] as MessageDetailsListItem.Body
                onLoadEmbeddedImagesClicked(item.message, item.embeddedImageIds)
            }

            displayRemoteContentButton.setOnClickListener {
                val item = visibleItems[position]
                val webView = itemView.messageWebViewContainer.findViewById<WebView>(R.id.item_message_body_web_view_id)

                if (webView != null && webView.contentHeight > 0) {
                    itemView.displayRemoteContentButton.isVisible = false
                    webView.getWebViewClientOrNull()?.showRemoteResources(true)
                    webView.reload()
                    webView.invalidate()
                    onDisplayRemoteContentClicked(item.message)
                }
            }

            openInProtonCalenderView.setOnClickListener {
                val item = visibleItems[position]
                onOpenInProtonCalendarClicked(item.message)
            }

            editDraftButton.setOnClickListener {
                val item = visibleItems[position]
                onEditDraftClicked(item.message)
            }
        }

        private fun setUpViewDividers() {
            val hideHeaderDivider = itemView.attachmentsView.visibility == View.GONE &&
                itemView.expirationInfoView.visibility == View.VISIBLE
            itemView.headerDividerView.isVisible = !hideHeaderDivider

            val showAttachmentsDivider = itemView.attachmentsView.visibility == View.VISIBLE &&
                itemView.expirationInfoView.visibility != View.VISIBLE
            itemView.attachmentsDividerView.isVisible = showAttachmentsDivider
        }

        private fun setMessageContentHeight(
            listItem: MessageDetailsListItem.Body,
            isLastNonDraftItemInTheList: Boolean
        ) {
            when {
                !listItem.messageFormattedHtml.isNullOrEmpty() -> {
                    // We want to wrap the message content of the last non-draft item in the list only when the content
                    // has been loaded, to make the message header stay attached to the top of the list
                    // (if we wrap too quickly, the list will scroll down)
                    if (isLastNonDraftItemInTheList) {
                        wrapMessageContentHeightWhenContentLoaded(itemView.messageWebViewContainer)
                    } else {
                        wrapMessageContentHeight(itemView.messageWebViewContainer)
                    }
                }
                // For messages in the middle of a conversation, that are not initially expanded when opening
                // a conversation, we use a fixed height while loading
                !isLastNonDraftItemInTheList -> {
                    setMessageContentFixedLoadingHeight(itemView.messageWebViewContainer)
                }
            }
        }

        private fun setHyperlinkCheck(webView: WebView, message: Message) {
            webView.getWebViewClientOrNull()?.setPhishingCheck(message.isPhishing())
        }

        private fun WebView.getWebViewClientOrNull(): MessageDetailsPmWebViewClient? {
            return if (WebViewFeature.isFeatureSupported(WebViewFeature.GET_WEB_VIEW_CLIENT)) {
                WebViewCompat.getWebViewClient(this) as MessageDetailsPmWebViewClient
            } else {
                null
            }
        }
    }
}
