import AVKit
import Foundation
import PhotosUI
import SwiftUI
import UIKit

public struct MediaPicker: UIViewControllerRepresentable {
    @Binding var selectedImage: UIImage?
    @Binding var selectedMovie: Movie?
    @Binding var transferProgress: Double
    var onSelectionComplete: () -> Void
    var onSelectionCancelled: () -> Void
    var onError: (Error) -> Void

    public init(
        selectedImage: Binding<UIImage?>,
        selectedMovie: Binding<Movie?>,
        transferProgress: Binding<Double>,
        onSelectionComplete: @escaping () -> Void,
        onSelectionCancelled: @escaping () -> Void,
        onError: @escaping (Error) -> Void
    ) {
        self._selectedImage = selectedImage
        self._selectedMovie = selectedMovie
        self._transferProgress = transferProgress
        self.onSelectionComplete = onSelectionComplete
        self.onSelectionCancelled = onSelectionCancelled
        self.onError = onError
    }

    public func makeUIViewController(context: Context) -> PHPickerViewController {
        var configuration = PHPickerConfiguration(photoLibrary: .shared())

        configuration.filter = .any(of: [.images, .videos])

        // If this property is not set to `current`, it will take longer
        // to load and present any .mov (HDR) videos, since they will
        // need to get transcoded first.
        configuration.preferredAssetRepresentationMode = .current

        configuration.selectionLimit = 1

        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = context.coordinator
        return picker
    }

    public func updateUIViewController(_: PHPickerViewController, context _: Context) {}

    public func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    public class Coordinator: NSObject, PHPickerViewControllerDelegate {
        var parent: MediaPicker

        init(_ parent: MediaPicker) {
            self.parent = parent
        }

        public func picker(_: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            guard let result = results.first else {
                DispatchQueue.main.async {
                    self.parent.selectedImage = nil
                    self.parent.selectedMovie = nil
                    self.parent.transferProgress = 0
                    self.parent.onSelectionCancelled()
                }
                return
            }

            let itemProvider = result.itemProvider

            if itemProvider.canLoadObject(ofClass: UIImage.self) {
                itemProvider.loadObject(ofClass: UIImage.self) { [parent] image, _ in
                    if let image = image as? UIImage {
                        DispatchQueue.main.async {
                            parent.selectedImage = image.fixOrientation()
                            parent.selectedMovie = nil
                            parent.transferProgress = 1.0
                            parent.onSelectionComplete()
                        }
                    }
                }
            } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
                let progress = itemProvider.loadTransferable(type: Movie.self) { [parent] result in
                    Task { @MainActor in
                        switch result {
                        case .success(let movie):
                            parent.selectedMovie = movie
                            parent.selectedImage = nil
                            parent.transferProgress = 1.0
                            parent.onSelectionComplete()

                        case .failure(let error):
                            parent.transferProgress = 0
                            parent.onError(MediaPickerError.failedToLoad(error.localizedDescription))
                        }
                    }
                }

                _ = progress.observe(\.fractionCompleted) { [parent] observedProgress, _ in
                    DispatchQueue.main.async {
                        parent.transferProgress = observedProgress.fractionCompleted
                    }
                }
            } else {
                parent.onError(MediaPickerError.incompatibleFileType)
            }
        }
    }
}

public enum MediaPickerError: Error {
    case failedToLoad(String)
    case incompatibleFileType
}
