//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import SignalServiceKit
import SignalUI

public extension ConversationViewController {

    static func createBanner(title: String,
                             bannerColor: UIColor,
                             tapBlock: @escaping () -> Void) -> UIView {
        owsAssertDebug(!title.isEmpty)

        let bannerView = GestureView()
        bannerView.addTap(block: tapBlock)
        bannerView.backgroundColor = bannerColor
        bannerView.accessibilityIdentifier = "banner_close"

        let label = buildBannerLabel(title: title)
        label.textAlignment = .center

        let closeIcon = UIImage(imageLiteralResourceName: "x-extra-small")
        let closeButton = UIImageView(image: closeIcon)
        closeButton.tintColor = .white
        bannerView.addSubview(closeButton)
        let kBannerCloseButtonPadding: CGFloat = 8
        closeButton.autoPinEdge(toSuperviewEdge: .top, withInset: kBannerCloseButtonPadding)
        closeButton.autoPinTrailingToSuperviewMargin(withInset: kBannerCloseButtonPadding)
        closeButton.autoSetDimensions(to: closeIcon.size)

        bannerView.addSubview(label)
        label.autoPinEdge(toSuperviewEdge: .top, withInset: 5)
        label.autoPinEdge(toSuperviewEdge: .bottom, withInset: 5)
        let kBannerHPadding: CGFloat = 15
        label.autoPinLeadingToSuperviewMargin(withInset: kBannerHPadding)
        let kBannerHSpacing: CGFloat = 10
        closeButton.autoPinTrailing(toEdgeOf: label, offset: kBannerHSpacing)

        return bannerView
    }

    // MARK: - Pending Join Requests Banner

    func createPendingJoinRequestBanner(
        viewState: CVViewState,
        pendingMemberRequests: Set<SignalServiceAddress>,
        viewMemberRequestsBlock: @escaping () -> Void
    ) -> UIView {
        owsAssertDebug(!pendingMemberRequests.isEmpty)

        let format = OWSLocalizedString("PENDING_GROUP_MEMBERS_REQUEST_BANNER_%d", tableName: "PluralAware",
                                       comment: "Format for banner indicating that there are pending member requests to join the group. Embeds {{ the number of pending member requests }}.")
        let title = String.localizedStringWithFormat(format, pendingMemberRequests.count)

        let dismissButton = OWSButton(title: CommonStrings.dismissButton) { [weak self] in
            guard let self = self else { return }
            AssertIsOnMainThread()

            self.databaseStorage.write { transaction in
                viewState.hidePendingMemberRequestsBanner(
                    currentPendingMembers: pendingMemberRequests,
                    transaction: transaction
                )
            }

            self.ensureBannerState()
        }
        dismissButton.titleLabel?.font = UIFont.dynamicTypeSubheadlineClamped.semibold()
        let viewRequestsLabel = OWSLocalizedString("PENDING_GROUP_MEMBERS_REQUEST_BANNER_VIEW_REQUESTS",
                                                  comment: "Label for the 'view requests' button in the pending member requests banner.")
        let viewRequestsButton = OWSButton(title: viewRequestsLabel, block: viewMemberRequestsBlock)
        viewRequestsButton.titleLabel?.font = UIFont.dynamicTypeSubheadlineClamped.semibold()

        return Self.createBanner(title: title,
                                 buttons: [dismissButton, viewRequestsButton],
                                 accessibilityIdentifier: "pending_group_request_banner")
    }

    // MARK: - Name collision banners

    func fetchAvatar(
        for address: SignalServiceAddress,
        tx: SDSAnyReadTransaction
    ) -> UIImage? {
        if
            address.isLocalAddress,
            let profileAvatar = self.profileManager.localProfileAvatarImage()
        {
            return profileAvatar.resizedImage(to: CGSize(square: 24))
        }
        return Self.avatarBuilder.avatarImage(
            forAddress: address,
            diameterPoints: 24,
            localUserDisplayMode: .asUser,
            transaction: tx
        )
    }

