//
//  ProfileViewController.swift
//  Mastodon
//
//  Created by MainasuK Cirno on 2021-2-23.
//

import UIKit
import Combine
import MastodonMeta
import MetaTextKit
import MastodonAsset
import MastodonCore
import MastodonUI
import MastodonLocalization
import CoreDataStack
import TabBarPager
import XLPagerTabStrip

protocol ProfileViewModelEditable {
    var isEdited: Bool { get }
}

final class ProfileViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
    
    public static let containerViewMarginForRegularHorizontalSizeClass: CGFloat = 64
    public static let containerViewMarginForCompactHorizontalSizeClass: CGFloat = 16
    
    weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
    weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
    
    var disposeBag = Set<AnyCancellable>()
    var viewModel: ProfileViewModel!
    
    let mediaPreviewTransitionController = MediaPreviewTransitionController()
    
    private(set) lazy var cancelEditingBarButtonItem: UIBarButtonItem = {
        let barButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ProfileViewController.cancelEditingBarButtonItemPressed(_:)))
        barButtonItem.tintColor = .white
        return barButtonItem
    }()

    private(set) lazy var settingBarButtonItem: UIBarButtonItem = {
        let barButtonItem = UIBarButtonItem(
            image: Asset.ObjectsAndTools.gear.image.withRenderingMode(.alwaysTemplate),
            style: .plain,
            target: self,
            action: #selector(ProfileViewController.settingBarButtonItemPressed(_:))
        )
        barButtonItem.tintColor = .white
        barButtonItem.accessibilityLabel = L10n.Common.Controls.Actions.settings
        return barButtonItem
    }()

    private(set) lazy var shareBarButtonItem: UIBarButtonItem = {
        let barButtonItem = UIBarButtonItem(
            image: Asset.Arrow.squareAndArrowUp.image.withRenderingMode(.alwaysTemplate),
            style: .plain,
            target: self,
            action: #selector(ProfileViewController.shareBarButtonItemPressed(_:))
        )
        barButtonItem.tintColor = .white
        barButtonItem.accessibilityLabel = L10n.Common.Controls.Actions.share
        return barButtonItem
    }()

    private(set) lazy var favoriteBarButtonItem: UIBarButtonItem = {
        let barButtonItem = UIBarButtonItem(
            image: Asset.ObjectsAndTools.star.image.withRenderingMode(.alwaysTemplate),
            style: .plain,
            target: self,
            action: #selector(ProfileViewController.favoriteBarButtonItemPressed(_:))
        )
        barButtonItem.tintColor = .white
        barButtonItem.accessibilityLabel = L10n.Scene.Favorite.title
        return barButtonItem
    }()
    
    private(set) lazy var bookmarkBarButtonItem: UIBarButtonItem = {
        let barButtonItem = UIBarButtonItem(
            image: Asset.ObjectsAndTools.bookmark.image.withRenderingMode(.alwaysTemplate),
            style: .plain,
            target: self,
            action: #selector(ProfileViewController.bookmarkBarButtonItemPressed(_:))
        )
        barButtonItem.tintColor = .white
        barButtonItem.accessibilityLabel = L10n.Scene.Bookmark.title
        return barButtonItem
    }()

    private(set) lazy var replyBarButtonItem: UIBarButtonItem = {
        let barButtonItem = UIBarButtonItem(image: UIImage(systemName: "arrowshape.turn.up.left"), style: .plain, target: self, action: #selector(ProfileViewController.replyBarButtonItemPressed(_:)))
        barButtonItem.tintColor = .white
        barButtonItem.accessibilityLabel = L10n.Common.Controls.Actions.reply
        return barButtonItem
    }()

    let moreMenuBarButtonItem: UIBarButtonItem = {
        let barButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: nil, action: nil)
        barButtonItem.tintColor = .white
        barButtonItem.accessibilityLabel = L10n.Common.Controls.Actions.seeMore
        return barButtonItem
    }()
    
    private(set) lazy var followedTagsBarButtonItem: UIBarButtonItem = {
        let barButtonItem = UIBarButtonItem(image: UIImage(systemName: "number"), style: .plain, target: self, action: #selector(ProfileViewController.followedTagsItemPressed(_:)))
        barButtonItem.tintColor = .white
        barButtonItem.accessibilityLabel = L10n.Scene.FollowedTags.title
        return barButtonItem
    }()

    let refreshControl: RefreshControl = {
        let refreshControl = RefreshControl()
        refreshControl.tintColor = .white
        return refreshControl
    }()
    
    private(set) lazy var tabBarPagerController = TabBarPagerController()

    private(set) lazy var profileHeaderViewController: ProfileHeaderViewController = {
        let viewController = ProfileHeaderViewController()
        viewController.context = context
        viewController.coordinator = coordinator
        viewController.viewModel = ProfileHeaderViewModel(context: context, authContext: viewModel.authContext)
        return viewController
    }()
    
    private(set) lazy var profilePagingViewController: ProfilePagingViewController = {
        let profilePagingViewController = ProfilePagingViewController()
        profilePagingViewController.viewModel = {
            let profilePagingViewModel = ProfilePagingViewModel(
                postsUserTimelineViewModel: viewModel.postsUserTimelineViewModel,
                repliesUserTimelineViewModel: viewModel.repliesUserTimelineViewModel,
                mediaUserTimelineViewModel: viewModel.mediaUserTimelineViewModel,
                profileAboutViewModel: viewModel.profileAboutViewModel
            )
            profilePagingViewModel.viewControllers.forEach { viewController in
                if let viewController = viewController as? NeedsDependency {
                    viewController.context = context
                    viewController.coordinator = coordinator
                }
            }
            return profilePagingViewModel
        }()
        return profilePagingViewController
    }()

    // title view nested in header
    var titleView: DoubleTitleLabelNavigationBarTitleView {
        profileHeaderViewController.titleView
    }
    

}

