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

import SignalServiceKit
import SignalUI

protocol LongTextViewDelegate: AnyObject {
    func longTextViewMessageWasDeleted(_ longTextViewController: LongTextViewController)
}

// MARK: -
public class LongTextViewController: OWSViewController {

    // MARK: - Properties

    weak var delegate: LongTextViewDelegate?

    let itemViewModel: CVItemViewModelImpl
    let threadViewModel: ThreadViewModel
    let spoilerState: SpoilerRenderState

    var messageTextView: UITextView!
    let footer = UIToolbar.clear()

    private var linkItems: [CVTextLabel.Item]?

    var displayableText: DisplayableText? { itemViewModel.displayableBodyText }

    // MARK: Initializers

    public init(
        itemViewModel: CVItemViewModelImpl,
        threadViewModel: ThreadViewModel,
        spoilerState: SpoilerRenderState
    ) {
        self.itemViewModel = itemViewModel
        self.threadViewModel = threadViewModel
        self.spoilerState = spoilerState
        super.init()
    }

    // MARK: View Lifecycle

    public override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.title = OWSLocalizedString("LONG_TEXT_VIEW_TITLE",
                                                 comment: "Title for the 'long text message' view.")

        createViews()

        self.messageTextView.contentOffset = CGPoint(x: 0, y: self.messageTextView.contentInset.top)