    func createMessageRequestNameCollisionBannerIfNecessary(viewState: CVViewState) -> UIView? {
        guard let contactThread = thread as? TSContactThread else { return nil }

        let collisionFinder = ContactThreadNameCollisionFinder
            .makeToCheckMessageRequestNameCollisions(forContactThread: contactThread)

        guard let (avatar1, avatar2) = databaseStorage.read(block: { tx -> (UIImage?, UIImage?)? in
            guard
                viewState.shouldShowMessageRequestNameCollisionBanner(transaction: tx),
                let collision = collisionFinder.findCollisions(transaction: tx).first
            else { return nil }

            return (
                fetchAvatar(for: collision.elements[0].address, tx: tx),
                fetchAvatar(for: collision.elements[1].address, tx: tx)
            )
        }) else { return nil }

        let banner = NameCollisionBanner()
        banner.labelText = OWSLocalizedString(
            "MESSAGE_REQUEST_NAME_COLLISION_BANNER_LABEL",
            comment: "Banner label notifying user that a new message is from a user with the same name as an existing contact")
        banner.reviewActionText = CommonStrings.viewButton
        if let avatar1 = avatar1, let avatar2 = avatar2 {
            banner.primaryImage = avatar1
            banner.secondaryImage = avatar2
        }

        banner.closeAction = { [weak self] in
            guard let self = self else { return }
            self.databaseStorage.write { viewState.hideMessageRequestNameCollisionBanner(transaction: $0) }
            self.ensureBannerState()
        }

        banner.reviewAction = { [weak self] in
            guard let self = self else { return }
            let vc = NameCollisionResolutionViewController(collisionFinder: collisionFinder, collisionDelegate: self)
            vc.present(fromViewController: self)
        }

        return banner
    }

    func createGroupMembershipCollisionBannerIfNecessary() -> UIView? {
        guard let groupThread = thread as? TSGroupThread else { return nil }

        // Collision discovery can be expensive, so we only build our banner if
        // we've already done the expensive bit
        guard let collisionFinder = viewState.groupNameCollisionFinder else {
            let collisionFinder = GroupMembershipNameCollisionFinder(thread: groupThread)
            viewState.groupNameCollisionFinder = collisionFinder

            firstly(on: DispatchQueue.sharedUserInitiated) {
                self.databaseStorage.read { readTx in
                    // Prewarm our collision finder off the main thread
                    _ = collisionFinder.findCollisions(transaction: readTx)
                }
            }.done(on: DispatchQueue.main) {
                self.ensureBannerState()
            }.catch { error in
                owsFailDebug("\(error)")
            }
            return nil
        }

        guard collisionFinder.hasFetchedProfileUpdateMessages else {
            // We already have a collision finder. It just hasn't finished fetching.
            return nil
        }

        // Fetch the necessary info to build the banner
        guard let (title, avatar1, avatar2) = databaseStorage.read(block: { readTx -> (String, UIImage?, UIImage?)? in
            let collisionSets = collisionFinder.findCollisions(transaction: readTx)
            guard !collisionSets.isEmpty else { return nil }

            let totalCollisionElementCount = collisionSets.reduce(0) { $0 + $1.elements.count }

            let title: String

            if collisionSets.count > 1 {
                let titleFormat = OWSLocalizedString(
                    "GROUP_MEMBERSHIP_MULTIPLE_COLLISIONS_BANNER_TITLE_%d",
                    tableName: "PluralAware",
                    comment: "Banner title alerting user to a name collision set ub the group membership. Embeds {{ total number of colliding members }}"
                )
                title = String.localizedStringWithFormat(titleFormat, collisionSets.count)
            } else {
                let titleFormat = OWSLocalizedString(
                    "GROUP_MEMBERSHIP_COLLISIONS_BANNER_TITLE_%d",
                    tableName: "PluralAware",
                    comment: "Banner title alerting user about multiple name collisions in group membership. Embeds {{ number of sets of colliding members }}"
                )
                title = String.localizedStringWithFormat(titleFormat, totalCollisionElementCount)
            }

            let avatar1 = fetchAvatar(
                for: collisionSets[0].elements[0].address,
                tx: readTx
            )
            let avatar2 = fetchAvatar(
                for: collisionSets[0].elements[1].address,
                tx: readTx
            )
            return (title, avatar1, avatar2)

        }) else { return nil }

        let banner = NameCollisionBanner()
        banner.labelText = title
        banner.reviewActionText = CommonStrings.viewButton
        if let avatar1 = avatar1, let avatar2 = avatar2 {
            banner.primaryImage = avatar1
            banner.secondaryImage = avatar2
        }

        banner.closeAction = { [weak self] in
            self?.databaseStorage.asyncWrite(block: { writeTx in
                collisionFinder.markCollisionsAsResolved(transaction: writeTx)
            }, completion: {
                self?.ensureBannerState()
            })
        }

        banner.reviewAction = { [weak self] in
            guard let self = self else { return }
            let vc = NameCollisionResolutionViewController(collisionFinder: collisionFinder, collisionDelegate: self)
            vc.present(fromViewController: self)
        }

        return banner
    }
}