extension ProfileViewController {
    
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }

    override func viewSafeAreaInsetsDidChange() {
        super.viewSafeAreaInsetsDidChange()

        profileHeaderViewController.updateHeaderContainerSafeAreaInset(view.safeAreaInsets)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .secondarySystemBackground
        let barAppearance = UINavigationBarAppearance()
        if isModal {
            barAppearance.configureWithDefaultBackground()
        } else {
            barAppearance.configureWithTransparentBackground()
        }
        navigationItem.standardAppearance = barAppearance
        navigationItem.compactAppearance = barAppearance
        navigationItem.scrollEdgeAppearance = barAppearance

        navigationItem.titleView = titleView

        let editingAndUpdatingPublisher = Publishers.CombineLatest(
            viewModel.$isEditing,
            viewModel.$isUpdating
        )
        // note: not add .share() here

        let barButtonItemHiddenPublisher = Publishers.CombineLatest3(
            viewModel.$isMeBarButtonItemsHidden,
            viewModel.$isReplyBarButtonItemHidden,
            viewModel.$isMoreMenuBarButtonItemHidden
        )

        editingAndUpdatingPublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] isEditing, isUpdating in
                guard let self = self else { return }
                self.cancelEditingBarButtonItem.isEnabled = !isUpdating
            }
            .store(in: &disposeBag)

        Publishers.CombineLatest4 (
            viewModel.relationshipViewModel.$isSuspended,
            profileHeaderViewController.viewModel.$isTitleViewDisplaying,
            editingAndUpdatingPublisher.eraseToAnyPublisher(),
            barButtonItemHiddenPublisher.eraseToAnyPublisher()
        )
        .receive(on: DispatchQueue.main)
        .sink { [weak self] isSuspended, isTitleViewDisplaying, tuple1, tuple2 in
            guard let self = self else { return }
            let (isEditing, _) = tuple1
            let (isMeBarButtonItemsHidden, isReplyBarButtonItemHidden, isMoreMenuBarButtonItemHidden) = tuple2

            var items: [UIBarButtonItem] = []
            defer {
                self.navigationItem.rightBarButtonItems = !items.isEmpty ? items : nil
            }

            guard !isSuspended else {
                return
            }

            guard !isEditing else {
                items.append(self.cancelEditingBarButtonItem)
                return
            }

            guard !isTitleViewDisplaying else {
                return
            }

            guard isMeBarButtonItemsHidden else {
                items.append(self.settingBarButtonItem)
                items.append(self.shareBarButtonItem)
                items.append(self.favoriteBarButtonItem)
                items.append(self.bookmarkBarButtonItem)
                
                if self.currentInstance?.canFollowTags == true {
                    items.append(self.followedTagsBarButtonItem)
                }
                
                return
            }

            if !isMoreMenuBarButtonItemHidden {
                items.append(self.moreMenuBarButtonItem)
            }
            if !isReplyBarButtonItemHidden {
                items.append(self.replyBarButtonItem)
            }
        }
        .store(in: &disposeBag)
        
        addChild(tabBarPagerController)
        tabBarPagerController.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tabBarPagerController.view)
        tabBarPagerController.didMove(toParent: self)
        tabBarPagerController.view.pinToParent()

        tabBarPagerController.delegate = self
        tabBarPagerController.dataSource = self
        
        tabBarPagerController.relayScrollView.refreshControl = refreshControl
        refreshControl.addTarget(self, action: #selector(ProfileViewController.refreshControlValueChanged(_:)), for: .valueChanged)
                
        // setup delegate
        profileHeaderViewController.delegate = self
        profilePagingViewController.viewModel.profileAboutViewController.delegate = self
                
        bindViewModel()
        bindTitleView()
        bindMoreBarButtonItem()
        bindPager()

        viewModel.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        navigationController?.navigationBar.prefersLargeTitles = false
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        setNeedsStatusBarAppearanceUpdate()
    }

}

