import Foundation
import NucleicProtocol

/// The on-device snapshot of the phone's mesh-cast ledger (`CastLedger`) — the activity feed,
/// mesh messages, and runner-presence cards it has received, plus (implicitly) the cursors a
/// reconnect subscribes with. Like `SessionCache`, a *projection cache*, never canonical: a
/// live host always supersedes it; it exists so a relaunch renders the feed instantly and
/// resubscribes O(gap) instead of re-pulling every channel from zero.
///
/// One JSON file under Application Support (`NucleicCastCache/casts.json`) — the ledger's
/// per-channel retention already bounds it. Reads/writes hop onto a utility `Task.detached`.
enum CastCache {
    private static var root: URL? {
        guard let base = try? FileManager.default.url(
            for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        else { return nil }
        return base.appendingPathComponent("NucleicCastCache", isDirectory: true)
    }
    private static var castsURL: URL? { root?.appendingPathComponent("casts.json") }

    /// Load the persisted ledger — called once at launch, before any host connects. The ledger's
    /// init re-applies retention, so a policy change between builds re-caps on load.
    static func loadLedger() -> CastLedger {
        guard let url = castsURL, let data = try? Data(contentsOf: url),
              let casts = try? JSONDecoder().decode([WireCast].self, from: data)
        else { return CastLedger() }
        return CastLedger(casts: casts)
    }

    /// Persist the ledger's snapshot. Off the main actor; callers debounce bursts.
    static func save(_ ledger: CastLedger) async {
        let casts = ledger.allCasts
        await Task.detached(priority: .utility) {
            guard let root, let url = castsURL else { return }
            try? FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
            if let data = try? JSONEncoder().encode(casts) {
                try? data.write(to: url, options: .atomic)
            }
        }.value
    }

    /// Drop the cache — on full unpair (no Mac left whose casts this device should hold).
    static func clear() {
        guard let root else { return }
        try? FileManager.default.removeItem(at: root)
    }
}
