Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
folium-app
GitHub Repository: folium-app/Folium
Path: blob/a-new-beginning/Folium-iOS/Controllers/LibraryControllers/LibraryController.swift
2 views
//
//  LibraryController.swift
//  Folium-iOS
//
//  Created by Jarrod Norwell on 12/7/2025.
//

import CombinedCores

import FilesManager
import Foundation
import StoreKit
import SwiftUI
import UIKit
import UniformTypeIdentifiers

class LibraryController : UICollectionViewController {
    var dataSource: UICollectionViewDiffableDataSource<Core, BaseGame>? = nil
    var snapshot: NSDiffableDataSourceSnapshot<Core, BaseGame>? = nil
    
    enum ImportFilesType : Int {
        case games,
        systemFiles,
        cia
    }
    var importFilesType: ImportFilesType? = nil
    var systemFileToImport: File? = nil
    
    var applicationModel: ApplicationModel
    init(applicationModel: ApplicationModel) {
        self.applicationModel = applicationModel
        super.init(collectionViewLayout: applicationModel.layoutManager.library)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        if let navigationController {
            navigationController.navigationBar.prefersLargeTitles = true
        }
        if #available(iOS 17, *) {
            navigationItem.largeTitleDisplayMode = .inline
        }
        navigationItem.style = .browser
        if #available(iOS 26, *) {
            navigationItem.largeTitle = .localized(for: .library)
            navigationItem.title = navigationItem.largeTitle
            navigationItem.largeSubtitle = "0 games available"
            navigationItem.subtitle = navigationItem.largeSubtitle
        } else {
            navigationItem.title = .localized(for: .library)
        }
        
        let filterAndJumpToBarButtonItems: [UIBarButtonItem] = if #available(iOS 26, *) {
            [
                UIBarButtonItem(image: UIImage(systemName: "list.bullet.badge.ellipsis"), menu: UIMenu(children: [
                    UIDeferredMenuElement.uncached { completion in
                        guard var filteredCores: [String] = UserDefaults.standard.stringArray(forKey: "folium.v1.38.filteredCores") else {
                            return
                        }
                        
                        let elements: [UIMenuElement] = Core.cores.map { core in
                            UIAction(title: core.string,
                                     subtitle: filteredCores.contains(core.string) ? .localized(for: .hideCore) : .localized(for: .showCore),
                                     image: .init(systemName: filteredCores.contains(core.string) ? "eye" : "eye.slash")) { _ in
                                if filteredCores.contains(core.string) {
                                    filteredCores.removeAll(where: { $0 == core.string })
                                } else {
                                    filteredCores.append(core.string)
                                }
                                
                                UserDefaults.standard.set(filteredCores, forKey: "folium.v1.38.filteredCores")
                                Task {
                                    await self.populateLibrary()
                                }
                            }
                        }
                        
                        completion(elements)
                    }
                ])),
                UIBarButtonItem(image: UIImage(systemName: "calendar.day.timeline.left"), menu: UIMenu(children: [
                    UIDeferredMenuElement.uncached { completion in
                        guard let filteredCores: [String] = UserDefaults.standard.stringArray(forKey: "folium.v1.38.filteredCores") else {
                            return
                        }
                        
                        let elements: [UIMenuElement] = Core.cores.map { core in
                            UIAction(title: core.string, subtitle: core.consoleShort,
                                     attributes: filteredCores.contains(core.string) ? [] : .disabled) { _ in
                                guard let dataSource = self.dataSource, let section = dataSource.snapshot().indexOfSection(core) else {
                                    return
                                }
                                
                                self.collectionView.scrollToItem(at: .init(item: 0, section: section), at: .top, animated: true)
                            }
                        }
                        
                        completion(elements)
                    }
                ]))
            ]
        } else {
            [
                UIBarButtonItem(image: UIImage(systemName: "list.bullet"), menu: UIMenu(children: [
                    UIMenu(title: .localized(for: .filter), image: UIImage(systemName: "line.3.horizontal.decrease"), children: [
                        UIDeferredMenuElement.uncached { completion in
                            guard var filteredCores: [String] = UserDefaults.standard.stringArray(forKey: "folium.v1.38.filteredCores") else {
                                return
                            }
                            
                            let elements: [UIMenuElement] = Core.cores.map { core in
                                UIAction(title: core.string,
                                         subtitle: filteredCores.contains(core.string) ? .localized(for: .hideCore) : .localized(for: .showCore),
                                         image: .init(systemName: filteredCores.contains(core.string) ? "eye" : "eye.slash")) { _ in
                                    if filteredCores.contains(core.string) {
                                        filteredCores.removeAll(where: { $0 == core.string })
                                    } else {
                                        filteredCores.append(core.string)
                                    }
                                    
                                    UserDefaults.standard.set(filteredCores, forKey: "folium.v1.38.filteredCores")
                                    Task {
                                        await self.populateLibrary()
                                    }
                                }
                            }
                            
                            completion(elements)
                        }
                    ]),
                    UIMenu(title: .localized(for: .jumpTo), image: UIImage(systemName: "calendar.day.timeline.left"), children: [
                        UIDeferredMenuElement.uncached { completion in
                            guard let filteredCores: [String] = UserDefaults.standard.stringArray(forKey: "folium.v1.38.filteredCores") else {
                                return
                            }
                            
                            let elements: [UIMenuElement] = Core.cores.map { core in
                                UIAction(title: core.string, subtitle: core.consoleShort,
                                         attributes: filteredCores.contains(core.string) ? [] : .disabled) { _ in
                                    guard let dataSource = self.dataSource, let section = dataSource.snapshot().indexOfSection(core) else {
                                        return
                                    }
                                    
                                    self.collectionView.scrollToItem(at: .init(item: 0, section: section), at: .top, animated: true)
                                }
                            }
                            
                            completion(elements)
                        }
                    ])
                ]))
            ]
        }
        
        navigationItem.trailingItemGroups = [
            .init(barButtonItems: [
                UIBarButtonItem(image: UIImage(systemName: "plus"), menu: UIMenu(children: [
                    UIAction(title: .localized(for: .games), image: UIImage(systemName: "gamecontroller")) { _ in
                        self.importFilesType = .games
                        
                        let documentPickerController: UIDocumentPickerViewController = .init(forOpeningContentTypes: [
                            .item
                        ], asCopy: true)
                        
                        if let sheetPresentationController = documentPickerController.sheetPresentationController {
                            sheetPresentationController.detents = [
                                .medium(),
                                .large()
                            ]
                        }
                        documentPickerController.allowsMultipleSelection = true
                        documentPickerController.delegate = self
                        self.present(documentPickerController, animated: true)
                    },
                    UIMenu(title: .localized(for: .systemFiles), image: UIImage(systemName: "brain"), children: [
                        UIDeferredMenuElement.uncached { completion in
                            Task {
                                var files: [File] = []
                                for core in Core.cores {
                                    files.append(contentsOf: await self.applicationModel.filesManager.missingFiles(for: core.string).files)
                                }
                                
                                let cores: [String] = Array(Set(files.map(\.core))).sorted()
                                var elements: [UIMenu] = []
                                cores.forEach { core in
                                    if let core = Core(rawValue: core) {
                                        elements.append(UIMenu(title: core.string, subtitle: core.consoleShort, children: files.filter { file in
                                            file.core == core.string
                                        }.map { file in
                                            UIAction(title: file.name, subtitle: file.importance.string) { _ in
                                                self.importFilesType = .systemFiles
                                                self.systemFileToImport = file
                                                
                                                let documentPickerController: UIDocumentPickerViewController = .init(forOpeningContentTypes: [
                                                    .item
                                                ], asCopy: true)
                                                
                                                if let sheetPresentationController = documentPickerController.sheetPresentationController {
                                                    sheetPresentationController.detents = [
                                                        .medium(),
                                                        .large()
                                                    ]
                                                }
                                                documentPickerController.delegate = self
                                                self.present(documentPickerController, animated: true)
                                            }
                                        }))
                                    }
                                }
                                
                                completion(elements)
                            }
                        }
                    ])
                ]))
            ], representativeItem: nil),
            .init(barButtonItems: filterAndJumpToBarButtonItems, representativeItem: nil),
            .init(barButtonItems: [
                UIBarButtonItem(image: UIImage(systemName: "gearshape"), menu: UIMenu(children: [
                    UIMenu(options: .displayInline, children: [
                        UIAction(title: "Folium") { _ in
                            let viewController: UINavigationController = UINavigationController(rootViewController: FoliumSettingsController())
                            viewController.modalPresentationStyle = .fullScreen
                            self.present(viewController, animated: true)
                        }
                    ]),
                    UIMenu(title: "Cores", children: [
                        UIMenu(options: .displayInline, preferredElementSize: .small, children: [
                            UIAction(title: "Cytrus") { _ in
                                let settingsController: UINavigationController = UINavigationController(rootViewController: CytrusSettingsController(self.applicationModel.cytrus))
                                settingsController.view.backgroundColor = .clear
                                settingsController.modalPresentationStyle = .overFullScreen
                                self.present(settingsController, animated: true)
                            },
                            UIAction(title: "Grape") { _ in
                                let settingsController: UINavigationController = UINavigationController(rootViewController: GrapeSettingsController())
                                settingsController.view.backgroundColor = .clear
                                settingsController.modalPresentationStyle = .overFullScreen
                                self.present(settingsController, animated: true)
                            }
                        ])/*,
                        UIMenu(options: .displayInline, preferredElementSize: .small, children: [
                            UIAction(title: "Mandarine") { _ in
                                let settingsController: UINavigationController = UINavigationController(rootViewController: MandarineSettingsController(self.applicationModel.mandarine))
                                settingsController.view.backgroundColor = .clear
                                settingsController.modalPresentationStyle = .overFullScreen
                                self.present(settingsController, animated: true)
                            }
                        ])*/
                    ])
                ]))
            ], representativeItem: nil)
        ]
        
        let headerCellRegistration: UICollectionView.SupplementaryRegistration<UICollectionViewListCell> = .init(elementKind: UICollectionView.elementKindSectionHeader) { supplementaryView, elementKind, indexPath in
            var contentConfiguration = UIListContentConfiguration.extraProminentInsetGroupedHeader()
            if let dataSource = self.dataSource, let core = dataSource.sectionIdentifier(for: indexPath.section) {
                contentConfiguration.text = core.string
                contentConfiguration.secondaryText = core.console
                
                var about: UIButton {
                    let scale: UIImage.SymbolScale = if #available(iOS 26, *) { .medium } else { .large }
                    let systemName: String = if #available(iOS 26, *) { "info" } else { "info.circle" }
                    
                    var configuration: UIButton.Configuration = if #available(iOS 26, *) { .glass() } else { .plain() }
                    configuration.buttonSize = if #available(iOS 26, *) { .medium } else { .large }
                    configuration.cornerStyle = .capsule
                    configuration.image = .init(systemName: systemName)?
                        .applyingSymbolConfiguration(.init(scale: scale))
                    
                    let button: UIButton = .init(configuration: configuration)
                    button.menu = core.information.menu
                    button.showsMenuAsPrimaryAction = true
                    return button
                }
                
                var home: UIButton {
                    let scale: UIImage.SymbolScale = if #available(iOS 26, *) { .medium } else { .large }
                    let systemName: String = "house"
                    
                    var configuration: UIButton.Configuration = if #available(iOS 26, *) { .glass() } else { .plain() }
                    configuration.buttonSize = if #available(iOS 26, *) { .medium } else { .large }
                    configuration.cornerStyle = .capsule
                    configuration.image = .init(systemName: systemName)?
                        .applyingSymbolConfiguration(.init(scale: scale))
                    
                    let button: UIButton = .init(configuration: configuration, primaryAction: UIAction { action in
                        let (_, _, required) = self.applicationModel.filesManager.missingFiles(for: Core.cytrus.string)
                        if required {
                            let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
                                                                           message: .localized(for: .systemFilesMissingSecondaryText),
                                                                           preferredStyle: .alert)
                            alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
                            self.present(alertController, animated: true)
                        } else {
                            let controller: CytrusController = .init(self.applicationModel,
                                                                     nil)
                            controller.modalPresentationStyle = .fullScreen
                            self.present(controller, animated: true)
                        }
                    })
                    return button
                }
                
                var install: UIButton {
                    let scale: UIImage.SymbolScale = if #available(iOS 26, *) { .medium } else { .large }
                    let systemName: String = "arrow.down"
                    
                    var configuration: UIButton.Configuration = if #available(iOS 26, *) { .glass() } else { .plain() }
                    configuration.buttonSize = if #available(iOS 26, *) { .medium } else { .large }
                    configuration.cornerStyle = .capsule
                    configuration.image = .init(systemName: systemName)?
                        .applyingSymbolConfiguration(.init(scale: scale))
                    
                    let button: UIButton = .init(configuration: configuration, primaryAction: UIAction { action in
                        self.importFilesType = .cia
                        self.systemFileToImport = nil
                        
                        let documentPickerController: UIDocumentPickerViewController = .init(forOpeningContentTypes: [
                            .item
                        ], asCopy: true)
                        documentPickerController.allowsMultipleSelection = true
                        
                        if let sheetPresentationController = documentPickerController.sheetPresentationController {
                            sheetPresentationController.detents = [
                                .medium(),
                                .large()
                            ]
                        }
                        documentPickerController.delegate = self
                        self.present(documentPickerController, animated: true)
                    })
                    return button
                }
                
                let aboutAccessory: UICellAccessory = .customView(configuration: .init(customView: about, placement: .trailing(), reservedLayoutWidth: .actual))
                
                // let homeAccessory: UICellAccessory = .customView(configuration: .init(customView: home, placement: .trailing(), reservedLayoutWidth: .actual))
                
                let installAccessory: UICellAccessory = .customView(configuration: .init(customView: install, placement: .trailing(), reservedLayoutWidth: .actual))
                
                supplementaryView.accessories = if let dataSource = self.dataSource, let section = dataSource.sectionIdentifier(for: indexPath.section), section == .cytrus {
                    [installAccessory, aboutAccessory]
                } else {
                    [aboutAccessory]
                }
            }
            supplementaryView.contentConfiguration = contentConfiguration
        }
        
        let cherryCellRegistration: UICollectionView.CellRegistration<CherryCell, NewCherryGame> = .init { cell, indexPath, itemIdentifier in
            cell.set(cherryGame: itemIdentifier, controller: self)
        }
        
        let cytrusCellRegistration: UICollectionView.CellRegistration<CytrusCell, NewCytrusGame> = .init { cell, indexPath, itemIdentifier in
            cell.set(cytrusGame: itemIdentifier, controller: self)
        }
        
        let grapeCellRegistration: UICollectionView.CellRegistration<GrapeCell, NewGrapeGame> = .init { cell, indexPath, itemIdentifier in
            cell.set(grapeGame: itemIdentifier, controller: self)
        }
        
        let guavaCellRegistration: UICollectionView.CellRegistration<GuavaCell, NewGuavaGame> = .init { cell, indexPath, itemIdentifier in
            cell.set(guavaGame: itemIdentifier, controller: self)
        }
        
        let kiwiCellRegistration: UICollectionView.CellRegistration<KiwiCell, NewKiwiGame> = .init { cell, indexPath, itemIdentifier in
            cell.set(kiwiGame: itemIdentifier, controller: self)
        }
        
        let mandarineCellRegistration: UICollectionView.CellRegistration<MandarineCell, NewMandarineGame> = .init { cell, indexPath, itemIdentifier in
            cell.set(mandarineGame: itemIdentifier, controller: self)
        }
        
        let mangoCellRegistration: UICollectionView.CellRegistration<MangoCell, NewMangoGame> = .init { cell, indexPath, itemIdentifier in
            cell.set(mangoGame: itemIdentifier, controller: self)
        }
        
        let plumCellRegistration: UICollectionView.CellRegistration<PlumCell, NewPlumGame> = .init { cell, indexPath, itemIdentifier in
            cell.set(plumGame: itemIdentifier, controller: self)
        }
        
        let tomatoCellRegistration: UICollectionView.CellRegistration<TomatoCell, NewTomatoGame> = .init { cell, indexPath, itemIdentifier in
            cell.set(tomatoGame: itemIdentifier, controller: self)
        }
        
        dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            switch itemIdentifier {
            case let cherryGame as NewCherryGame:
                collectionView.dequeueConfiguredReusableCell(using: cherryCellRegistration, for: indexPath, item: cherryGame)
            case let cytrusGame as NewCytrusGame:
                collectionView.dequeueConfiguredReusableCell(using: cytrusCellRegistration, for: indexPath, item: cytrusGame)
            case let grapeGame as NewGrapeGame:
                collectionView.dequeueConfiguredReusableCell(using: grapeCellRegistration, for: indexPath, item: grapeGame)
            case let guavaGame as NewGuavaGame:
                collectionView.dequeueConfiguredReusableCell(using: guavaCellRegistration, for: indexPath, item: guavaGame)
            case let kiwiGame as NewKiwiGame:
                collectionView.dequeueConfiguredReusableCell(using: kiwiCellRegistration, for: indexPath, item: kiwiGame)
            case let mandarineGame as NewMandarineGame:
                collectionView.dequeueConfiguredReusableCell(using: mandarineCellRegistration, for: indexPath, item: mandarineGame)
            case let mangoGame as NewMangoGame:
                collectionView.dequeueConfiguredReusableCell(using: mangoCellRegistration, for: indexPath, item: mangoGame)
            case let plumGame as NewPlumGame:
                collectionView.dequeueConfiguredReusableCell(using: plumCellRegistration, for: indexPath, item: plumGame)
            case let tomatoGame as NewTomatoGame:
                collectionView.dequeueConfiguredReusableCell(using: tomatoCellRegistration, for: indexPath, item: tomatoGame)
            default:
                nil
            }
        }
        guard let dataSource else {
            return
        }
        
        dataSource.supplementaryViewProvider = { collectionView, elementKind, indexPath in
            collectionView.dequeueConfiguredReusableSupplementary(using: headerCellRegistration, for: indexPath)
        }
        
        snapshot = .init()
        
        Task {
            await populateLibrary()
        }
        
        let refreshControl: UIRefreshControl = .init()
        refreshControl.addTarget(self, action: #selector(pullToRefreshLibrary(_:)), for: .valueChanged)
        collectionView.refreshControl = refreshControl
        
        // UserDefaults.standard.removeObject(forKey: "folium.v1.39.whatsNewComplete")
#if targetEnvironment(simulator)
        // UserDefaults.standard.removeObject(forKey: "folium.v1.39.whatsNewComplete")
#endif
        if !UserDefaults.standard.bool(forKey: "folium.v1.39.whatsNewComplete") {
            let whatsNewModel: WhatsNewModel = WhatsNewModel(applicationModel: applicationModel)
            Task {
                await whatsNewModel.whatsNew(controller: self)
            }
        }
    }
    
    func populateLibrary() async {
        guard let filteredCores: [String] = UserDefaults.standard.stringArray(forKey: "folium.v1.38.filteredCores") else {
            return
        }
        
        guard let dataSource, var snapshot else {
            return
        }
        
        var (coresWithGames, games, errors): (AnyRangeReplaceableCollection<Core>, [BaseGame], [Core : [AnyError]]) = await applicationModel.gamesManager.games()
        
        if !errors.values.allSatisfy(\.isEmpty) {
            let item: UIBarButtonItem = .init(image: .init(systemName: "exclamationmark.octagon"),
                                              primaryAction: .init { _ in
                let libraryErrorController: LibraryIssuesController = .init(self.applicationModel, errors)
                let navigationController: UINavigationController = .init(rootViewController: libraryErrorController)
                navigationController.modalPresentationStyle = .fullScreen
                self.present(navigationController, animated: true)
            })
            item.tintColor = .systemOrange
            
            // navigationItem.trailingItemGroups = [
            //     .init(barButtonItems: [
            //         item
            //     ], representativeItem: nil),
            //     .init(barButtonItems: navigationItem.rightBarButtonItems?.reversed() ?? [], representativeItem: nil)
            // ]
            // navigationItem.rightBarButtonItems = nil
        }
        
        games.removeAll(where: { !filteredCores.contains($0.getCore()?.string ?? "") })
        coresWithGames.removeAll(where: { !filteredCores.contains($0.string) })
        
        if #available(iOS 26, *) {
            navigationItem.largeSubtitle = "\(games.count) game\(games.count == 1 ? "" : "s") available"
            navigationItem.subtitle = navigationItem.largeSubtitle
        }
        
        snapshot.appendSections(coresWithGames.map { $0 })
        snapshot.sectionIdentifiers.forEach { core in
            let filtered = games.filter { game in
                switch game {
                case let cherryGame as NewCherryGame:
                    cherryGame.core == core
                case let cytrusGame as NewCytrusGame:
                    cytrusGame.core == core
                case let grapeGame as NewGrapeGame:
                    grapeGame.core == core
                case let guavaGame as NewGuavaGame:
                    guavaGame.core == core
                case let kiwiGame as NewKiwiGame:
                    kiwiGame.core == core
                case let mandarineGame as NewMandarineGame:
                    mandarineGame.core == core
                case let mangoGame as NewMangoGame:
                    mangoGame.core == core
                case let plumCore as NewPlumGame:
                    plumCore.core == core
                case let tomatoGame as NewTomatoGame:
                    tomatoGame.core == core
                default: false
                }
            }
            
            snapshot.appendItems(filtered.sorted(), toSection: core)
        }
        
        await dataSource.apply(snapshot)
    }
    
    @objc func pullToRefreshLibrary(_ refreshControl: UIRefreshControl) {
        Task {
            refreshControl.beginRefreshing()
            await populateLibrary()
            refreshControl.endRefreshing()
        }
    }
    
    override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        collectionView.deselectItem(at: indexPath, animated: true)
        guard let dataSource, let item = dataSource.itemIdentifier(for: indexPath) else {
            return
        }
        
        if let cherryGame: NewCherryGame = item.asGame() {
            Task {
                let (_, _, required) = await applicationModel.filesManager.missingFiles(for: cherryGame.core.string)
                if required {
                    let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
                                                                   message: .localized(for: .systemFilesMissingSecondaryText),
                                                                   preferredStyle: .alert)
                    alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
                    present(alertController, animated: true)
                } else {
                    let viewController: CherryController = CherryController(cherryGame,
                                                                            applicationModel.cherry)
                    viewController.modalPresentationStyle = .fullScreen
                    present(viewController, animated: true)
                }
            }
        }
        
        if let cytrusGame: NewCytrusGame = item.asGame() {
            Task {
                let (_, _, required) = await applicationModel.filesManager.missingFiles(for: cytrusGame.core.string)
                if required {
                    let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
                                                                   message: .localized(for: .systemFilesMissingSecondaryText),
                                                                   preferredStyle: .alert)
                    alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
                    present(alertController, animated: true)
                } else {
                    let controller: CytrusController = CytrusController(applicationModel,
                                                                        cytrusGame)
                    controller.modalPresentationStyle = .fullScreen
                    present(controller, animated: true)
                }
            }
        }
        
        if let grapeGame: NewGrapeGame = item.asGame() {
            Task {
                let (_, _, required) = await applicationModel.filesManager.missingFiles(for: grapeGame.core.string)
                if required {
                    let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
                                                                   message: .localized(for: .systemFilesMissingSecondaryText),
                                                                   preferredStyle: .alert)
                    alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
                    present(alertController, animated: true)
                } else {
                    let controller: UIViewController = GrapeController(grapeGame, await applicationModel.combinedCores.grapeCore)
                    controller.modalPresentationStyle = .fullScreen
                    present(controller, animated: true)
                }
            }
        }
        
        if let guavaGame: NewGuavaGame = item.asGame() {
            Task {
                let (_, _, required) = await applicationModel.filesManager.missingFiles(for: guavaGame.core.string)
                if required {
                    let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
                                                                   message: .localized(for: .systemFilesMissingSecondaryText),
                                                                   preferredStyle: .alert)
                    alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
                    present(alertController, animated: true)
                } else {
                    let controller: GuavaController = GuavaController(guavaGame,
                                                                      await applicationModel.combinedCores.guavaCore)
                    controller.modalPresentationStyle = .fullScreen
                    present(controller, animated: true)
                }
            }
        }
        
        if let kiwiGame: NewKiwiGame = item.asGame() {
            Task {
                let kiwiDefaultController: KiwiController = KiwiController(kiwiGame,
                                                                           await applicationModel.combinedCores.kiwiCore)
                kiwiDefaultController.modalPresentationStyle = .fullScreen
                present(kiwiDefaultController, animated: true)
            }
        }
        
        if let mandarineGame: NewMandarineGame = item.asGame() {
            Task {
                let (_, _, required) = await applicationModel.filesManager.missingFiles(for: mandarineGame.core.string)
                if required {
                    let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
                                                                   message: .localized(for: .systemFilesMissingSecondaryText),
                                                                   preferredStyle: .alert)
                    alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
                    present(alertController, animated: true)
                } else {
                    let controller: MandarineController = MandarineController(mandarineGame, await applicationModel.combinedCores.mandarineCore)
                    controller.modalPresentationStyle = .fullScreen
                    present(controller, animated: true)
                }
            }
        }
        
        if let mangoGame: NewMangoGame = item.asGame() {
            let controller: MangoController = .init(mangoGame, applicationModel.mango)
            controller.modalPresentationStyle = .fullScreen
            present(controller, animated: true)
        }
        
        if let plumGame: NewPlumGame = item.asGame() {
            let controller: PlumController = PlumController(plumGame, applicationModel.plum)
            controller.modalPresentationStyle = .fullScreen
            present(controller, animated: true)
        }
        
        if let tomatoGame: NewTomatoGame = item.asGame() {
            Task {
                let (_, _, required) = await applicationModel.filesManager.missingFiles(for: tomatoGame.core.string)
                if required {
                    let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
                                                                   message: .localized(for: .systemFilesMissingSecondaryText),
                                                                   preferredStyle: .alert)
                    alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
                    present(alertController, animated: true)
                } else {
                    let controller: TomatoController = TomatoController(tomatoGame, applicationModel.tomato)
                    controller.modalPresentationStyle = .fullScreen
                    present(controller, animated: true)
                }
            }
        }
    }
}