        databaseStorage.appendDatabaseChangeDelegate(self)
    }

    public override func themeDidChange() {
        super.themeDidChange()

        loadContent()
    }

    public func loadContent() {
        super.themeDidChange()

        view.backgroundColor = Theme.backgroundColor
        messageTextView.backgroundColor = Theme.backgroundColor
        messageTextView.textColor = Theme.primaryTextColor
        footer.tintColor = Theme.primaryIconColor

        let displayConfig = HydratedMessageBody.DisplayConfiguration.longMessageView(
            revealedSpoilerIds: spoilerState.revealState.revealedSpoilerIds(
                interactionIdentifier: .fromInteraction(itemViewModel.interaction)
            )
        )

        messageTextViewSpoilerConfig.animationManager = self.spoilerState.animationManager
        messageTextViewSpoilerConfig.text = displayableText?.fullTextValue
        messageTextViewSpoilerConfig.displayConfig = displayConfig

        if let displayableText = displayableText {
            let baseAttrs: [NSAttributedString.Key: Any] = [
                .font: UIFont.dynamicTypeBody,
                .foregroundColor: Theme.primaryTextColor
            ]

            let mutableText: NSMutableAttributedString
            switch displayableText.fullTextValue {
            case .text(let text):
                mutableText = NSMutableAttributedString(string: text, attributes: baseAttrs)
            case .attributedText(let text):
                mutableText = NSMutableAttributedString(attributedString: text)
                mutableText.addAttributesToEntireString(baseAttrs)
            case .messageBody(let messageBody):
                let attrString = messageBody.asAttributedStringForDisplay(
                    config: displayConfig,
                    isDarkThemeEnabled: Theme.isDarkThemeEnabled
                )
                mutableText = (attrString as? NSMutableAttributedString) ?? NSMutableAttributedString(attributedString: attrString)
            }

            let hasPendingMessageRequest = databaseStorage.read { transaction in
                itemViewModel.thread.hasPendingMessageRequest(transaction: transaction)
            }
            CVComponentBodyText.configureTextView(
                messageTextView,
                interaction: itemViewModel.interaction,
                displayableText: displayableText
            )

            let items = CVComponentBodyText.detectItems(
                text: displayableText,
                hasPendingMessageRequest: hasPendingMessageRequest,
                shouldAllowLinkification: displayableText.shouldAllowLinkification,
                textWasTruncated: false,
                revealedSpoilerIds: displayConfig.style.revealedIds,
                interactionUniqueId: itemViewModel.interaction.uniqueId,
                interactionIdentifier: .fromInteraction(itemViewModel.interaction)
            )

            CVTextLabel.linkifyData(
                attributedText: mutableText,
                linkifyStyle: .linkAttribute,
                items: items
            )
            messageTextView.attributedText = mutableText
            messageTextView.textAlignment = displayableText.fullTextNaturalAlignment
            self.linkItems = items

            if items.isEmpty.negated {
                messageTextView.addGestureRecognizer(UITapGestureRecognizer(
                    target: self,
                    action: #selector(didTapMessageTextView)
                ))
            }
        } else {
            owsFailDebug("displayableText was unexpectedly nil")
            messageTextView.text = ""
        }

        let linkTextAttributes: [NSAttributedString.Key: Any] = [
            NSAttributedString.Key.foregroundColor: Theme.primaryTextColor,
            NSAttributedString.Key.underlineColor: Theme.primaryTextColor,
            NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue
        ]
        messageTextView.linkTextAttributes = linkTextAttributes
    }

    // MARK: -

    private func refreshContent() {
        AssertIsOnMainThread()

        let uniqueId = itemViewModel.interaction.uniqueId
        let messageWasDeleted = databaseStorage.read {
            TSInteraction.anyFetch(uniqueId: uniqueId, transaction: $0) == nil
        }
        guard messageWasDeleted else {
            return
        }
        Logger.error("Message was deleted")
        DispatchQueue.main.async {
            self.delegate?.longTextViewMessageWasDeleted(self)
        }
    }

    // MARK: - Create Views

    private func createViews() {
        view.backgroundColor = Theme.backgroundColor

        let messageTextView = OWSTextView()
        self.messageTextView = messageTextView
        messageTextView.font = UIFont.dynamicTypeBody
        messageTextView.backgroundColor = Theme.backgroundColor
        messageTextView.isOpaque = true
        messageTextView.isEditable = false
        messageTextView.isSelectable = true
        messageTextView.isScrollEnabled = true
        messageTextView.showsHorizontalScrollIndicator = false
        messageTextView.showsVerticalScrollIndicator = true
        messageTextView.isUserInteractionEnabled = true
        messageTextView.textColor = Theme.primaryTextColor

        view.addSubview(messageTextView)
        messageTextView.autoPinEdge(toSuperviewEdge: .top)
        messageTextView.autoPinEdge(toSuperviewEdge: .leading)
        messageTextView.autoPinEdge(toSuperviewEdge: .trailing)
        messageTextView.textContainerInset = UIEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)

        view.addSubview(footer)
        footer.autoPinWidthToSuperview()
        footer.autoPinEdge(.top, to: .bottom, of: messageTextView)
        footer.autoPin(toBottomLayoutGuideOf: self, withInset: 0)
        footer.tintColor = Theme.primaryIconColor

        footer.items = [
            UIBarButtonItem(
                image: Theme.iconImage(.buttonShare),
                style: .plain,
                target: self,
                action: #selector(shareButtonPressed)
            ),
            .flexibleSpace(),
            .button(icon: .buttonForward, style: .plain) { [weak self] in
                self?.forwardButtonPressed()
            },
        ]

        loadContent()
    }

    // MARK: - Spoiler Animation

    private lazy var messageTextViewSpoilerConfig = SpoilerableTextConfig.Builder(isViewVisible: true) {
        didSet {
            messageTextViewSpoilerAnimator.updateAnimationState(messageTextViewSpoilerConfig)
        }
    }

    private lazy var messageTextViewSpoilerAnimator: SpoilerableTextViewAnimator = {
        let animator = SpoilerableTextViewAnimator(textView: messageTextView)
        animator.updateAnimationState(messageTextViewSpoilerConfig)
        return animator
    }()

    // MARK: - Actions

    @objc
    private func shareButtonPressed(_ sender: UIBarButtonItem) {
        guard let displayableText else {
            return
        }
        let shareText: String
        switch displayableText.fullTextValue {
        case .text(let text):
            shareText = text
        case .attributedText(let string):
            shareText = string.string
        case .messageBody(let messageBody):
            shareText = messageBody.asPlaintext()
        }
        AttachmentSharing.showShareUI(for: shareText, sender: sender)
    }

    private func forwardButtonPressed() {
        // Only forward text.
        let selectionType: CVSelectionType = (itemViewModel.componentState.hasPrimaryAndSecondaryContentForSelection
                                                ? .secondaryContent
                                                : .allContent)
        let selectionItem = CVSelectionItem(interactionId: itemViewModel.interaction.uniqueId,
                                            interactionType: itemViewModel.interaction.interactionType,
                                            isForwardable: true,
                                            selectionType: selectionType)
        ForwardMessageViewController.present(forSelectionItems: [selectionItem],
                                             from: self,
                                             delegate: self)
    }

    @objc
    private func didTapMessageTextView(_ sender: UIGestureRecognizer) {
        guard let linkItems else {
            return
        }
        let location = sender.location(in: messageTextView)

        guard let characterIndex = messageTextView.characterIndex(of: location) else {
            return
        }

        for item in linkItems {
            if item.range.contains(characterIndex) {
                switch item {
                case .referencedUser:
                    owsFailDebug("Should not have referenced user in long message body.")
                    return
                case .dataItem(let dataItem):
                    UIApplication.shared.open(dataItem.url, options: [:], completionHandler: nil)
                    return
                case .mention(let mentionItem):
                    ImpactHapticFeedback.impactOccurred(style: .light)

                    var groupViewHelper: GroupViewHelper?
                    if threadViewModel.isGroupThread {
                        groupViewHelper = GroupViewHelper(threadViewModel: threadViewModel)
                        groupViewHelper!.delegate = self
                    }

                    let address = SignalServiceAddress(mentionItem.mentionAci)
                    ProfileSheetSheetCoordinator(
                        address: address,
                        groupViewHelper: groupViewHelper,
                        spoilerState: spoilerState
                    )
                    .presentAppropriateSheet(from: self)
                    return
                case .unrevealedSpoiler(let unrevealedSpoiler):
                    self.spoilerState.revealState.setSpoilerRevealed(
                        withID: unrevealedSpoiler.spoilerId,
                        interactionIdentifier: unrevealedSpoiler.interactionIdentifier
                    )
                    self.loadContent()
                    return
                }
            }
        }
    }
}

