//
//  ProfileHeaderView.swift
//  Tusker
//
//  Created by Shadowfacts on 7/4/20.
//  Copyright © 2020 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm

protocol ProfileHeaderViewDelegate: TuskerNavigationDelegate, MenuActionProvider {
    func profileHeader(_ headerView: ProfileHeaderView, selectedPageChangedTo newPage: ProfileViewController.Page)
}

class ProfileHeaderView: UIView {
    
    static func create() -> ProfileHeaderView {
        let nib = UINib(nibName: "ProfileHeaderView", bundle: .main)
        return nib.instantiate(withOwner: nil, options: nil).first as! ProfileHeaderView
    }
    
    weak var delegate: ProfileHeaderViewDelegate?
    var mastodonController: MastodonController! { delegate?.apiController }

    @IBOutlet weak var headerImageView: UIImageView!
    @IBOutlet weak var avatarContainerView: UIView!
    @IBOutlet weak var avatarImageView: UIImageView!
    @IBOutlet weak var moreButton: ProfileHeaderButton!
    @IBOutlet weak var followButton: ProfileHeaderButton!
    @IBOutlet weak var displayNameLabel: AccountDisplayNameLabel!
    @IBOutlet weak var usernameLabel: UILabel!
    @IBOutlet weak var lockImageView: UIImageView!
    @IBOutlet weak var vStack: UIStackView!
    @IBOutlet weak var relationshipLabel: UILabel!
    @IBOutlet weak var noteTextView: StatusContentTextView!
    @IBOutlet weak var fieldsView: ProfileFieldsView!
    @IBOutlet weak var followCountButton: UIButton!
    private(set) var pagesSegmentedControl: ScrollingSegmentedControl<ProfileViewController.Page>!
    private var movedOverlayView: ProfileHeaderMovedOverlayView?
    
    var accountID: String!
    
    private var avatarRequest: ImageCache.Request?
    private var headerRequest: ImageCache.Request?
    
    private var isGrayscale = false
    private var followButtonMode = FollowButtonMode.follow {
        didSet {
            var config = followButton.configuration!
            followButtonMode.updateConfig(&config)
            config.showsActivityIndicator = false
            followButton.configuration = config
        }
    }
    
