//
//  AttachmentsListController.swift
//  ComposeUI
//
//  Created by Shadowfacts on 3/8/23.
//

import SwiftUI
import PhotosUI
import PencilKit

class AttachmentsListController: ViewController {
    
    unowned let parent: ComposeController
    var draft: Draft { parent.draft }
    
    var isValid: Bool {
        !requiresAttachmentDescriptions && validAttachmentCombination
    }

    private var requiresAttachmentDescriptions: Bool {
        if parent.config.requireAttachmentDescriptions {
            if draft.attachments.count == 0 {
                return false
            } else {
                return !parent.attachmentsMissingDescriptions.isEmpty
            }
        } else {
            return false
        }
    }
    
    var validAttachmentCombination: Bool {
        if !parent.mastodonController.instanceFeatures.mastodonAttachmentRestrictions {
            return true
        } else if draft.attachments.count > 1,
                  draft.draftAttachments.contains(where: { $0.type == .video }) {
            return false
        } else if draft.attachments.count > 4 {
            return false
        }
        return true
    }
    
    init(parent: ComposeController) {
        self.parent = parent
    }
    
    var canAddAttachment: Bool {
        if parent.mastodonController.instanceFeatures.mastodonAttachmentRestrictions {
            return draft.attachments.count < 4 && draft.draftAttachments.allSatisfy { $0.type == .image } && draft.poll == nil
        } else {
            return true
        }
    }
    
    private var canAddPoll: Bool {
        if parent.mastodonController.instanceFeatures.pollsAndAttachments {
            return true
        } else {
            return draft.attachments.count == 0
        }
    }
    
    var view: some View {
        AttachmentsList()
    }
    
    private func moveAttachments(from source: IndexSet, to destination: Int) {
        // just using moveObjects(at:to:) on the draft.attachments NSMutableOrderedSet
        // results in the order switching back to the previous order and then to the correct one
        // on the subsequent 2 view updates. creating a new set with the proper order doesn't have that problem
        var array = draft.draftAttachments
        array.move(fromOffsets: source, toOffset: destination)
        draft.attachments = NSMutableOrderedSet(array: array)
    }
    
    private func deleteAttachments(at indices: IndexSet) {
        var array = draft.draftAttachments
        array.remove(atOffsets: indices)
        draft.attachments = NSMutableOrderedSet(array: array)
    }
    
    private func insertAttachments(at offset: Int, itemProviders: [NSItemProvider]) {
        for provider in itemProviders where provider.canLoadObject(ofClass: DraftAttachment.self) {
            provider.loadObject(ofClass: DraftAttachment.self) { object, error in
                guard let attachment = object as? DraftAttachment else { return }
                DispatchQueue.main.async {
                    guard self.canAddAttachment else { return }
                    DraftsPersistentContainer.shared.viewContext.insert(attachment)
                    attachment.draft = self.draft
                    self.draft.attachments.add(attachment)
                }
            }
        }
    }
    
    private func addImage() {
        parent.deleteDraftOnDisappear = false
        parent.config.presentAssetPicker?({ results in
            self.insertAttachments(at: self.draft.attachments.count, itemProviders: results.map(\.itemProvider))
        })
    }
    
    private func addDrawing() {
        parent.deleteDraftOnDisappear = false
        parent.config.presentDrawing?(PKDrawing()) { drawing in
            let attachment = DraftAttachment(context: DraftsPersistentContainer.shared.viewContext)
            attachment.id = UUID()
            attachment.drawing = drawing
            attachment.draft = self.draft
            self.draft.attachments.add(attachment)
        }
    }
    
    private func togglePoll() {
        UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
        
        withAnimation {
            draft.poll = draft.poll == nil ? Poll(context: DraftsPersistentContainer.shared.viewContext) : nil
        }
    }
    
    struct AttachmentsList: View {
        private let cellHeight: CGFloat = 80
        private let cellPadding: CGFloat = 12
        
        @EnvironmentObject private var controller: AttachmentsListController
        @EnvironmentObject private var draft: Draft
        @Environment(\.colorScheme) private var colorScheme
        @Environment(\.horizontalSizeClass) private var horizontalSizeClass
        
        var body: some View {
            Group {
                attachmentsList
                
                if controller.parent.config.presentAssetPicker != nil {
                    addImageButton
                        .listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
                }
                
                if controller.parent.config.presentDrawing != nil {
                    addDrawingButton
                        .listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
                }
                
                togglePollButton
                    .listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
            }
        }
        
        private var attachmentsList: some View {
            ForEach(draft.attachments.array as! [DraftAttachment]) { attachment in
                ControllerView(controller: { AttachmentRowController(parent: controller.parent, attachment: attachment) })
                    .listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
                    .id(attachment.id)
            }
            .onMove(perform: controller.moveAttachments)
            .onDelete(perform: controller.deleteAttachments)
            .conditionally(controller.canAddAttachment) {
                $0.onInsert(of: DraftAttachment.readableTypeIdentifiersForItemProvider, perform: { offset, providers in
                    controller.insertAttachments(at: offset, itemProviders: providers)
                })
            }
            // only sort of works, see #240
            .onDrop(of: DraftAttachment.readableTypeIdentifiersForItemProvider, isTargeted: nil) { providers in
                controller.insertAttachments(at: 0, itemProviders: providers)
                return true
            }
        }
        
        private var addImageButton: some View {
            Button(action: controller.addImage) {
                Label("Add photo or video", systemImage: colorScheme == .dark ? "photo.fill" : "photo")
            }
            .disabled(!controller.canAddAttachment)
            .foregroundColor(.accentColor)
            .frame(height: cellHeight / 2)
        }
        
        private var addDrawingButton: some View {
            Button(action: controller.addDrawing) {
                Label("Draw something", systemImage: "hand.draw")
            }
            .disabled(!controller.canAddAttachment)
            .foregroundColor(.accentColor)
            .frame(height: cellHeight / 2)
        }
        
        private var togglePollButton: some View {
            Button(action: controller.togglePoll) {
                Label(draft.poll == nil ? "Add a poll" : "Remove poll", systemImage: "chart.bar.doc.horizontal")
            }
            .disabled(!controller.canAddPoll)
            .foregroundColor(.accentColor)
            .frame(height: cellHeight / 2)
        }
    }
}

fileprivate extension View {
    @ViewBuilder
    func conditionally(_ condition: Bool, body: (Self) -> some View) -> some View {
        if condition {
            body(self)
        } else {
            self
        }
    }
    
    @available(iOS, obsoleted: 16.0)
    @ViewBuilder
    func sheetOrPopover(isPresented: Binding<Bool>, @ViewBuilder content: @escaping () -> some View) -> some View {
        if #available(iOS 16.0, *) {
            self.modifier(SheetOrPopover(isPresented: isPresented, view: content))
        } else {
            self.popover(isPresented: isPresented, content: content)
        }
    }
    
    @available(iOS, obsoleted: 16.0)
    @ViewBuilder
    func withSheetDetentsIfAvailable() -> some View {
        if #available(iOS 16.0, *) {
            self
                .presentationDetents([.medium, .large])
                .presentationDragIndicator(.visible)
        } else {
            self
        }
    }
}

@available(iOS 16.0, *)
fileprivate struct SheetOrPopover<V: View>: ViewModifier {
    @Binding var isPresented: Bool
    @ViewBuilder let view: () -> V
    
    @Environment(\.horizontalSizeClass) var sizeClass
    
    func body(content: Content) -> some View {
        if sizeClass == .compact {
            content.sheet(isPresented: $isPresented, content: view)
        } else {
            content.popover(isPresented: $isPresented, content: view)
        }
    }
}
