// Copyright © 2020 Metabolist. All rights reserved.

import AVFoundation
import Combine
import Mastodon
import UIKit
import ViewModels

final class CompositionInputAccessoryView: UIView {
    let tagForInputView = UUID().hashValue
    let autocompleteSelections: AnyPublisher<String, Never>

    private let viewModel: CompositionViewModel
    private let parentViewModel: NewStatusViewModel
    private let toolbar = UIToolbar()
    private let autocompleteCollectionView = UICollectionView(
        frame: .zero,
        collectionViewLayout: CompositionInputAccessoryView.autocompleteLayout())
    private let autocompleteDataSource: AutocompleteDataSource
    private let autocompleteCollectionViewHeightConstraint: NSLayoutConstraint
    private let autocompleteSelectionsSubject = PassthroughSubject<String, Never>()
    private var cancellables = Set<AnyCancellable>()

    init(viewModel: CompositionViewModel,
         parentViewModel: NewStatusViewModel,
         autocompleteQueryPublisher: AnyPublisher<String?, Never>) {
        self.viewModel = viewModel
        self.parentViewModel = parentViewModel
        autocompleteDataSource = AutocompleteDataSource(
            collectionView: autocompleteCollectionView,
            queryPublisher: autocompleteQueryPublisher,
            parentViewModel: parentViewModel)
        autocompleteCollectionViewHeightConstraint =
            autocompleteCollectionView.heightAnchor.constraint(equalToConstant: .hairline)
        autocompleteSelections = autocompleteSelectionsSubject.eraseToAnyPublisher()

        super.init(
            frame: .init(
                origin: .zero,
                size: .init(width: UIScreen.main.bounds.width, height: .minimumButtonDimension)))

        initialSetup()
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func didMoveToSuperview() {
        super.didMoveToSuperview()

        layoutIfNeeded()
    }
}

private extension CompositionInputAccessoryView {
    static let autocompleteCollectionViewMaxHeight: CGFloat = 150

    var heightConstraint: NSLayoutConstraint? {
        superview?.constraints.first(where: { $0.identifier == "accessoryHeight" })
    }

    // swiftlint:disable:next function_body_length
    func initialSetup() {
        autoresizingMask = .flexibleHeight

        addSubview(autocompleteCollectionView)
        autocompleteCollectionView.translatesAutoresizingMaskIntoConstraints = false
        autocompleteCollectionView.alwaysBounceVertical = false
        autocompleteCollectionView.backgroundColor = .clear
        autocompleteCollectionView.layer.cornerRadius = .defaultCornerRadius
        autocompleteCollectionView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
        autocompleteCollectionView.dataSource = autocompleteDataSource
        autocompleteCollectionView.delegate = self

        let autocompleteBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterial))

        autocompleteCollectionView.backgroundView = autocompleteBackgroundView

        addSubview(toolbar)
        toolbar.translatesAutoresizingMaskIntoConstraints = false
        toolbar.setContentCompressionResistancePriority(.required, for: .vertical)

        NSLayoutConstraint.activate([
            autocompleteCollectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
            autocompleteCollectionView.topAnchor.constraint(equalTo: topAnchor),
            autocompleteCollectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
            autocompleteCollectionView.bottomAnchor.constraint(equalTo: toolbar.topAnchor),
            toolbar.leadingAnchor.constraint(equalTo: leadingAnchor),
            toolbar.trailingAnchor.constraint(equalTo: trailingAnchor),
            toolbar.bottomAnchor.constraint(equalTo: bottomAnchor),
            toolbar.heightAnchor.constraint(equalToConstant: .minimumButtonDimension),
            autocompleteCollectionViewHeightConstraint
        ])

        var attachmentActions = [
            UIAction(
                title: NSLocalizedString("compose.browse", comment: ""),
                image: UIImage(systemName: "ellipsis")) { [weak self] _ in
                guard let self = self else { return }

                self.parentViewModel.presentDocumentPicker(viewModel: self.viewModel)
            },
            UIAction(
                title: NSLocalizedString("compose.photo-library", comment: ""),
                image: UIImage(systemName: "rectangle.on.rectangle")) { [weak self] _ in
                guard let self = self else { return }

                self.parentViewModel.presentMediaPicker(viewModel: self.viewModel)
            }
        ]

        #if !IS_SHARE_EXTENSION
        attachmentActions.insert(UIAction(
            title: NSLocalizedString("compose.take-photo-or-video", comment: ""),
            image: UIImage(systemName: "camera.fill")) { [weak self] _ in
            guard let self = self else { return }

            self.parentViewModel.presentCamera(viewModel: self.viewModel)
        },
        at: 1)
        #endif

