import Foundation
import SharedModels

/// Disk-backed cache for verb example sentences (Issue #27). One JSON file
/// in the Caches directory keyed by verb id; lazy-loaded on first access and
/// write-through on every generation. Matches DictionaryService's disk pattern.
///
/// Cache eviction by the OS is acceptable because contents are regenerable.
@MainActor
@Observable
final class VerbExampleCache {

    /// Bump to invalidate every cached example set. Raised to 2 for Issue #33
    /// — examples generated before the verb-grounding/validation fix could
    /// contain sentences built on the wrong verb.
    private static let cacheVersion = 2

    private struct CacheFile: Codable {
        var version: Int
        var entries: [String: [VerbExample]]
    }

    private var store: [Int: [VerbExample]] = [:]
    private var isLoaded = false

    init() {}

    // MARK: - Public API

    /// Look up cached examples for a verb; returns nil on miss.
    /// Safe to call before `loadIfNeeded()`; it triggers the disk load itself.
    func examples(for verbId: Int) -> [VerbExample]? {
        loadIfNeeded()
        return store[verbId]
    }

    /// Store newly generated examples and persist to disk.
    func setExamples(_ examples: [VerbExample], for verbId: Int) {
        loadIfNeeded()
        store[verbId] = examples
        save()
    }

    // MARK: - Disk I/O

    private static var cacheURL: URL {
        FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
            .appendingPathComponent("verb_examples.json")
    }

    private func loadIfNeeded() {
        guard !isLoaded else { return }
        defer { isLoaded = true }

        guard let data = try? Data(contentsOf: Self.cacheURL),
              let decoded = try? JSONDecoder().decode(CacheFile.self, from: data),
              decoded.version == Self.cacheVersion
        else {
            // Missing, unreadable, old flat format, or stale version — start
            // fresh so pre-fix examples don't linger.
            return
        }

        // Persisted with String keys because JSON object keys are strings;
        // convert back to Int for in-memory lookup.
        var rebuilt: [Int: [VerbExample]] = [:]
        for (key, value) in decoded.entries {
            if let id = Int(key) {
                rebuilt[id] = value
            }
        }
        store = rebuilt
    }

    private func save() {
        let entries = Dictionary(uniqueKeysWithValues: store.map { (String($0.key), $0.value) })
        let file = CacheFile(version: Self.cacheVersion, entries: entries)
        guard let data = try? JSONEncoder().encode(file) else { return }
        try? data.write(to: Self.cacheURL)
    }
}