extension ProfileViewController {
    
    private func bindViewModel() {
        // header
        let headerViewModel = profileHeaderViewController.viewModel!
        viewModel.$user
            .assign(to: \.user, on: headerViewModel)
            .store(in: &disposeBag)
        viewModel.$isEditing
            .assign(to: \.isEditing, on: headerViewModel)
            .store(in: &disposeBag)
        viewModel.$isUpdating
            .assign(to: \.isUpdating, on: headerViewModel)
            .store(in: &disposeBag)
        viewModel.relationshipViewModel.$isMyself
            .assign(to: \.isMyself, on: headerViewModel)
            .store(in: &disposeBag)
        viewModel.relationshipViewModel.$optionSet
            .map { $0 ?? .none }
            .assign(to: \.relationshipActionOptionSet, on: headerViewModel)
            .store(in: &disposeBag)
        viewModel.$accountForEdit
            .assign(to: \.accountForEdit, on: headerViewModel)
            .store(in: &disposeBag)
        
        // timeline
        [
            viewModel.postsUserTimelineViewModel,
            viewModel.repliesUserTimelineViewModel,
            viewModel.mediaUserTimelineViewModel,
        ].forEach { userTimelineViewModel in
            viewModel.relationshipViewModel.$isBlocking.assign(to: \.isBlocking, on: userTimelineViewModel).store(in: &disposeBag)
            viewModel.relationshipViewModel.$isBlockingBy.assign(to: \.isBlockedBy, on: userTimelineViewModel).store(in: &disposeBag)
            viewModel.relationshipViewModel.$isSuspended.assign(to: \.isSuspended, on: userTimelineViewModel).store(in: &disposeBag)
        }
    
        // about
        let aboutViewModel = viewModel.profileAboutViewModel
        viewModel.$user
            .assign(to: \.user, on: aboutViewModel)
            .store(in: &disposeBag)
        viewModel.$isEditing
            .assign(to: \.isEditing, on: aboutViewModel)
            .store(in: &disposeBag)
        viewModel.$accountForEdit
            .assign(to: \.accountForEdit, on: aboutViewModel)
            .store(in: &disposeBag)
    }