// MARK: -

fileprivate extension ConversationViewController {

    static func buildBannerLabel(title: String) -> UILabel {
        let label = UILabel()
        label.font = UIFont.dynamicTypeSubheadlineClamped.semibold()
        label.text = title
        label.textColor = .white
        label.numberOfLines = 0
        label.lineBreakMode = .byWordWrapping
        return label
    }

    static func createBanner(title: String,
                             buttons: [UIView],
                             accessibilityIdentifier: String) -> UIView {

        let titleLabel = buildBannerLabel(title: title)
        titleLabel.font = .dynamicTypeSubheadlineClamped

        let buttonRow = UIStackView(arrangedSubviews: [UIView.hStretchingSpacer()] + buttons)
        buttonRow.axis = .horizontal
        buttonRow.spacing = 24

        let bannerView = UIStackView(arrangedSubviews: [ titleLabel, buttonRow ])
        bannerView.axis = .vertical
        bannerView.alignment = .fill
        bannerView.spacing = 10
        bannerView.layoutMargins = UIEdgeInsets(top: 14, leading: 16, bottom: 14, trailing: 16)
        bannerView.isLayoutMarginsRelativeArrangement = true
        bannerView.addBackgroundView(withBackgroundColor: .ows_accentBlue)
        bannerView.accessibilityIdentifier = accessibilityIdentifier
        return bannerView
    }
}

// MARK: -

// A convenience view that allows block-based gesture handling.
public class GestureView: UIView {
    public init() {
        super.init(frame: .zero)

        self.layoutMargins = .zero
    }

    @available(*, unavailable, message: "use other constructor instead.")
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    public typealias BlockType = () -> Void

    private var tapBlock: BlockType?

    public func addTap(block tapBlock: @escaping () -> Void) {
        owsAssertDebug(self.tapBlock == nil)

        self.tapBlock = tapBlock
        addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTap(_:))))
    }

    // MARK: - Events

    @objc
    func didTap(_ sender: UITapGestureRecognizer) {
        guard sender.state == .recognized else {
            return
        }
        guard let tapBlock = tapBlock else {
            owsFailDebug("Missing tapBlock.")
            return
        }
        tapBlock()
    }
}

private class NameCollisionBanner: UIView {

    private enum Constants {
        static let avatarSize = CGSize(square: 24)
        static let avatarBorderSize: CGFloat = 2
    }

    var primaryImage: UIImage? {
        get { primaryImageView.image }
        set {
            primaryImageView.image = newValue
            setNeedsUpdateConstraints()
        }
    }

    var secondaryImage: UIImage? {
        get { secondaryImageView.image }
        set {
            secondaryImageView.image = newValue
            setNeedsUpdateConstraints()
        }
    }

    var labelText: String? {
        get { label.text }
        set { label.text = newValue }
    }

    var reviewActionText: String? {
        get { reviewButton.title(for: .normal) }
        set { reviewButton.setTitle(newValue, for: .normal) }
    }

    var reviewAction: () -> Void {
        get { reviewButton.block }
        set { reviewButton.block = newValue }
    }

    var closeAction: () -> Void {
        get { closeButton.block }
        set { closeButton.block = newValue }
    }