extension LibraryController : UIDocumentPickerDelegate {
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        guard let importFilesType else {
            return
        }
        
        switch importFilesType {
        case .cia:
            for url in urls {
                let installed: Bool = applicationModel.cytrus.installCIA(url, {})
                print(installed)
            }
        case .games:
            guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
                return
            }
            
            let task = Task {
                for url in urls {
                    let `extension`: String = url.pathExtension.lowercased()
                    guard let supportedFormat: SupportedFormat = .init(rawValue: `extension`) else {
                        return
                    }
                    
                    let coresForExtension: [Core] = Core.cores(for: supportedFormat)
                    
                    if coresForExtension.count == 1, let core = coresForExtension.first {
                        let romsDirectoryURL: URL = documentDirectory.appending(component: core.string).appending(component: "roms")
                        
                        let to: URL = if supportedFormat == .`3ds` {
                            romsDirectoryURL.appending(component: url.deletingPathExtension().appendingPathExtension("cci").lastPathComponent)
                        } else {
                            romsDirectoryURL.appending(component: url.lastPathComponent)
                        }
                        
                        try FileManager.default.copyItem(at: url, to: to)
                    } else if coresForExtension.count != 0 {
                        let alertController: UIAlertController = .init(title: .localized(for: .conflictingExtension),
                                                                       message: .localized(for: .conflictingExtensionSecondaryText),
                                                                       preferredStyle: .alert)
                        coresForExtension.forEach { core in
                            alertController.addAction(.init(title: core.string,
                                                            style: .default,
                                                            handler: { _ in
                                let romsDirectoryURL: URL = documentDirectory.appending(component: core.string).appending(component: "roms")
                                
                                let to: URL = if supportedFormat == .`3ds` {
                                    romsDirectoryURL.appending(component: url.deletingPathExtension().appendingPathExtension("cci").lastPathComponent)
                                } else {
                                    romsDirectoryURL.appending(component: url.lastPathComponent)
                                }
                                
                                let task = Task {
                                    try FileManager.default.copyItem(at: url, to: to)
                                }
                                
                                Task {
                                    switch await task.result {
                                    case .success:
                                        break
                                    case .failure(let error):
                                        print(#function, #line, error.localizedDescription)
                                    }
                                }
                            }))
                        }
                        alertController.addAction(.init(title: .localized(for: .dismiss),
                                                        style: .cancel))
                        self.present(alertController, animated: true)
                    }
                }
                
                await populateLibrary()
            }
            
            Task {
                switch await task.result {
                case .success:
                    break
                case .failure(let error):
                    print(#function, #line, error.localizedDescription)
                }
            }
        case .systemFiles:
            guard let systemFileToImport, let url = urls.first else {
                return
            }
            
            let task = Task {
                try systemFileToImport.import(from: url)
                await applicationModel.filesManager.import(file: systemFileToImport)
            }
            
            Task {
                switch await task.result {
                case .success:
                    break
                case .failure(let error):
                    print(#function, #line, error.localizedDescription)
                }
            }
        }
        
        self.importFilesType = nil
        self.systemFileToImport = nil
    }
}