    private func bindTitleView() {
        Publishers.CombineLatest3(
            profileHeaderViewController.profileHeaderView.viewModel.$name,
            profileHeaderViewController.profileHeaderView.viewModel.$emojiMeta,
            profileHeaderViewController.profileHeaderView.viewModel.$statusesCount
        )
        .receive(on: DispatchQueue.main)
        .sink { [weak self] name, emojiMeta, statusesCount in
            guard let self = self else { return }
            guard let title = name, let statusesCount = statusesCount,
                  let formattedStatusCount = MastodonMetricFormatter().string(from: statusesCount) else {
                self.titleView.isHidden = true
                return
            }
            self.titleView.isHidden = false
            let subtitle = L10n.Plural.Count.MetricFormatted.post(formattedStatusCount, statusesCount)
            let mastodonContent = MastodonContent(content: title, emojis: emojiMeta)
            do {
                let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
                self.titleView.update(titleMetaContent: metaContent, subtitle: subtitle)
            } catch {

            }
        }
        .store(in: &disposeBag)
        profileHeaderViewController.profileHeaderView.viewModel.$name
            .receive(on: DispatchQueue.main)
            .sink { [weak self] name in
                guard let self = self, self.isModal == false else { return }
                self.navigationItem.title = name
            }
            .store(in: &disposeBag)
        Publishers.CombineLatest(
            profileHeaderViewController.viewModel.$user,
            profileHeaderViewController.profileHeaderView.viewModel.viewDidAppear
        )
        .sink { [weak self] (user, _) in
            guard let self, let user else { return }
            Task {
                _ = try await self.context.apiService.fetchUser(
                    username: user.username,
                    domain: user.domainFromAcct,
                    authenticationBox: self.authContext.mastodonAuthenticationBox
                )
            }
        }
        .store(in: &disposeBag)
    }

    private func bindMoreBarButtonItem() {
        Publishers.CombineLatest(
            viewModel.$user,
            viewModel.relationshipViewModel.$optionSet
        )
        .asyncMap { [weak self] user, relationshipSet -> UIMenu? in
            guard let self = self else { return nil }
            guard let user = user else {
                return nil
            }
            let name = user.displayNameWithFallback
            let _ = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)

            var menuActions: [MastodonMenu.Action] = [
                .muteUser(.init(name: name, isMuting: self.viewModel.relationshipViewModel.isMuting)),
                .blockUser(.init(name: name, isBlocking: self.viewModel.relationshipViewModel.isBlocking)),
                .reportUser(.init(name: name)),
                .shareUser(.init(name: name)),
            ]

            if let me = self.viewModel?.me, me.following.contains(user) {
                let showReblogs = me.showingReblogsBy.contains(user)
                let context = MastodonMenu.HideReblogsActionContext(showReblogs: showReblogs)
                menuActions.insert(.hideReblogs(context), at: 1)
            }

            let menu = MastodonMenu.setupMenu(
                actions: [menuActions],
                delegate: self
            )
            return menu
        }
        .sink { [weak self] completion in
            guard let self = self else { return }
            switch completion {
            case .failure:
                self.moreMenuBarButtonItem.menu = nil
            case .finished:
                break
            }
        } receiveValue: { [weak self] menu in
            guard let self = self else { return }
            DispatchQueue.main.async {
              self.moreMenuBarButtonItem.menu = menu
            }
        }
        .store(in: &disposeBag)
    }
    
    private func bindPager() {
        viewModel.$isPagingEnabled
            .receive(on: DispatchQueue.main)
            .sink { [weak self] isPagingEnabled in
                guard let self = self else { return }
                self.profilePagingViewController.containerView.isScrollEnabled = isPagingEnabled
                self.profilePagingViewController.buttonBarView.isUserInteractionEnabled = isPagingEnabled
            }
            .store(in: &disposeBag)
        
        viewModel.$isEditing
            .receive(on: DispatchQueue.main)
            .sink { [weak self] isEditing in
                guard let self = self else { return }
                // set first responder for key command
                if !isEditing {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                        self.profilePagingViewController.becomeFirstResponder()
                    }
                }

                // dismiss keyboard if needs
                if !isEditing { self.view.endEditing(true) }
                
                if isEditing,
                   let index = self.profilePagingViewController.viewControllers.firstIndex(where: { type(of: $0) is ProfileAboutViewController.Type }),
                   self.profilePagingViewController.canMoveTo(index: index)
                {
                    self.profilePagingViewController.moveToViewController(at: index)
                }
            }
            .store(in: &disposeBag)
    }