        let attachmentButton = UIBarButtonItem(
            image: UIImage(systemName: "paperclip"),
            menu: UIMenu(children: attachmentActions))

        attachmentButton.accessibilityLabel =
            NSLocalizedString("compose.attachments-button.accessibility-label", comment: "")

        let pollButton = UIBarButtonItem(
            image: UIImage(systemName: "chart.bar.xaxis"),
            primaryAction: UIAction { [weak self] _ in self?.viewModel.displayPoll.toggle() })

        pollButton.accessibilityLabel = NSLocalizedString("compose.poll-button.accessibility-label", comment: "")

        let visibilityButton = UIBarButtonItem(
            image: UIImage(systemName: parentViewModel.visibility.systemImageName),
            menu: visibilityMenu(selectedVisibility: parentViewModel.visibility))

        let contentWarningButton = UIBarButtonItem(
            title: NSLocalizedString("status.content-warning-abbreviation", comment: ""),
            primaryAction: UIAction { [weak self] _ in self?.viewModel.displayContentWarning.toggle() })

        viewModel.$displayContentWarning.sink {
            if $0 {
                contentWarningButton.accessibilityHint =
                    NSLocalizedString("compose.content-warning-button.remove", comment: "")
            } else {
                contentWarningButton.accessibilityHint =
                    NSLocalizedString("compose.content-warning-button.add", comment: "")
            }
        }
        .store(in: &cancellables)

        let emojiButton = UIBarButtonItem(
            image: UIImage(systemName: "face.smiling"),
            primaryAction: UIAction { [weak self] _ in
                guard let self = self else { return }

                self.parentViewModel.presentEmojiPicker(tag: self.tagForInputView)
            })

        emojiButton.accessibilityLabel = NSLocalizedString("compose.emoji-button", comment: "")

        let addButton = UIBarButtonItem(
            image: UIImage(systemName: "plus.circle.fill"),
            primaryAction: UIAction { [weak self] _ in
                guard let self = self else { return }

                self.parentViewModel.insert(after: self.viewModel)
            })

        switch parentViewModel.identityContext.appPreferences.statusWord {
        case .toot:
            addButton.accessibilityLabel =
                NSLocalizedString("compose.add-button-accessibility-label.toot", comment: "")
        case .post:
            addButton.accessibilityLabel =
                NSLocalizedString("compose.add-button-accessibility-label.post", comment: "")
        }

        let charactersBarItem = UIBarButtonItem()

        charactersBarItem.isEnabled = false

        toolbar.items = [
            attachmentButton,
            UIBarButtonItem.fixedSpace(.defaultSpacing),
            pollButton,
            UIBarButtonItem.fixedSpace(.defaultSpacing),
            visibilityButton,
            UIBarButtonItem.fixedSpace(.defaultSpacing),
            contentWarningButton,
            UIBarButtonItem.fixedSpace(.defaultSpacing),
            emojiButton,
            UIBarButtonItem.flexibleSpace(),
            charactersBarItem,
            UIBarButtonItem.fixedSpace(.defaultSpacing),
            addButton]

        viewModel.$canAddAttachment
            .sink { attachmentButton.isEnabled = $0 }
            .store(in: &cancellables)

        viewModel.$attachmentViewModels
            .combineLatest(viewModel.$attachmentUploadViewModels)
            .sink { pollButton.isEnabled = $0.isEmpty && $1.isEmpty }
            .store(in: &cancellables)

        viewModel.$remainingCharacters.sink {
            charactersBarItem.title = String($0)
            charactersBarItem.setTitleTextAttributes(
                [.foregroundColor: $0 < 0 ? UIColor.systemRed : UIColor.label],
                for: .disabled)
            charactersBarItem.accessibilityHint = String.localizedStringWithFormat(
                NSLocalizedString("compose.characters-remaining-accessibility-label-%ld", comment: ""),
                $0)
        }
        .store(in: &cancellables)

        viewModel.$isPostable
            .sink { addButton.isEnabled = $0 }
            .store(in: &cancellables)

        self.autocompleteCollectionView.publisher(for: \.contentSize)
            .map(\.height)
            .removeDuplicates()
            .throttle(for: .seconds(TimeInterval.shortAnimationDuration), scheduler: DispatchQueue.main, latest: true)
            .sink { [weak self] height in
                UIView.animate(withDuration: .zeroIfReduceMotion(.shortAnimationDuration)) {
                    self?.setAutocompleteCollectionViewHeight(height)
                }
            }
            .store(in: &cancellables)

