//
//  PersistenceService.swift
//  Persistence
//
//  Created by Javier Cicchelli on 10/04/2023.
//  Copyright © 2023 Röck+Cöde. All rights reserved.
//

import CoreData

public struct PersistenceService {
    
    // MARK: Properties
    
    public static let shared = PersistenceService()
    public static let inMemory = PersistenceService(inMemory: true)
    
    public let container: NSPersistentContainer
    
    // MARK: Initialisers
    
    init(inMemory: Bool = false) {
        guard
            let modelURL = Bundle.module.url(forResource: .Model.name, withExtension: .Model.extension),
            let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
        else {
            fatalError("Could not load the model from the library.")
        }

        container = NSPersistentContainer(
            name: .Model.name,
            managedObjectModel: managedObjectModel
        )
        
        setContainer(inMemory)
    }
    
    // MARK: Functions
    
    /// Create a private queue context.
    /// - Returns: A concurrent `NSManagedObjectContext` context instance ready to use.
    public func makeTaskContext() -> NSManagedObjectContext {
        let taskContext = container.newBackgroundContext()
        
        taskContext.automaticallyMergesChangesFromParent = true
        taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        
        return taskContext
    }
    
    /// Create a child context of the view context.
    /// - Returns: A generated child `NSManagedObjectContext` context instance ready to use.
    public func makeChildContext() -> NSManagedObjectContext {
        let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        
        context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        context.parent = container.viewContext
        context.automaticallyMergesChangesFromParent = true
        
        return context
    }
    
    /// Save a given context,
    /// - Parameter context: A `NSManagedObjectContext` context instance to save.
    public func save(context: NSManagedObjectContext) {
        guard context.hasChanges else {
            return
        }
        
        do {
            try context.save()
        } catch {
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }
    
    /// Save a given child context as well as its respective parent context.
    /// - Parameter context: A child `NSManagedObjectContext` context instance to save.
    public func save(childContext context: NSManagedObjectContext) {
        guard context.hasChanges else {
            return
        }
        
        do {
            try context.save()
            
            guard
                let parent = context.parent,
                parent == container.viewContext
            else {
                return
            }

            try parent.performAndWait {
                try parent.save()
            }
        } catch {
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }
    
}

// MARK: - Helpers

private extension PersistenceService {
    func setContainer(_ inMemory: Bool) {
        container.persistentStoreDescriptions = [
            NSPersistentStoreDescription(url:
                inMemory
                    ? URL(fileURLWithPath: "/dev/null")
                    : NSPersistentContainer.defaultDirectoryURL().appending(path: "\(String.Model.name).sqlite")
            )
        ]
        container.loadPersistentStores { _, error in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        }
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

// MARK: - String+Constants

private extension String {
    enum Model {
        static let name = "Model"
        static let `extension` = "momd"
    }
}