// MARK: -

extension LongTextViewController: DatabaseChangeDelegate {

    public func databaseChangesDidUpdate(databaseChanges: DatabaseChanges) {
        AssertIsOnMainThread()

        guard databaseChanges.didUpdate(interaction: itemViewModel.interaction) else {
            return
        }
        assert(databaseChanges.didUpdateInteractions)

        refreshContent()
    }

    public func databaseChangesDidUpdateExternally() {
        AssertIsOnMainThread()

        refreshContent()
    }

    public func databaseChangesDidReset() {
        AssertIsOnMainThread()

        refreshContent()
    }
}

// MARK: -

extension LongTextViewController: ForwardMessageDelegate {
    public func forwardMessageFlowDidComplete(items: [ForwardMessageItem],
                                              recipientThreads: [TSThread]) {
        dismiss(animated: true) {
            ForwardMessageViewController.finalizeForward(items: items,
                                                         recipientThreads: recipientThreads,
                                                         fromViewController: self)
        }
    }

    public func forwardMessageFlowDidCancel() {
        dismiss(animated: true)
    }
}

// MARK: -

extension LongTextViewController: GroupViewHelperDelegate {
    var currentGroupModel: TSGroupModel? {
        return (threadViewModel.threadRecord as? TSGroupThread)?.groupModel
    }

    func groupViewHelperDidUpdateGroup() {}

    var fromViewController: UIViewController? {
        return self
    }
}
