//
//  ComposeHostingController.swift
//  Tusker
//
//  Created by Shadowfacts on 3/6/23.
//  Copyright © 2023 Shadowfacts. All rights reserved.
//

import UIKit
import SwiftUI
import ComposeUI
import Combine
import PhotosUI
import PencilKit
import Pachyderm
import CoreData
import Duckable

protocol ComposeHostingControllerDelegate: AnyObject {
    func dismissCompose(mode: DismissMode) -> Bool
}

class ComposeHostingController: UIHostingController<ComposeHostingController.View>, DuckableViewController {
    
    weak var delegate: ComposeHostingControllerDelegate?
    
    let controller: ComposeController
    let mastodonController: MastodonController
    
    private var cancellables = Set<AnyCancellable>()
    
    private var assetPickerCompletion: (@MainActor ([PHPickerResult]) -> Void)?
    private var drawingCompletion: ((PKDrawing) -> Void)?
    
    init(draft: Draft?, mastodonController: MastodonController) {
        let draft = draft ?? mastodonController.createDraft()
        
        self.controller = ComposeController(
            draft: draft,
            config: ComposeUIConfig(),
            mastodonController: mastodonController,
            fetchAvatar: { @MainActor in await ImageCache.avatars.get($0).1 },
            fetchAttachment: { @MainActor in await ImageCache.attachments.get($0).1 },
            fetchStatus: { mastodonController.persistentContainer.status(for: $0) },
            displayNameLabel: { AnyView(AccountDisplayNameView(account: $0, textStyle: $1, emojiSize: $2)) },
            replyContentView: { AnyView(ComposeReplyContentView(status: $0, mastodonController: mastodonController, heightChanged: $1)) },
            emojiImageView: { AnyView(CustomEmojiImageView(emoji: $0)) }
        )
        
        self.mastodonController = mastodonController
        
        super.init(rootView: View(mastodonController: mastodonController, controller: controller))
        
        self.updateConfig()
        
        pasteConfiguration = UIPasteConfiguration(forAccepting: DraftAttachment.self)
        
        NotificationCenter.default.addObserver(self, selector: #selector(updateConfig), name: .preferencesChanged, object: nil)
        
        // set an initial title immediately, in case we're starting ducked
        self.navigationItem.title = self.controller.navigationTitle
        
        mastodonController.$account
            .sink { [unowned self] in
                self.controller.currentAccount = $0
            }
            .store(in: &cancellables)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @objc private func updateConfig() {
        var config = ComposeUIConfig()
        config.backgroundColor = .appBackground
        config.groupedBackgroundColor = .appGroupedBackground
        config.groupedCellBackgroundColor = .appGroupedCellBackground
        config.fillColor = .appFill
        switch Preferences.shared.avatarStyle {
        case .roundRect:
            config.avatarStyle = .roundRect
        case .circle:
            config.avatarStyle = .circle
        }
        
        config.useTwitterKeyboard = Preferences.shared.useTwitterKeyboard
        config.contentType = Preferences.shared.statusContentType
        config.requireAttachmentDescriptions = Preferences.shared.requireAttachmentDescriptions
        
        config.dismiss = { [weak self] in self?.dismiss(mode: $0) }
        config.presentAssetPicker = { [unowned self] in self.presentAssetPicker(completion: $0) }
        config.presentDrawing = { [unowned self] in self.presentDrawing($0, completion: $1) }
        config.userActivityForDraft = { [unowned self] in
            let activity = UserActivityManager.editDraftActivity(id: $0.id, accountID: self.mastodonController.accountInfo!.id)
            activity.displaysAuxiliaryScene = true
            return NSItemProvider(object: activity)
        }
        
        controller.config = config
    }
    
    override func canPaste(_ itemProviders: [NSItemProvider]) -> Bool {
        return controller.canPaste(itemProviders: itemProviders)
    }
    
    override func paste(itemProviders: [NSItemProvider]) {
        controller.paste(itemProviders: itemProviders)
    }
    
    private func dismiss(mode: DismissMode) {
        if delegate?.dismissCompose(mode: mode) == true {
            return
        } else {
            dismiss(animated: true)
        }
    }
    
    private func presentAssetPicker(completion: @MainActor @escaping ([PHPickerResult]) -> Void) {
        self.assetPickerCompletion = completion
        
        var config = PHPickerConfiguration()
        config.selection = .ordered
        config.selectionLimit = 0
        config.preferredAssetRepresentationMode = .compatible
        let picker = PHPickerViewController(configuration: config)
        picker.delegate = self
        picker.modalPresentationStyle = .pageSheet
        picker.overrideUserInterfaceStyle = .dark
        // sheet detents don't play nice with PHPickerViewController, see
//        let sheet = picker.sheetPresentationController!
//        sheet.detents = [.medium(), .large()]
//        sheet.prefersEdgeAttachedInCompactHeight = true
//        sheet.prefersGrabberVisible = true
        present(picker, animated: true)
    }
    
    private func presentDrawing(_ drawing: PKDrawing, completion: @escaping (PKDrawing) -> Void) {
        self.drawingCompletion = completion
        
        present(ComposeDrawingNavigationController(editing: drawing, delegate: self), animated: true)
    }
    
    // MARK: Duckable
    
    func duckableViewControllerShouldDuck() -> DuckAttemptAction {
        if controller.isPosting {
            return .block
        } else if controller.draft.hasContent {
            return .duck
        } else {
            return .dismiss
        }
    }
    
    func duckableViewControllerWillAnimateDuck(withDuration duration: CGFloat, afterDelay delay: CGFloat) {
        controller.deleteDraftOnDisappear = false
        
        withAnimation(.linear(duration: duration).delay(delay)) {
            controller.showToolbar = false
        }
    }
    
    func duckableViewControllerDidFinishAnimatingDuck() {
        controller.showToolbar = true
    }
    
    struct View: SwiftUI.View {
        let mastodonController: MastodonController
        let controller: ComposeController
        
        var body: some SwiftUI.View {
            ControllerView(controller: { controller })
                .task {
                    if let account = try? await mastodonController.getOwnAccount() {
                        controller.currentAccount = account
                    }
                }
        }
    }
}

extension MastodonController: ComposeMastodonContext {
    @MainActor
    func searchCachedAccounts(query: String) -> [AccountProtocol] {
        // todo: there's got to be something more efficient than this :/
        let wildcardedQuery = query.map { "*\($0)" }.joined() + "*"
        let request: NSFetchRequest<AccountMO> = AccountMO.fetchRequest()
        request.predicate = NSPredicate(format: "displayName LIKE[cd] %@ OR acct LIKE[cd] %@", wildcardedQuery, wildcardedQuery)
        
        if let results = try? persistentContainer.viewContext.fetch(request) {
            return results
        } else {
            return []
        }
    }
    
    @MainActor
    func cachedRelationship(for accountID: String) -> RelationshipProtocol? {
        return persistentContainer.relationship(forAccount: accountID)
    }
    
    @MainActor
    func searchCachedHashtags(query: String) -> [Hashtag] {
        let wildcardedQuery = query.map { "*\($0)" }.joined() + "*"
        let predicate = NSPredicate(format: "name LIKE[cd] %@", wildcardedQuery)
        let savedReq = SavedHashtag.fetchRequest(account: accountInfo!)
        savedReq.predicate = predicate
        let followedReq = FollowedHashtag.fetchRequest()
        followedReq.predicate = predicate
        
        let saved = try? persistentContainer.viewContext.fetch(savedReq).map { Hashtag(name: $0.name, url: $0.url) }
        let followed = try? persistentContainer.viewContext.fetch(followedReq).map { Hashtag(name: $0.name, url: $0.url) }
        
        var results = saved ?? []
        if let followed {
            results.append(contentsOf: followed)
        }
        return results
    }
    
    func storeCreatedStatus(_ status: Status) {
        persistentContainer.addOrUpdate(status: status)
    }
}

extension ComposeHostingController: PHPickerViewControllerDelegate {
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        dismiss(animated: true)
        
        assetPickerCompletion?(results)
        assetPickerCompletion = nil
    }
}

extension ComposeHostingController: ComposeDrawingViewControllerDelegate {
    func composeDrawingViewControllerClose(_ drawingController: ComposeDrawingViewController) {
        dismiss(animated: true)
        drawingCompletion = nil
    }
    
    func composeDrawingViewController(_ drawingController: ComposeDrawingViewController, saveDrawing drawing: PKDrawing) {
        dismiss(animated: true)
        drawingCompletion?(drawing)
        drawingCompletion = nil
    }
}