    private let label: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.font = UIFont.dynamicTypeFootnoteClamped
        label.textColor = Theme.secondaryTextAndIconColor
        return label
    }()

    private let primaryImageView: UIImageView = {
        let avatarSize = Constants.avatarSize

        let imageView = UIImageView.withTemplateImageName("info", tintColor: Theme.secondaryTextAndIconColor)

        imageView.layer.cornerRadius = avatarSize.smallerAxis / 2
        imageView.layer.masksToBounds = true

        imageView.autoSetDimensions(to: avatarSize)
        imageView.setCompressionResistanceHigh()
        imageView.setContentHuggingHigh()
        return imageView
    }()

    private class SecondaryImageView: UIImageView {
        override func layoutSubviews() {
            layer.cornerRadius = Constants.avatarSize.smallerAxis / 2
            layer.masksToBounds = true

            // Mask out a border around the primary avatar.
            // The background is a UIVisualEffect, so we can't just rely
            // on adding a border around the primary avatar itself.
            let borderSize = Constants.avatarBorderSize

            let maskPath = UIBezierPath(rect: bounds)

            let circlePath = UIBezierPath(
                ovalIn: .init(
                    origin: .init(
                        x: self.width / 2 - borderSize,
                        y: self.height / 2 - borderSize
                    ),
                    size: self.frame.size.plus(.square(borderSize * 2))
                )
            )

            maskPath.append(circlePath)

            let maskLayer = CAShapeLayer()
            maskLayer.path = maskPath.cgPath
            // Mask the inverse of the circle path
            maskLayer.fillRule = .evenOdd

            self.layer.mask = maskLayer
        }
    }

    private let secondaryImageView: SecondaryImageView = {
        let imageView = SecondaryImageView()
        imageView.autoSetDimensions(to: Constants.avatarSize)
        imageView.setCompressionResistanceHigh()
        imageView.setContentHuggingHigh()
        return imageView
    }()

    private let closeButton: OWSButton = {
        let button = OWSButton(
            imageName: "x-20",
            tintColor: Theme.secondaryTextAndIconColor
        )
        button.accessibilityLabel = OWSLocalizedString(
            "BANNER_CLOSE_ACCESSIBILITY_LABEL",
            comment: "Accessibility label for banner close button"
        )
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setCompressionResistanceHigh()
        button.setContentHuggingHigh()
        return button
    }()

    private let reviewButton: OWSButton = {
        let button = OWSRoundedButton()
        button.backgroundColor = Theme.isDarkThemeEnabled ? UIColor(white: 1, alpha: 0.16) : UIColor(white: 0, alpha: 0.08)
        button.setTitleColor(Theme.primaryTextColor, for: .normal)
        button.titleLabel?.font = UIFont.dynamicTypeFootnoteClamped.semibold()
        button.dimsWhenHighlighted = true

        button.translatesAutoresizingMaskIntoConstraints = false
        button.contentEdgeInsets = .init(hMargin: 12, vMargin: 6)
        button.setCompressionResistanceHigh()
        button.setContentHuggingLow()

        return button
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)

        let stackView = UIStackView()

        if UIAccessibility.isReduceTransparencyEnabled {
            stackView.backgroundColor = Theme.secondaryBackgroundColor
        } else {
            let blurEffectView = UIVisualEffectView(effect: Theme.barBlurEffect)
            stackView.addSubview(blurEffectView)
            blurEffectView.autoPinEdgesToSuperviewEdges()
            stackView.clipsToBounds = true
        }

        stackView.spacing = 12
        stackView.layoutMargins = .init(margin: 12)
        stackView.isLayoutMarginsRelativeArrangement = true
        stackView.axis = .horizontal
        stackView.alignment = .center

        let imageContainer = UIView.container()

        imageContainer.addSubview(secondaryImageView)
        imageContainer.addSubview(primaryImageView)
        stackView.addArrangedSubview(imageContainer)

        // Offsets adjusted in updateConstraints() based on content
        primaryImageViewConstraints = (
            top: primaryImageView.autoPinEdge(.top, to: .top, of: imageContainer, withOffset: 0),
            leading: primaryImageView.autoPinEdge(.leading, to: .leading, of: imageContainer, withOffset: 4)
        )
        primaryImageView.autoPinEdge(toSuperviewEdge: .trailing)
        primaryImageView.autoPinEdge(.bottom, to: .bottom, of: imageContainer)
        // Secondary image is always offset to the top left of the primary image
        secondaryImageView.autoPinEdge(.top, to: .top, of: primaryImageView, withOffset: -12)
        secondaryImageView.autoPinEdge(.leading, to: .leading, of: primaryImageView, withOffset: -12)

        stackView.addArrangedSubviews([label, reviewButton, closeButton])
        accessibilityElements = [label, reviewButton, closeButton]

        let cornerRadius: CGFloat = 18

        let stackContainer = UIView.container()
        stackContainer.addSubview(stackView)
        stackView.autoPinEdgesToSuperviewEdges()
        stackView.addBorder(with: .ows_blackAlpha10)
        // Clip the stack view so the blurred background is clipped
        stackView.layer.cornerRadius = cornerRadius
        stackView.clipsToBounds = true
        // Apply the shadow to the container so it doesn't get clipped out
        stackContainer.layer.cornerRadius = cornerRadius
        stackContainer.setShadow(radius: 8, opacity: 0.2, offset: .init(width: 0, height: 4))

        let containerView = UIView()
        containerView.layoutMargins = .init(hMargin: 16, vMargin: 8)
        containerView.addSubview(stackContainer)
        stackContainer.autoPinEdgesToSuperviewMargins()

        self.addSubview(containerView)
        containerView.autoPinEdgesToSuperviewEdges()
    }

    var primaryImageViewConstraints: (top: NSLayoutConstraint, leading: NSLayoutConstraint)?

    override func updateConstraints() {
        super.updateConstraints()
        guard let constraints = primaryImageViewConstraints else { return }

        // If we have a secondary image, we want to adjust our constraints a bit
        let hasSecondaryImage = (secondaryImage != nil)
        constraints.top.constant = hasSecondaryImage ? 12 : 0
        constraints.leading.constant = hasSecondaryImage ? 16 : 4
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// MARK: -

extension ConversationViewController {

    public func ensureBannerState() {
        AssertIsOnMainThread()

        // This method should be called rarely, so it's simplest to discard and
        // rebuild the indicator view every time.
        bannerView?.removeFromSuperview()
        self.bannerView = nil

        var banners = [UIView]()

        // Most of these banners should hide themselves when the user scrolls
        if !userHasScrolled {
            let message: String?
            let noLongerVerifiedIdentityKeys = databaseStorage.read { tx in self.noLongerVerifiedIdentityKeys(tx: tx) }
            switch noLongerVerifiedIdentityKeys.count {
            case 0:
                message = nil

            case 1:
                let address = noLongerVerifiedIdentityKeys.first!.key
                let displayName = databaseStorage.read { tx in contactsManager.displayName(for: address, tx: tx).resolvedValue() }
                let format = (isGroupConversation
                                ? OWSLocalizedString("MESSAGES_VIEW_1_MEMBER_NO_LONGER_VERIFIED_FORMAT",
                                                    comment: "Indicates that one member of this group conversation is no longer verified. Embeds {{user's name or phone number}}.")
                                : OWSLocalizedString("MESSAGES_VIEW_CONTACT_NO_LONGER_VERIFIED_FORMAT",
                                                    comment: "Indicates that this 1:1 conversation is no longer verified. Embeds {{user's name or phone number}}."))
                message = String(format: format, displayName)

            default:
                message = OWSLocalizedString("MESSAGES_VIEW_N_MEMBERS_NO_LONGER_VERIFIED",
                                            comment: "Indicates that more than one member of this group conversation is no longer verified.")
            }
            if let message {
                let banner = ConversationViewController.createBanner(
                    title: message,
                    bannerColor: .ows_accentRed,
                    tapBlock: { [weak self] in
                        self?.noLongerVerifiedBannerViewWasTapped(noLongerVerifiedIdentityKeys: noLongerVerifiedIdentityKeys)
                    }
                )
                banners.append(banner)
            }

            func buildBlockStateMessage() -> String? {
                guard isGroupConversation else {
                    return nil
                }
                let blockedGroupMemberCount = self.blockedGroupMemberCount
                if blockedGroupMemberCount > 0 {
                    return String.localizedStringWithFormat(
                        OWSLocalizedString("MESSAGES_VIEW_GROUP_N_MEMBERS_BLOCKED_%d", tableName: "PluralAware",
                                          comment: "Indicates that some members of this group has been blocked. Embeds {{the number of blocked users in this group}}."),
                        blockedGroupMemberCount)
                } else {
                    return nil
                }
            }
            if let blockStateMessage = buildBlockStateMessage() {
                let banner = ConversationViewController.createBanner(title: blockStateMessage,
                                                                     bannerColor: .ows_accentRed) { [weak self] in
                    self?.blockBannerViewWasTapped()
                }
                banners.append(banner)
            }

            let pendingMemberRequests = self.pendingMemberRequests
            if
                !pendingMemberRequests.isEmpty,
                self.canApprovePendingMemberRequests,
                databaseStorage.read(block: { transaction in
                    // We will skip this read if the above checks fail, which
                    // will be most of the time.
                    viewState.shouldShowPendingMemberRequestsBanner(
                        currentPendingMembers: pendingMemberRequests,
                        transaction: transaction
                    )
                }) {
                let banner = self.createPendingJoinRequestBanner(
                    viewState: viewState,
                    pendingMemberRequests: pendingMemberRequests
                ) { [weak self] in
                    self?.showConversationSettingsAndShowMemberRequests()
                }

                banners.append(banner)
            }
        }

        if let banner = createMessageRequestNameCollisionBannerIfNecessary(viewState: viewState) {
            banners.append(banner)
        }

        if let banner = createGroupMembershipCollisionBannerIfNecessary() {
            banners.append(banner)
        }

        if banners.isEmpty {
            if hasViewDidAppearEverBegun {
                updateContentInsets()
            }
            return
        }

        let bannerView = UIStackView(arrangedSubviews: banners)
        bannerView.axis = .vertical
        bannerView.alignment = .fill
        self.view.addSubview(bannerView)
        bannerView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
        bannerView.autoPinEdge(toSuperviewEdge: .leading)
        bannerView.autoPinEdge(toSuperviewEdge: .trailing)
        self.view.layoutSubviews()

        self.bannerView = bannerView
        if hasViewDidAppearEverBegun {
            updateContentInsets()
        }
    }

    private var pendingMemberRequests: Set<SignalServiceAddress> {
        if let groupThread = thread as? TSGroupThread {
            return groupThread.groupMembership.requestingMembers
        } else {
            return []
        }
    }

    private var canApprovePendingMemberRequests: Bool {
        if let groupThread = thread as? TSGroupThread {
            return groupThread.isLocalUserFullMemberAndAdministrator
        } else {
            return false
        }
    }

    private func blockBannerViewWasTapped() {
        AssertIsOnMainThread()

        if isBlockedConversation() {
            // If this a blocked conversation, offer to unblock.
            showUnblockConversationUI(completion: nil)
        } else if isGroupConversation {
            // If this a group conversation with at least one blocked member,
            // Show the block list view.
            let blockedGroupMemberCount = self.blockedGroupMemberCount
            if blockedGroupMemberCount > 0 {
                let vc = BlockListViewController()
                navigationController?.pushViewController(vc, animated: true)
            }
        }
    }

    private func noLongerVerifiedBannerViewWasTapped(noLongerVerifiedIdentityKeys: [SignalServiceAddress: Data]) {
        AssertIsOnMainThread()

        let title: String
        switch noLongerVerifiedIdentityKeys.count {
        case 0:
            return
        case 1:
            title = OWSLocalizedString(
                "VERIFY_PRIVACY",
                comment: "Label for button or row which allows users to verify the safety number of another user."
            )
        default:
            title = OWSLocalizedString(
                "VERIFY_PRIVACY_MULTIPLE",
                comment: "Label for button or row which allows users to verify the safety numbers of multiple users."
            )
        }

        let actionSheet = ActionSheetController()

        actionSheet.addAction(ActionSheetAction(title: title, style: .default) { [weak self] _ in
            self?.showNoLongerVerifiedUI(noLongerVerifiedIdentityKeys: noLongerVerifiedIdentityKeys)
        })

        actionSheet.addAction(ActionSheetAction(title: CommonStrings.dismissButton,
                                                accessibilityIdentifier: "dismiss",
                                                style: .cancel) { [weak self] _ in
            self?.resetVerificationStateToDefault(noLongerVerifiedIdentityKeys: noLongerVerifiedIdentityKeys)
        })

        dismissKeyBoard()
        presentActionSheet(actionSheet)
    }

    private var blockedGroupMemberCount: Int {
        guard let groupThread = thread as? TSGroupThread else {
            owsFailDebug("Invalid thread.")
            return 0
        }

        let blockedMembers = databaseStorage.read { readTx in
            groupThread.groupModel.groupMembers.filter {
                blockingManager.isAddressBlocked($0, transaction: readTx)
            }
        }
        return blockedMembers.count
    }
}
