//
//  ConversationMainStatusCollectionViewCell.swift
//  Tusker
//
//  Created by Shadowfacts on 1/20/23.
//  Copyright © 2023 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm
import Combine

class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, StatusCollectionViewCell {
    
    static let contentFont = UIFontMetrics.default.scaledFont(for: .systemFont(ofSize: 18))
    static let monospaceFont = UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 18, weight: .regular))
    static let contentParagraphStyle = HTMLConverter.defaultParagraphStyle
    static let metaFont = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .systemFont(ofSize: 15))
    
    static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
       formatter.dateStyle = .medium
       formatter.timeStyle = .medium
       return formatter
   }()
    
    // MARK: Subviews
    
    private static let avatarImageViewSize: CGFloat = 50
    private(set) lazy var avatarImageView = CachedImageView(cache: .avatars).configure {
        $0.contentMode = .scaleAspectFill
        $0.layer.masksToBounds = true
        $0.layer.cornerCurve = .continuous
        NSLayoutConstraint.activate([
            $0.heightAnchor.constraint(equalToConstant: ConversationMainStatusCollectionViewCell.avatarImageViewSize),
            $0.widthAnchor.constraint(equalToConstant: ConversationMainStatusCollectionViewCell.avatarImageViewSize),
        ])
        $0.isUserInteractionEnabled = true
        $0.addInteraction(UIPointerInteraction(delegate: self))
        $0.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
    }
    
    let displayNameLabel = AccountDisplayNameLabel().configure {
        $0.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: .systemFont(ofSize: 24, weight: .semibold))
        $0.adjustsFontForContentSizeCategory = true
    }
    
    private let metaIndicatorsView = StatusMetaIndicatorsView().configure {
        $0.allowedIndicators = [.visibility, .localOnly]
        $0.squeezeHorizontal = true
        $0.primaryAxis = .horizontal
    }
    
    let usernameLabel = UILabel().configure {
        $0.textColor = .secondaryLabel
        $0.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: .systemFont(ofSize: 17, weight: .light))
        $0.adjustsFontForContentSizeCategory = true
    }
    
    private lazy var accountDetailContainerView = UIView().configure {
        $0.backgroundColor = .clear
        $0.addSubview(avatarImageView)
        $0.addSubview(displayNameLabel)
        $0.addSubview(metaIndicatorsView)
        $0.addSubview(usernameLabel)
        avatarImageView.translatesAutoresizingMaskIntoConstraints = false
        displayNameLabel.translatesAutoresizingMaskIntoConstraints = false
        metaIndicatorsView.translatesAutoresizingMaskIntoConstraints = false
        usernameLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            avatarImageView.leadingAnchor.constraint(equalTo: $0.leadingAnchor),
            avatarImageView.topAnchor.constraint(equalTo: $0.topAnchor),
            avatarImageView.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
            
            displayNameLabel.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 8),
            displayNameLabel.trailingAnchor.constraint(equalTo: metaIndicatorsView.leadingAnchor, constant: -8),
            displayNameLabel.topAnchor.constraint(equalTo: $0.topAnchor),
            
            metaIndicatorsView.topAnchor.constraint(equalTo: $0.topAnchor),
            metaIndicatorsView.trailingAnchor.constraint(equalTo: $0.trailingAnchor),
            
            usernameLabel.leadingAnchor.constraint(equalTo: displayNameLabel.leadingAnchor),
            usernameLabel.trailingAnchor.constraint(equalTo: $0.trailingAnchor),
            usernameLabel.topAnchor.constraint(equalTo: displayNameLabel.bottomAnchor),
            usernameLabel.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
        ])
        $0.isUserInteractionEnabled = true
        $0.addInteraction(UIContextMenuInteraction(delegate: self))
        $0.addInteraction(UIDragInteraction(delegate: self))
        $0.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
    }
    private lazy var accountDetailAccessibilityElement = ConversationMainStatusAccountDetailAccessibilityElement(accessibilityContainer: self)
    
    private(set) lazy var contentWarningLabel = EmojiLabel().configure {
        $0.numberOfLines = 0
        $0.textColor = .secondaryLabel
        $0.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).addingAttributes([
            .traits: [
                UIFontDescriptor.TraitKey.weight: UIFont.Weight.bold.rawValue,
            ]
        ]), size: 0)
        $0.adjustsFontForContentSizeCategory = true
        // this needs to have a higher priorty than the content container's zero height constraint
        $0.setContentHuggingPriority(.defaultHigh, for: .vertical)
        $0.isUserInteractionEnabled = true
        $0.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(collapseButtonPressed)))
    }
    
    private(set) lazy var collapseButton = StatusCollapseButton(configuration: {
        var config = UIButton.Configuration.filled()
        config.image = UIImage(systemName: "chevron.down")
        return config
    }()).configure {
        // this button is so big that dimming its background color is visually distracting
        $0.tintAdjustmentMode = .normal
        $0.setContentHuggingPriority(.defaultHigh, for: .vertical)
        $0.addTarget(self, action: #selector(collapseButtonPressed), for: .touchUpInside)
    }
    
    let contentContainer = StatusContentContainer<StatusContentTextView, StatusPollView>(useTopSpacer: true).configure {
        $0.contentTextView.defaultFont = ConversationMainStatusCollectionViewCell.contentFont
        $0.contentTextView.monospaceFont = ConversationMainStatusCollectionViewCell.monospaceFont
        $0.contentTextView.paragraphStyle = ConversationMainStatusCollectionViewCell.contentParagraphStyle
        $0.contentTextView.isSelectable = true
        $0.contentTextView.dataDetectorTypes = [.flightNumber, .address, .shipmentTrackingNumber, .phoneNumber]
        if #available(iOS 16.0, *) {
            $0.contentTextView.dataDetectorTypes.formUnion([.money, .physicalValue])
        }
        $0.setContentHuggingPriority(.defaultLow, for: .vertical)
    }
    
    private lazy var favoritesCountButton = UIButton().configure {
        $0.titleLabel!.adjustsFontForContentSizeCategory = true
        $0.addTarget(self, action: #selector(favoritesCountPressed), for: .touchUpInside)
        $0.isPointerInteractionEnabled = true
    }
    
    private lazy var reblogsCountButton = UIButton().configure {
        $0.titleLabel!.adjustsFontForContentSizeCategory = true
        $0.addTarget(self, action: #selector(reblogsCountPressed), for: .touchUpInside)
        $0.isPointerInteractionEnabled = true
    }
    
    // using a UIStackView for this does not layout correctly on the first pass
    // (everything is shifted slightly to the right for some reason)
    // so do it manually, since there are only two subvviews
    private lazy var actionsCountHStack = UIView().configure {
        reblogsCountButton.translatesAutoresizingMaskIntoConstraints = false
        $0.addSubview(reblogsCountButton)
        favoritesCountButton.translatesAutoresizingMaskIntoConstraints = false
        $0.addSubview(favoritesCountButton)
        NSLayoutConstraint.activate([
            reblogsCountButton.leadingAnchor.constraint(equalTo: $0.leadingAnchor),
            reblogsCountButton.topAnchor.constraint(equalTo: $0.topAnchor),
            reblogsCountButton.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
            favoritesCountButton.leadingAnchor.constraint(equalTo: reblogsCountButton.trailingAnchor, constant: 8),
            favoritesCountButton.topAnchor.constraint(equalTo: $0.topAnchor),
            favoritesCountButton.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
            favoritesCountButton.trailingAnchor.constraint(equalTo: $0.trailingAnchor),
        ])
    }
    
    private let timestampAndClientLabel = UILabel().configure {
        $0.textColor = .secondaryLabel
        $0.font = ConversationMainStatusCollectionViewCell.metaFont
        $0.adjustsFontForContentSizeCategory = true
    }
    
    private lazy var editTimestampButton = UIButton().configure {
        $0.titleLabel!.adjustsFontForContentSizeCategory = true
        $0.addTarget(self, action: #selector(editTimestampPressed), for: .touchUpInside)
        $0.isPointerInteractionEnabled = true
    }
    
    private let firstSeparator = UIView().configure {
        $0.backgroundColor = .separator
        NSLayoutConstraint.activate([
            $0.heightAnchor.constraint(equalToConstant: 0.5),
        ])
    }
    private let secondSeparator = UIView().configure {
        $0.backgroundColor = .separator
        NSLayoutConstraint.activate([
            $0.heightAnchor.constraint(equalToConstant: 0.5),
        ])
    }
    
    private lazy var metaVStack = UIStackView(arrangedSubviews: [
        firstSeparator,
        actionsCountHStack,
        timestampAndClientLabel,
        editTimestampButton,
        secondSeparator,
    ]).configure {
        $0.axis = .vertical
        $0.spacing = 4
        $0.alignment = .leading
    }
    
    private(set) lazy var replyButton = UIButton().configure {
        $0.setImage(UIImage(systemName: "arrowshape.turn.up.left.fill"), for: .normal)
        $0.addTarget(self, action: #selector(replyPressed), for: .touchUpInside)
        $0.addInteraction(UIPointerInteraction(delegate: self))
    }
    
    private(set) lazy var favoriteButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
        $0.setImage(UIImage(systemName: "star.fill"), for: .normal)
        $0.addTarget(self, action: #selector(favoritePressed), for: .touchUpInside)
        $0.addInteraction(UIPointerInteraction(delegate: self))
    }
    
    private(set) lazy var reblogButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
        $0.setImage(UIImage(systemName: "repeat"), for: .normal)
        $0.addTarget(self, action: #selector(reblogPressed), for: .touchUpInside)
        $0.addInteraction(UIPointerInteraction(delegate: self))
    }
    
    private(set) lazy var moreButton = UIButton().configure {
        $0.setImage(UIImage(systemName: "ellipsis"), for: .normal)
        $0.showsMenuAsPrimaryAction = true
        $0.addInteraction(UIPointerInteraction(delegate: self))
    }
    
    private var actionButtons: [UIButton] {
        [replyButton, reblogButton, favoriteButton, moreButton]
    }
    
    private lazy var actionsHStack = UIStackView(arrangedSubviews: [
        replyButton,
        reblogButton,
        favoriteButton,
        moreButton,
    ]).configure {
        $0.axis = .horizontal
        $0.distribution = .fillEqually
        NSLayoutConstraint.activate([
            $0.heightAnchor.constraint(equalToConstant: 26),
        ])
    }
    
    private let accountDetailToContentWarningSpacer = UIView().configure {
        $0.backgroundColor = .clear
        $0.heightAnchor.constraint(equalToConstant: 4).isActive = true
    }
    private let contentWarningToCollapseButtonSpacer = UIView().configure {
        $0.backgroundColor = .clear
        $0.heightAnchor.constraint(equalToConstant: 4).isActive = true
    }
    private let contentToMetaSpacer = UIView().configure {
        $0.backgroundColor = .clear
        $0.heightAnchor.constraint(equalToConstant: 8).isActive = true
    }
    private let metaToActionsSpacer = UIView().configure {
        $0.backgroundColor = .clear
        $0.heightAnchor.constraint(equalToConstant: 8).isActive = true
    }
    
    private lazy var mainVStack = UIStackView(arrangedSubviews: [
        accountDetailContainerView,
        accountDetailToContentWarningSpacer,
        contentWarningLabel,
        contentWarningToCollapseButtonSpacer,
        collapseButton,
        contentContainer,
        contentToMetaSpacer,
        metaVStack,
        metaToActionsSpacer,
        actionsHStack,
    ]).configure {
        $0.axis = .vertical
        $0.spacing = 0
        $0.alignment = .leading
        
        NSLayoutConstraint.activate([
            accountDetailContainerView.widthAnchor.constraint(equalTo: $0.widthAnchor),
            contentWarningLabel.widthAnchor.constraint(equalTo: $0.widthAnchor),
            collapseButton.widthAnchor.constraint(equalTo: $0.widthAnchor),
            contentContainer.widthAnchor.constraint(equalTo: $0.widthAnchor),
            firstSeparator.widthAnchor.constraint(equalTo: $0.widthAnchor),
            secondSeparator.widthAnchor.constraint(equalTo: $0.widthAnchor),
            actionsHStack.widthAnchor.constraint(equalTo: $0.widthAnchor),
        ])
    }
    
    var prevThreadLinkView: UIView?
    var nextThreadLinkView: UIView?
    
    // MARK: Cell State
    var mastodonController: MastodonController! { delegate?.apiController }
    weak var delegate: StatusCollectionViewCellDelegate?
    var showStatusAutomatically = false
    
    var statusID: String!
    var statusState: CollapseState!
    var accountID: String!
    
    var isGrayscale = false
    private var hasCreatedObservers = false
    var cancellables = Set<AnyCancellable>()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        // don't show selection background, because this cell isn't selectable
        automaticallyUpdatesBackgroundConfiguration = false
        
        mainVStack.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(mainVStack)
        
        NSLayoutConstraint.activate([
            mainVStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            mainVStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            mainVStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
            mainVStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
        ])
        
        accessibilityElements = [
            accountDetailAccessibilityElement,
            contentWarningLabel,
            collapseButton,
            contentContainer.contentTextView,
            contentContainer.attachmentsView,
            contentContainer.pollView,
            favoritesCountButton,
            reblogsCountButton,
            timestampAndClientLabel,
            replyButton,
            favoriteButton,
            reblogButton,
            moreButton,
        ]
        
        NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func updateConfiguration(using state: UICellConfigurationState) {
        var config = UIBackgroundConfiguration.listPlainCell().updated(for: state)
        // conv main status isn't selectable
        if !state.isFocused {
            config.backgroundColor = .appBackground
        }
        backgroundConfiguration = config
    }
    
    // MARK: Configure UI
    
    func updateUI(statusID: String, state: CollapseState) {
        guard let status = mastodonController.persistentContainer.status(for: statusID) else {
            fatalError()
        }
        
        createObservers()
        
        self.statusID = statusID
        self.statusState = state
        
        doUpdateUI(status: status)
        accountDetailToContentWarningSpacer.isHidden = collapseButton.isHidden
        contentWarningToCollapseButtonSpacer.isHidden = collapseButton.isHidden
        
        accountDetailAccessibilityElement.navigationDelegate = delegate
        accountDetailAccessibilityElement.accountID = accountID
        
        var timestampAndClientText = ConversationMainStatusCollectionViewCell.dateFormatter.string(from: status.createdAt)
        if let application = status.applicationName {
            timestampAndClientText += " • \(application)"
        }
        timestampAndClientLabel.text = timestampAndClientText
        
        if let editedAt = status.editedAt {
            editTimestampButton.isHidden = false
            var config = UIButton.Configuration.plain()
            config.baseForegroundColor = .secondaryLabel
            config.attributedTitle = AttributedString("Edited on \(ConversationMainStatusCollectionViewCell.dateFormatter.string(from: editedAt))", attributes: AttributeContainer([
                .font: ConversationMainStatusCollectionViewCell.metaFont
            ]))
            config.contentInsets = .zero
            editTimestampButton.configuration = config
        } else {
            editTimestampButton.isHidden = true
        }
    }
    
    private func createObservers() {
        guard !hasCreatedObservers else {
            return
        }
        hasCreatedObservers = true
        baseCreateObservers()
    }
    
    func updateStatusState(status: StatusMO) {
        baseUpdateStatusState(status: status)
        
        let attributes = AttributeContainer([
            .font: ConversationMainStatusCollectionViewCell.metaFont
        ])
        
        let favoritesFormat = NSLocalizedString("favorites count", comment: "conv main status favorites button label")
        var favoritesConfig = UIButton.Configuration.plain()
        favoritesConfig.baseForegroundColor = .secondaryLabel
        favoritesConfig.attributedTitle = AttributedString(String.localizedStringWithFormat(favoritesFormat, status.favouritesCount), attributes: attributes)
        favoritesConfig.contentInsets = .zero
        favoritesCountButton.configuration = favoritesConfig
        
        let reblogsFormat = NSLocalizedString("reblogs count", comment: "conv main status reblogs button label")
        var reblogsConfig = UIButton.Configuration.plain()
        reblogsConfig.baseForegroundColor = .secondaryLabel
        reblogsConfig.attributedTitle = AttributedString(String.localizedStringWithFormat(reblogsFormat, status.reblogsCount), attributes: attributes)
        reblogsConfig.contentInsets = .zero
        reblogsCountButton.configuration = reblogsConfig
    }
    
    func estimateContentHeight() -> CGFloat {
        let width = bounds.width - 2*16
        return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: width)
    }
    
    func updateUIForPreferences(status: StatusMO) {
        baseUpdateUIForPreferences(status: status)
    }
    
    @objc private func preferencesChanged() {
        guard let mastodonController,
              let statusID,
              let status = mastodonController.persistentContainer.status(for: statusID) else {
            return
        }
        
        updateUIForPreferences(status: status)
        
        if isGrayscale != Preferences.shared.grayscaleImages {
            updateGrayscaleableUI(status: status)
        }
        
        if actionsCountHStack.isHidden != !Preferences.shared.showFavoriteAndReblogCounts {
            actionsCountHStack.isHidden = !Preferences.shared.showFavoriteAndReblogCounts
            delegate?.statusCellNeedsReconfigure(self, animated: true, completion: nil)
        }
    }
    
    // MARK: Interaction
    
    @objc private func accountPressed() {
        delegate?.selected(account: accountID)
    }
    
    @objc private func collapseButtonPressed() {
        toggleCollapse()
    }
    
    @objc private func favoritesCountPressed() {
        guard let delegate else {
            return
        }
        let vc = StatusActionAccountListViewController(actionType: .favorite, statusID: statusID, statusState: statusState.copy(), accountIDs: nil, mastodonController: mastodonController)
        vc.showInaccurateCountWarning = true
        delegate.show(vc)
    }
    
    @objc private func reblogsCountPressed() {
        guard let delegate else {
            return
        }
        let vc = StatusActionAccountListViewController(actionType: .reblog, statusID: statusID, statusState: statusState.copy(), accountIDs: nil, mastodonController: mastodonController)
        vc.showInaccurateCountWarning = true
        delegate.show(vc)
    }
    
    @objc private func replyPressed() {
        delegate?.compose(inReplyToID: statusID)
    }
    
    @objc private func favoritePressed() {
        toggleFavorite()
    }
    
    @objc private func reblogPressed() {
        toggleReblog()
    }
    
    @objc private func editTimestampPressed() {
        delegate?.show(StatusEditHistoryViewController(statusID: statusID, mastodonController: mastodonController), sender: nil)
    }
    
}

private class ConversationMainStatusAccountDetailAccessibilityElement: UIAccessibilityElement {
    var navigationDelegate: TuskerNavigationDelegate!
    var mastodonController: MastodonController { navigationDelegate.apiController }
    var accountID: String!
    
    override var accessibilityLabel: String? {
        get { mastodonController.persistentContainer.account(for: accountID)?.displayNameWithoutCustomEmoji }
        set {}
    }
    
    override var accessibilityHint: String? {
        get { "Double tap to show profile." }
        set {}
    }
    
    override func accessibilityActivate() -> Bool {
        navigationDelegate.selected(account: accountID)
        return true
    }
}

extension ConversationMainStatusCollectionViewCell: UIContextMenuInteractionDelegate {
    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
        return contextMenuConfigurationForAccount(sourceView: accountDetailContainerView)
    }
    
    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
        if let delegate {
            MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: delegate)
        }
    }
}