//    private func bindProfileRelationship() {
//
//        Publishers.CombineLatest3(
//            viewModel.isBlocking.eraseToAnyPublisher(),
//            viewModel.isBlockedBy.eraseToAnyPublisher(),
//            viewModel.suspended.eraseToAnyPublisher()
//        )
//        .receive(on: DispatchQueue.main)
//        .sink { [weak self] isBlocking, isBlockedBy, suspended in
//            guard let self = self else { return }
//            let isNeedSetHidden = isBlocking || isBlockedBy || suspended
//            self.profileHeaderViewController.viewModel.needsSetupBottomShadow.value = !isNeedSetHidden
//            self.profileHeaderViewController.profileHeaderView.bioContainerView.isHidden = isNeedSetHidden
//            self.profileHeaderViewController.viewModel.needsFiledCollectionViewHidden.value = isNeedSetHidden
//            self.profileHeaderViewController.buttonBar.isUserInteractionEnabled = !isNeedSetHidden
//            self.viewModel.needsPagePinToTop.value = isNeedSetHidden
//        }
//        .store(in: &disposeBag)
//    }   // end func bindProfileRelationship

    private func handleMetaPress(_ meta: Meta) {
        switch meta {
        case .url(_, _, let url, _):
            guard let url = URL(string: url) else { return }
            _ = coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
        case .mention(_, _, let userInfo):
            guard let href = userInfo?["href"] as? String,
                  let url = URL(string: href) else { return }
            _ = coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
        case .hashtag(_, let hashtag, _):
            let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authContext: viewModel.authContext, hashtag: hashtag)
            _ = coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show)
        case .email, .emoji:
            break
        }
    }

}

extension ProfileViewController {

    @objc private func cancelEditingBarButtonItemPressed(_ sender: UIBarButtonItem) {
        viewModel.isEditing = false
    }

    @objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
        guard let setting = context.settingService.currentSetting.value else { return }

        _ = coordinator.present(scene: .settings(setting: setting), from: self, transition: .none)
    }

    @objc private func shareBarButtonItemPressed(_ sender: UIBarButtonItem) {
        guard let user = viewModel.user else { return }
        let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
        Task {
            let _activityViewController = try await DataSourceFacade.createActivityViewController(
                dependency: self,
                user: record
            )
            guard let activityViewController = _activityViewController else { return }
            _ = self.coordinator.present(
                scene: .activityViewController(
                    activityViewController: activityViewController,
                    sourceView: nil,
                    barButtonItem: sender
                ),
                from: self,
                transition: .activityViewControllerPresent(animated: true, completion: nil)
            )
        }   // end Task
    }

    @objc private func favoriteBarButtonItemPressed(_ sender: UIBarButtonItem) {
        let favoriteViewModel = FavoriteViewModel(context: context, authContext: viewModel.authContext)
        _ = coordinator.present(scene: .favorite(viewModel: favoriteViewModel), from: self, transition: .show)
    }
    
    @objc private func bookmarkBarButtonItemPressed(_ sender: UIBarButtonItem) {
        let bookmarkViewModel = BookmarkViewModel(context: context, authContext: viewModel.authContext)
        _ = coordinator.present(scene: .bookmark(viewModel: bookmarkViewModel), from: self, transition: .show)
    }

    @objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) {
        guard let mastodonUser = viewModel.user else { return }
        let mention = "@" + mastodonUser.acct
        UITextChecker.learnWord(mention)
        let composeViewModel = ComposeViewModel(
            context: context,
            authContext: viewModel.authContext,
            composeContext: .composeStatus,
            destination: .topLevel,
            initialContent: mention
        )
        _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
    }
    
    @objc private func followedTagsItemPressed(_ sender: UIBarButtonItem) {
        let followedTagsViewModel = FollowedTagsViewModel(context: context, authContext: viewModel.authContext)
        _ = coordinator.present(scene: .followedTags(viewModel: followedTagsViewModel), from: self, transition: .show)
    }

    @objc private func refreshControlValueChanged(_ sender: RefreshControl) {
        if let userTimelineViewController = profilePagingViewController.currentViewController as? UserTimelineViewController {
            userTimelineViewController.viewModel.stateMachine.enter(UserTimelineViewModel.State.Reloading.self)
        }

        // trigger authenticated user account update
        viewModel.context.authenticationService.updateActiveUserAccountPublisher.send()

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
            sender.endRefreshing()
        }
    }

}

// MARK: - TabBarPagerDelegate
extension ProfileViewController: TabBarPagerDelegate {
    
    func tabBarMinimalHeight() -> CGFloat {
        return ProfileHeaderViewController.headerMinHeight
    }
    