    deinit {
        avatarRequest?.cancel()
        headerRequest?.cancel()
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        backgroundColor = .appBackground
        
        avatarContainerView.backgroundColor = .appBackground
        avatarContainerView.layer.masksToBounds = true
        avatarContainerView.layer.cornerCurve = .continuous
        avatarImageView.layer.masksToBounds = true
        avatarImageView.layer.cornerCurve = .continuous
        avatarImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(avatarPressed)))
        avatarImageView.isUserInteractionEnabled = true
        
        headerImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(headerPressed)))
        headerImageView.isUserInteractionEnabled = true
        
        var config = UIButton.Configuration.plain()
        config.image = UIImage(systemName: "ellipsis")
        moreButton.configuration = config
        moreButton.setNeedsUpdateConfiguration()
        moreButton.addInteraction(UIPointerInteraction(delegate: self))
        moreButton.showsMenuAsPrimaryAction = true
        moreButton.isContextMenuInteractionEnabled = true
        
        followButton.setNeedsUpdateConfiguration()
        followButton.addInteraction(UIPointerInteraction(delegate: self))
        
        displayNameLabel.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: .systemFont(ofSize: 24, weight: .semibold))
        displayNameLabel.adjustsFontForContentSizeCategory = true
        
        usernameLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .light))
        usernameLabel.adjustsFontForContentSizeCategory = true
        
        relationshipLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 14))
        relationshipLabel.adjustsFontForContentSizeCategory = true
        
        noteTextView.defaultFont = .preferredFont(forTextStyle: .body)
        noteTextView.monospaceFont = UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 17, weight: .regular))
        noteTextView.adjustsFontForContentSizeCategory = true
        
        pagesSegmentedControl = ScrollingSegmentedControl(frame: .zero)
        pagesSegmentedControl.options = [
            .init(value: .posts, name: "Posts"),
            .init(value: .postsAndReplies, name: "Posts and Replies"),
            .init(value: .media, name: "Media"),
        ]
        pagesSegmentedControl.setSelectedOption(.posts, animated: false)
        pagesSegmentedControl.didSelectOption = { [unowned self] newPage in
            if let newPage {
                self.delegate?.profileHeader(self, selectedPageChangedTo: newPage)
            }
        }
        vStack.addArrangedSubview(pagesSegmentedControl)
        // equal inset on both sides, the leading inset is applied to the vStack
        pagesSegmentedControl.widthAnchor.constraint(equalTo: vStack.widthAnchor, constant: -16).isActive = true
        
        // the segemented control itself is only focusable when VoiceOver is in Group navigation mode,
        // so make it clear that to switch tabs the user needs to enter the group
        pagesSegmentedControl.accessibilityHint = "Enter group to select scope"
        
        NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
    }
    
    func updateUI(for accountID: String) {
        self.accountID = accountID
        
        guard let mastodonController = mastodonController else { return }
        guard let account = mastodonController.persistentContainer.account(for: accountID) else {
            fatalError("Missing cached account \(accountID)")
        }
        
        updateUIForPreferences()
        
        usernameLabel.text = "@\(account.acct)"
        lockImageView.isHidden = !account.locked
        
        updateImages(account: account)
        
        moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: delegate?.actionsForProfile(accountID: accountID, source: .view(moreButton), fetchRelationship: false) ?? [])
        
        noteTextView.navigationDelegate = delegate
        noteTextView.setTextFromHtml(account.note)
        noteTextView.setEmojis(account.emojis, identifier: account.id)
        
        if accountID == mastodonController.account?.id {
            followButton.isHidden = true
        } else {
            followButton.isHidden = false
            
            // while fetching the most up-to-date, show the current data (if any)
            updateRelationship()
        }
        
        fieldsView.delegate = delegate
        fieldsView.updateUI(account: account)
        
        let (followingAbbr, followingSpelledOut) = formatBigNumber(account.followingCount)
        let (followersAbbr, followersSpelledOut) = formatBigNumber(account.followersCount)
        let followCountTitle = NSMutableAttributedString()
        followCountTitle.append(NSAttributedString(string: followingAbbr, attributes: [
            .font: UIFont.preferredFont(forTextStyle: .body).withTraits(.traitBold)!,
        ]))
        followCountTitle.append(NSAttributedString(string: " Following, ", attributes: [
            .foregroundColor: UIColor.secondaryLabel,
        ]))
        followCountTitle.append(NSAttributedString(string: followersAbbr, attributes: [
            .font: UIFont.preferredFont(forTextStyle: .body).withTraits(.traitBold)!,
        ]))
        followCountTitle.append(NSAttributedString(string: " Follower\(account.followersCount == 1 ? "" : "s")", attributes: [
            .foregroundColor: UIColor.secondaryLabel,
        ]))
        followCountButton.setAttributedTitle(followCountTitle, for: .normal)
        followCountButton.accessibilityLabel = "\(followingSpelledOut) following, \(followersSpelledOut) followers"
        
        if let movedTo = account.movedTo {
            if let movedOverlayView {
                movedOverlayView.updateUI(movedTo: movedTo)
            } else {
                let overlay = createMovedOverlayView(movedTo: movedTo)
                movedOverlayView = overlay
                
                accessibilityElements = [
                    overlay,
                ]
            }
        } else {
            movedOverlayView?.removeFromSuperview()
            movedOverlayView = nil
            
            accessibilityElements = [
                displayNameLabel!,
                usernameLabel!,
                relationshipLabel!,
                noteTextView!,
                fieldsView!,
                moreButton!,
                pagesSegmentedControl!,
            ]
        }
    }
    
    private func createMovedOverlayView(movedTo: AccountMO) -> ProfileHeaderMovedOverlayView {
        let overlay = ProfileHeaderMovedOverlayView()
        overlay.delegate = delegate
        overlay.updateUI(movedTo: movedTo)
        overlay.translatesAutoresizingMaskIntoConstraints = false
        addSubview(overlay)
        let bottomConstraint = overlay.bottomAnchor.constraint(equalTo: bottomAnchor)
        NSLayoutConstraint.activate([
            overlay.leadingAnchor.constraint(equalTo: leadingAnchor),
            overlay.trailingAnchor.constraint(equalTo: trailingAnchor),
            overlay.topAnchor.constraint(equalTo: topAnchor),
            bottomConstraint,
        ])
        
        overlay.collapse = { [weak self, weak overlay] in
            guard let self, let overlay else { return }
            bottomConstraint.isActive = false
            overlay.bottomAnchor.constraint(equalTo: self.avatarContainerView.topAnchor, constant: -2).isActive = true
            let animator = UIViewPropertyAnimator(duration: 0.35, dampingRatio: 0.8)
            animator.addAnimations {
                self.layoutIfNeeded()
                overlay.collapseButton.layer.opacity = 0
            }
            animator.addCompletion { _ in
                overlay.collapseButton.layer.opacity = 1
                overlay.collapseButton?.removeFromSuperview()
            }
            animator.startAnimation()
        }
        
        return overlay
    }
    
    private func updateRelationship() {
        guard let mastodonController = mastodonController,
              let relationship = mastodonController.persistentContainer.relationship(forAccount: accountID) else {
            relationshipLabel.isHidden = true
            followButton.isEnabled = false
            followButtonMode = .follow
            return
        }
        
        var relationshipStr: String?
        if relationship.following && relationship.followedBy {
            relationshipStr = "You follow each other"
        } else if relationship.following {
            relationshipStr = "You follow"
        } else if relationship.followedBy {
            relationshipStr = "Follows you"
        } else if relationship.blocking {
            relationshipStr = "You block"
        }
        if let relationshipStr {
            relationshipLabel.text = relationshipStr
            relationshipLabel.isHidden = false
        } else {
            relationshipLabel.isHidden = true
        }
        
        if relationship.blocking || relationship.domainBlocking {
            followButton.isEnabled = false
            followButtonMode = .blocked
        } else {
            followButton.isEnabled = true
            if relationship.following {
                followButtonMode = .unfollow
            } else if relationship.requested {
                followButtonMode = .cancelRequest
            } else {
                followButtonMode = .follow
            }
        }
    }
    
    @objc private func updateUIForPreferences() {
        guard let mastodonController = mastodonController,
              // nil if prefs changed before own account is loaded
              let accountID = accountID,
              let account = mastodonController.persistentContainer.account(for: accountID) else {
            return
        }
        
        avatarContainerView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarContainerView)
        avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
        displayNameLabel.updateForAccountDisplayName(account: account)
        
        if isGrayscale != Preferences.shared.grayscaleImages {
            updateImages(account: account)
        }
    }
    
    private func updateImages(account: AccountMO) {
        isGrayscale = Preferences.shared.grayscaleImages
        
        let accountID = account.id
        if let avatarURL = account.avatar {
            // always load original for avatars, because ImageCache.avatars stores them scaled-down in memory
            avatarRequest = ImageCache.avatars.get(avatarURL, loadOriginal: true) { [weak self] (_, image) in
                guard let self = self,
                      let image = image,
                      self.accountID == accountID,
                      let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else {
                    DispatchQueue.main.async {
                        self?.avatarRequest = nil
                    }
                    return
                }
                
                DispatchQueue.main.async {
                    self.avatarRequest = nil
                    self.avatarImageView.image = transformedImage
                }
            }
        }
        if let header = account.header {
            headerRequest = ImageCache.headers.get(header) { [weak self] (_, image) in
                guard let self = self,
                      let image = image,
                      self.accountID == accountID,
                      let transformedImage = ImageGrayscalifier.convertIfNecessary(url: header, image: image) else {
                    DispatchQueue.main.async {
                        self?.headerRequest = nil
                    }
                    return
                }
                
                DispatchQueue.main.async {
                    self.headerRequest = nil
                    self.headerImageView.image = transformedImage
                }
            }
        }
    }
    
    private func formatBigNumber(_ value: Int) -> (String, String) {
        let formatter = NumberFormatter()
        formatter.maximumFractionDigits = 1
        
        for (threshold, abbr, spelledOut) in [(1_000_000, "m", "million"), (1_000, "k", "thousand")] {
            if value >= threshold {
                let frac = Double(value) / Double(threshold)
                let s = formatter.string(from: frac as NSNumber)!
                return ("\(s)\(abbr)", "\(s) \(spelledOut)")
            }
        }
        
        let s = formatter.string(from: value as NSNumber)!
        return (s, s)
    }
    
    // MARK: Interaction
    
    @objc func avatarPressed() {
        guard let account = mastodonController.persistentContainer.account(for: accountID),
              let avatar = account.avatar else {
            return
        }
        delegate?.showLoadingLargeImage(url: avatar, cache: .avatars, description: nil, animatingFrom: avatarImageView)
    }
    
    @objc func headerPressed() {
        guard let account = mastodonController.persistentContainer.account(for: accountID),
              let header = account.header else {
            return
        }
        delegate?.showLoadingLargeImage(url: header, cache: .headers, description: nil, animatingFrom: headerImageView)
    }
    
    @IBAction func followPressed() {
        let req: Request<Relationship>
        let action: String
        switch followButtonMode {
        case .follow:
            req = Account.follow(accountID)
            action = "Following"
        case .unfollow, .cancelRequest:
            req = Account.unfollow(accountID)
            action = followButtonMode == .unfollow ? "Unfollowing" : "Cancelling Request"
        case .blocked:
            return
        }
        followButton.configuration!.showsActivityIndicator = true
        followButton.isEnabled = false
        UIImpactFeedbackGenerator(style: .light).impactOccurred()
        Task {
            do {
                let (relationship, _) = try await mastodonController.run(req)
                mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
                // don't need to update the button, since the relationship observer will do so anyways
            } catch {
                followButton.isEnabled = true
                followButton.configuration!.showsActivityIndicator = false
                if let delegate = self.delegate {
                    let config = ToastConfiguration(from: error, with: "Error \(action)", in: delegate) { toast in
                        toast.dismissToast(animated: true)
                        self.followPressed()
                    }
                    delegate.showToast(configuration: config, animated: true)
                }
            }
        }
    }
    
    @IBAction func followCountButtonPressed(_ sender: Any) {
        guard let accountID else { return }
        delegate?.show(AccountFollowsViewController(accountID: accountID, mastodonController: mastodonController))
    }
}

extension ProfileHeaderView {
    enum FollowButtonMode {
        case follow, unfollow, cancelRequest, blocked
        
        func updateConfig(_ config: inout UIButton.Configuration) {
            switch self {
            case .follow:
                config.title = "Follow"
                config.image = UIImage(systemName: "person.badge.plus")
            case .unfollow:
                config.title = "Unfollow"
                config.image = UIImage(systemName: "person.badge.minus")
            case .cancelRequest:
                config.title = "Cancel Request"
                config.image = UIImage(systemName: "person.badge.clock")
            case .blocked:
                config.title = "Blocked"
                config.image = UIImage(systemName: "circle.slash")
            }
        }
    }
}

extension ProfileHeaderView: UIPointerInteractionDelegate {
    func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? {
        let preview = UITargetedPreview(view: interaction.view!)
        return UIPointerStyle(effect: .lift(preview), shape: .none)
    }
}