extension ConversationMainStatusCollectionViewCell: UIDragInteractionDelegate {
    func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
        return dragItemsForAccount()
    }
}

extension ConversationMainStatusCollectionViewCell: UIPointerInteractionDelegate {
    func pointerInteraction(_ interaction: UIPointerInteraction, regionFor request: UIPointerRegionRequest, defaultRegion: UIPointerRegion) -> UIPointerRegion? {
        if interaction.view == avatarImageView {
            return defaultRegion
        } else if let button = interaction.view as? UIButton,
                  actionButtons.contains(button) {
            var rect = button.convert(button.imageView!.bounds, to: button.imageView!)
            rect = rect.insetBy(dx: -24, dy: -24)
            return UIPointerRegion(rect: rect)
        }
        return nil
    }
    
    func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? {
        if interaction.view == avatarImageView {
            let preview = UITargetedPreview(view: avatarImageView)
            return UIPointerStyle(effect: .lift(preview))
        } else if let button = interaction.view as? UIButton,
                  actionButtons.contains(button) {
            let preview = UITargetedPreview(view: button.imageView!)
            var rect = button.convert(button.imageView!.bounds, to: button.imageView!)
            rect = rect.insetBy(dx: -24, dy: -24)
            return UIPointerStyle(effect: .highlight(preview), shape: .roundedRect(rect))
        }
        return nil
    }
}