    func resetPageContentOffset(_ tabBarPagerController: TabBarPagerController) {
        for viewController in profilePagingViewController.viewModel.viewControllers {
            viewController.pageScrollView.contentOffset = .zero
        }
    }
    
    func tabBarPagerController(_ tabBarPagerController: TabBarPagerController, didScroll scrollView: UIScrollView) {
        // try to find some patterns:
        // print("""
        // -----
        // headerMinHeight: \(ProfileHeaderViewController.headerMinHeight)
        // scrollView.contentOffset.y: \(scrollView.contentOffset.y)
        // scrollView.contentSize.height: \(scrollView.contentSize.height)
        // scrollView.frame: \(scrollView.frame)
        // scrollView.adjustedContentInset.top: \(scrollView.adjustedContentInset.top)
        // scrollView.adjustedContentInset.bottom: \(scrollView.adjustedContentInset.bottom)
        // """
        // )

        
        // elastically banner

        // make banner top snap to window top
        // do not rely on the view frame becase the header frame is .zero during the initial call
        profileHeaderViewController.profileHeaderView.bannerImageViewTopLayoutConstraint.constant = min(0, scrollView.contentOffset.y)
        
        if profileHeaderViewController.profileHeaderView.frame != .zero {
            // make banner bottom not higher than navigation bar bottom
            let bannerContainerInWindow = profileHeaderViewController.profileHeaderView.convert(
                profileHeaderViewController.profileHeaderView.bannerContainerView.frame,
                to: nil
            )
            let bannerContainerBottomOffset = bannerContainerInWindow.origin.y + bannerContainerInWindow.height
            // print("bannerContainerBottomOffset: \(bannerContainerBottomOffset)")
            
            let height = profileHeaderViewController.view.frame.height - bannerContainerInWindow.height
            // make avata hidden when scroll 0.5x avatar height 
            let throttle = height != .zero ? 0.5 * ProfileHeaderView.avatarImageViewSize.height / height : 0
            let progress: CGFloat
            
            if bannerContainerBottomOffset < tabBarPagerController.containerScrollView.safeAreaInsets.top {
                let offset = bannerContainerBottomOffset - tabBarPagerController.containerScrollView.safeAreaInsets.top
                profileHeaderViewController.profileHeaderView.bannerImageViewBottomLayoutConstraint.constant = offset
                // the progress for header move from banner bottom to header bottom (from 0 to 1)
                progress = height != .zero ? abs(offset) / height : 0
            } else {
                profileHeaderViewController.profileHeaderView.bannerImageViewBottomLayoutConstraint.constant = 0
                progress = 0
            }
            
            // setup follows you mask
            // 1. set mask size
            profileHeaderViewController.profileHeaderView.followsYouMaskView.frame = profileHeaderViewController.profileHeaderView.followsYouBlurEffectView.bounds
            // 2. check follows you view overflow navigation bar or not
            let followsYouBlurEffectViewInWindow = profileHeaderViewController.profileHeaderView.convert(
                profileHeaderViewController.profileHeaderView.followsYouBlurEffectView.frame,
                to: nil
            )
            if followsYouBlurEffectViewInWindow.minY < tabBarPagerController.containerScrollView.safeAreaInsets.top {
                let offestY = tabBarPagerController.containerScrollView.safeAreaInsets.top - followsYouBlurEffectViewInWindow.minY
                let height = profileHeaderViewController.profileHeaderView.followsYouMaskView.frame.height
                profileHeaderViewController.profileHeaderView.followsYouMaskView.frame.origin.y = min(offestY, height)
            } else {
                profileHeaderViewController.profileHeaderView.followsYouMaskView.frame.origin.y = .zero
            }
            
            // setup titleView offset and fade avatar
            profileHeaderViewController.updateHeaderScrollProgress(progress, throttle: throttle)
            
            // setup buttonBar shadow
            profilePagingViewController.updateButtonBarShadow(progress: progress)
        }
    }
    
}

// MARK: - TabBarPagerDataSource
extension ProfileViewController: TabBarPagerDataSource {
    func headerViewController() -> UIViewController & TabBarPagerHeader {
        return profileHeaderViewController
    }
    
    func pageViewController() -> UIViewController & TabBarPageViewController {
        return profilePagingViewController
    }
}