        parentViewModel.$visibility
            .sink { [weak self] in
                visibilityButton.image = UIImage(systemName: $0.systemImageName)
                visibilityButton.menu = self?.visibilityMenu(selectedVisibility: $0)
                visibilityButton.accessibilityLabel = String.localizedStringWithFormat(
                    NSLocalizedString("compose.visibility-button.accessibility-label-%@", comment: ""),
                    $0.title ?? "")
            }
            .store(in: &cancellables)
    }
}

extension CompositionInputAccessoryView: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        collectionView.deselectItem(at: indexPath, animated: true)

        guard let item = autocompleteDataSource.itemIdentifier(for: indexPath) else { return }

        switch item {
        case let .account(account):
            autocompleteSelectionsSubject.send("@".appending(account.acct))
        case let .tag(tag):
            autocompleteSelectionsSubject.send("#".appending(tag.name))
        case let .emoji(emoji):
            let escaped = emoji.applyingDefaultSkinTone(identityContext: parentViewModel.identityContext).escaped

            autocompleteSelectionsSubject.send(escaped)
            autocompleteDataSource.updateUse(emoji: emoji)
        }

        UISelectionFeedbackGenerator().selectionChanged()

        // To dismiss without waiting for the throttle
        UIView.animate(withDuration: .zeroIfReduceMotion(.shortAnimationDuration)) {
            self.setAutocompleteCollectionViewHeight(.hairline)
        }
    }

    func collectionView(_ collectionView: UICollectionView,
                        contextMenuConfigurationForItemAt indexPath: IndexPath,
                        point: CGPoint) -> UIContextMenuConfiguration? {
        guard let item = autocompleteDataSource.itemIdentifier(for: indexPath),
              case let .emoji(emojiItem) = item,
              case let .system(emoji, _) = emojiItem,
              !emoji.skinToneVariations.isEmpty
        else { return nil }

        return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
            UIMenu(children: ([emoji] + emoji.skinToneVariations).map { skinToneVariation in
                UIAction(title: skinToneVariation.emoji) { [weak self] _ in
                    self?.autocompleteSelectionsSubject.send(skinToneVariation.emoji)
                    self?.autocompleteDataSource.updateUse(emoji: emojiItem)
                }
            })
        }
    }
}

private extension CompositionInputAccessoryView {
    static func autocompleteLayout() -> UICollectionViewLayout {
        var listConfig = UICollectionLayoutListConfiguration(appearance: .plain)

        listConfig.backgroundColor = .clear

        return UICollectionViewCompositionalLayout { index, environment -> NSCollectionLayoutSection? in
            guard let autocompleteSection = AutocompleteSection(rawValue: index) else { return nil }

            switch autocompleteSection {
            case .search:
                return .list(using: listConfig, layoutEnvironment: environment)
            case .emoji:
                let itemSize = NSCollectionLayoutSize(
                    widthDimension: .fractionalWidth(1.0),
                    heightDimension: .fractionalHeight(1.0))
                let item = NSCollectionLayoutItem(layoutSize: itemSize)
                let groupSize = NSCollectionLayoutSize(
                    widthDimension: .absolute(.minimumButtonDimension),
                    heightDimension: .absolute(.minimumButtonDimension))
                let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
                let section = NSCollectionLayoutSection(group: group)

                section.interGroupSpacing = .defaultSpacing
                section.orthogonalScrollingBehavior = .continuous
                section.contentInsets = NSDirectionalEdgeInsets(
                    top: .compactSpacing,
                    leading: .compactSpacing,
                    bottom: .compactSpacing,
                    trailing: .compactSpacing)

                return section
            }
        }
    }

    func visibilityMenu(selectedVisibility: Status.Visibility) -> UIMenu {
        UIMenu(children: Status.Visibility.allCasesExceptUnknown.reversed().map { visibility in
            UIAction(
                title: visibility.title ?? "",
                image: UIImage(systemName: visibility.systemImageName),
                discoverabilityTitle: visibility.description,
                state: visibility == selectedVisibility ? .on : .off) { [weak self] _ in
                self?.parentViewModel.visibility = visibility
            }
        })
    }

    func setAutocompleteCollectionViewHeight(_ height: CGFloat) {
        let autocompleteCollectionViewHeight = min(max(height, .hairline), Self.autocompleteCollectionViewMaxHeight)

        autocompleteCollectionViewHeightConstraint.constant = autocompleteCollectionViewHeight
        autocompleteCollectionView.alpha = autocompleteCollectionViewHeightConstraint.constant == .hairline ? 0 : 1

        heightConstraint?.constant = .minimumButtonDimension + autocompleteCollectionViewHeight
        updateConstraints()
        superview?.superview?.layoutIfNeeded()
    }
}
