import Foundation
import SwiftData

/// Creates the SwiftData cache container. The store is disposable: schema
/// changes and iCloud account changes wipe it, and `SyncEngine` rebuilds it
/// from the files (an empty cache is the entire contract between the two).
enum NotesModelContainer {
    /// Bump whenever the cache schema changes — wipes and rebuilds from files.
    /// v2 added the flattened audio columns on `NoteEntity`.
    static let cacheSchemaVersion = 2
    private static let cacheSchemaVersionKey = "Notes.cacheSchemaVersion"
    private static let identityTokenKey = "Notes.iCloudIdentityToken"

    private static var storeURL: URL {
        URL.applicationSupportDirectory.appending(path: "Notes.store")
    }

    static func make(inMemory: Bool = false) -> ModelContainer {
        let schema = Schema([NoteEntity.self])

        if !inMemory {
            try? FileManager.default.createDirectory(
                at: storeURL.deletingLastPathComponent(), withIntermediateDirectories: true)
            wipeIfNeeded()
            wipeIfAccountChanged()

            do {
                // `cloudKitDatabase: .none` is load-bearing: the parameter
                // defaults to `.automatic`, which — because the app is
                // iCloud-entitled — would silently mirror the cache to
                // CloudKit on top of the file-based sync.
                let config = ModelConfiguration(schema: schema, url: storeURL, cloudKitDatabase: .none)
                let container = try ModelContainer(for: schema, configurations: [config])
                UserDefaults.standard.set(cacheSchemaVersion, forKey: cacheSchemaVersionKey)
                return container
            } catch {
                print("Notes: ModelContainer creation failed at \(storeURL.path): \(error). Falling back to in-memory.")
            }
        }

        // In-memory: screenshot/test mode, or last resort so the app still
        // launches (degraded, cache-only for the session) rather than crashing.
        do {
            let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true, cloudKitDatabase: .none)
            return try ModelContainer(for: schema, configurations: [config])
        } catch {
            fatalError("Notes: could not create even an in-memory ModelContainer: \(error)")
        }
    }

    private static func wipeIfNeeded() {
        let stored = UserDefaults.standard.integer(forKey: cacheSchemaVersionKey)
        guard stored < cacheSchemaVersion else { return }
        wipeStore()
    }

    /// Wipes when the signed-in iCloud account's ubiquity identity token
    /// differs from the one the cache was built for. A nil token is not a
    /// change — it's legitimately nil for the same account early in launch.
    private static func wipeIfAccountChanged() {
        guard let current = currentIdentityTokenData() else { return }
        let stored = UserDefaults.standard.data(forKey: identityTokenKey)
        guard current != stored else { return }
        wipeStore()
        UserDefaults.standard.set(current, forKey: identityTokenKey)
    }

    private static func currentIdentityTokenData() -> Data? {
        guard let token = FileManager.default.ubiquityIdentityToken else { return nil }
        return try? NSKeyedArchiver.archivedData(withRootObject: token, requiringSecureCoding: false)
    }

    /// Removes the on-disk store and its SQLite WAL/SHM sidecars. Safe
    /// because the store is a rebuildable cache.
    private static func wipeStore() {
        for url in [storeURL, URL(fileURLWithPath: storeURL.path + "-wal"), URL(fileURLWithPath: storeURL.path + "-shm")] {
            try? FileManager.default.removeItem(at: url)
        }
    }
}