//// MARK: - UIScrollViewDelegate
//extension ProfileViewController: UIScrollViewDelegate {
//
//    func scrollViewDidScroll(_ scrollView: UIScrollView) {
//        contentOffsets[profileSegmentedViewController.pagingViewController.currentIndex!] = scrollView.contentOffset.y
//        let topMaxContentOffsetY = profileSegmentedViewController.view.frame.minY - ProfileHeaderViewController.headerMinHeight - containerScrollView.safeAreaInsets.top
//        if scrollView.contentOffset.y < topMaxContentOffsetY {
//            self.containerScrollView.contentOffset.y = scrollView.contentOffset.y
//            for postTimelineView in profileSegmentedViewController.pagingViewController.viewModel.viewControllers {
//                postTimelineView.scrollView?.contentOffset.y = 0
//            }
//            contentOffsets.removeAll()
//        } else {
//            containerScrollView.contentOffset.y = topMaxContentOffsetY
//            if viewModel.needsPagePinToTop.value {
//                // do nothing
//            } else {
//                if let customScrollViewContainerController = profileSegmentedViewController.pagingViewController.currentViewController as? ScrollViewContainer {
//                    let contentOffsetY = scrollView.contentOffset.y - containerScrollView.contentOffset.y
//                    customScrollViewContainerController.scrollView?.contentOffset.y = contentOffsetY
//                }
//            }
//
//        }
//    }
//
//}

// MARK: - AuthContextProvider
extension ProfileViewController: AuthContextProvider {
    var authContext: AuthContext { viewModel.authContext }
}

// MARK: - ProfileHeaderViewControllerDelegate
extension ProfileViewController: ProfileHeaderViewControllerDelegate {
    func profileHeaderViewController(
        _ profileHeaderViewController: ProfileHeaderViewController,
        profileHeaderView: ProfileHeaderView,
        relationshipButtonDidPressed button: ProfileRelationshipActionButton
    ) {
        let relationshipActionSet = viewModel.relationshipViewModel.optionSet ?? .none
        
        // handle edit logic for editable profile
        // handle relationship logic for non-editable profile
        if relationshipActionSet.contains(.edit) {
            // do nothing when updating
            guard !viewModel.isUpdating else { return }

            guard let profileHeaderViewModel = profileHeaderViewController.viewModel else { return }
            guard let profileAboutViewModel = profilePagingViewController.viewModel.profileAboutViewController.viewModel else { return }
            
            let isEdited = profileHeaderViewModel.isEdited || profileAboutViewModel.isEdited
            
            if isEdited {
                // update profile when edited
                viewModel.isUpdating = true
                Task { @MainActor in
                    do {
                        // TODO: handle error
                        _ = try await viewModel.updateProfileInfo(
                            headerProfileInfo: profileHeaderViewModel.profileInfoEditing,
                            aboutProfileInfo: profileAboutViewModel.profileInfoEditing
                        )
                        self.viewModel.isEditing = false
                        
                    } catch {
                        let alertController = UIAlertController(
                            for: error,
                            title: L10n.Common.Alerts.EditProfileFailure.title,
                            preferredStyle: .alert
                        )
                        let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default)
                        alertController.addAction(okAction)
                        self.present(alertController, animated: true)
                    }
                    
                    // finish updating
                    self.viewModel.isUpdating = false
                }   // end Task
            } else {
                // set `updating` then toggle `edit` state
                viewModel.isUpdating = true
                viewModel.fetchEditProfileInfo()
                    .receive(on: DispatchQueue.main)
                    .sink { [weak self] completion in
                        guard let self = self else { return }
                        defer {
                            // finish updating
                            self.viewModel.isUpdating = false
                        }
                        switch completion {
                        case .failure(let error):
                            let alertController = UIAlertController(for: error, title: L10n.Common.Alerts.EditProfileFailure.title, preferredStyle: .alert)
                            let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
                            alertController.addAction(okAction)
                            _ = self.coordinator.present(
                                scene: .alertController(alertController: alertController),
                                from: nil,
                                transition: .alertController(animated: true, completion: nil)
                            )
                        case .finished:
                            // enter editing mode
                            self.viewModel.isEditing.toggle()
                        }
                    } receiveValue: { [weak self] response in
                        guard let self = self else { return }
                        self.viewModel.accountForEdit = response.value
                    }
                    .store(in: &disposeBag)
            }
        } else {
            guard let relationshipAction = relationshipActionSet.highPriorityAction(except: .editOptions) else { return }
            switch relationshipAction {
            case .none:
                break
            case .follow, .request, .pending, .following:
                guard let user = viewModel.user else { return }
                let record = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
                Task {
                    try await DataSourceFacade.responseToUserFollowAction(
                        dependency: self,
                        user: record
                    )
                }
            case .muting:
                guard let user = viewModel.user else { return }
                let name = user.displayNameWithFallback
                
                let alertController = UIAlertController(
                    title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.title,
                    message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.message(name),
                    preferredStyle: .alert
                )
                let record = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
                let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Friendship.unmute, style: .default) { [weak self] _ in
                    guard let self = self else { return }
                    Task {
                        try await DataSourceFacade.responseToUserMuteAction(
                            dependency: self,
                            user: record
                        )
                    }
                }
                alertController.addAction(unmuteAction)
                let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
                alertController.addAction(cancelAction)
                present(alertController, animated: true, completion: nil)
            case .blocking:
                guard let user = viewModel.user else { return }
                let name = user.displayNameWithFallback
                
                let alertController = UIAlertController(
                    title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.title,
                    message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.message(name),
                    preferredStyle: .alert
                )
                let record = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
                let unblockAction = UIAlertAction(title: L10n.Common.Controls.Friendship.unblock, style: .default) { [weak self] _ in
                    guard let self = self else { return }
                    Task {
                        try await DataSourceFacade.responseToUserBlockAction(
                            dependency: self,
                            user: record
                        )
                    }
                }
                alertController.addAction(unblockAction)
                let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
                alertController.addAction(cancelAction)
                present(alertController, animated: true, completion: nil)
            case .blocked, .showReblogs, .isMyself,.followingBy, .blockingBy, .suspended, .edit, .editing, .updating:
                break
            }
        }
        
    }
    
    func profileHeaderViewController(
        _ profileHeaderViewController: ProfileHeaderViewController,
        profileHeaderView: ProfileHeaderView,
        metaTextView: MetaTextView,
        metaDidPressed meta: Meta
    ) {
        handleMetaPress(meta)
    }
}

// MARK: - ProfileAboutViewControllerDelegate
extension ProfileViewController: ProfileAboutViewControllerDelegate {
    func profileAboutViewController(
        _ viewController: ProfileAboutViewController,
        profileFieldCollectionViewCell: ProfileFieldCollectionViewCell,
        metaLabel: MetaLabel,
        didSelectMeta meta: Meta
    ) {
        handleMetaPress(meta)
    }
}

// MARK: - MastodonMenuDelegate
extension ProfileViewController: MastodonMenuDelegate {
    func menuAction(_ action: MastodonMenu.Action) {
        guard let user = viewModel.user else { return }

        let userRecord: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)

        Task {
            try await DataSourceFacade.responseToMenuAction(
                dependency: self,
                action: action,
                menuContext: DataSourceFacade.MenuContext(
                    author: userRecord,
                    statusViewModel: nil,
                    button: nil,
                    barButtonItem: self.moreMenuBarButtonItem
                )
            )
        }   // end Task
    }
}

// MARK: - ScrollViewContainer
extension ProfileViewController: ScrollViewContainer {
    var scrollView: UIScrollView {
        return tabBarPagerController.relayScrollView
    }
}

extension ProfileViewController {

    override var keyCommands: [UIKeyCommand]? {
        if !viewModel.isEditing {
            return pagerTabStripNavigateKeyCommands
        }

        return nil
    }

}

// MARK: - PagerTabStripNavigateable
extension ProfileViewController: PagerTabStripNavigateable {

    var navigateablePageViewController: PagerTabStripViewController {
        return profilePagingViewController
    }

    @objc func pagerTabStripNavigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) {
        pagerTabStripNavigateKeyCommandHandler(sender)
    }

}

private extension ProfileViewController {
    var currentInstance: Instance? {
        authContext.mastodonAuthenticationBox.authentication.instance(in: context.managedObjectContext)
    }
}
