//
//  MIDIDownloadCache.swift
//  DashSmash
//
//  On-disk LRU cache for recently downloaded MIDI files. Keeps the last N
//  songs around so re-picking a track skips the network round-trip, while
//  bounding storage by evicting the least-recently-accessed entries.
//

import CryptoKit
import Foundation

final class MIDIDownloadCache {
    static let shared = MIDIDownloadCache()

    private let directory: URL
    private let indexURL: URL
    private let maxEntries: Int
    private let queue = DispatchQueue(label: "DashSmash.MIDIDownloadCache")

    private init(maxEntries: Int = 20) {
        self.maxEntries = maxEntries
        let base = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
        self.directory = base.appendingPathComponent("MIDIDownloads", isDirectory: true)
        self.indexURL = directory.appendingPathComponent("index.json")
        try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
    }

    func song(for pageURL: URL) -> MIDISong? {
        queue.sync {
            var index = loadIndex()
            let key = cacheKey(for: pageURL)
            guard var entry = index[key] else { return nil }
            let fileURL = directory.appendingPathComponent(entry.fileName)
            guard let data = try? Data(contentsOf: fileURL),
                  let parsed = try? MIDIFileParser.parse(data) else {
                // File missing or corrupt — drop the stale entry so the next
                // request fetches a clean copy.
                index[key] = nil
                saveIndex(index)
                try? FileManager.default.removeItem(at: fileURL)
                return nil
            }
            entry.lastAccess = Date().timeIntervalSince1970
            index[key] = entry
            saveIndex(index)
            return MIDISong(
                title: entry.title,
                artistName: entry.artistName,
                sourcePageURL: entry.sourcePageURL,
                downloadURL: entry.downloadURL,
                data: data,
                parsedFile: parsed
            )
        }
    }

    func store(_ song: MIDISong) {
        queue.sync {
            var index = loadIndex()
            let key = cacheKey(for: song.sourcePageURL)
            let fileName = key + ".mid"
            let fileURL = directory.appendingPathComponent(fileName)
            do {
                try song.data.write(to: fileURL, options: .atomic)
            } catch {
                return
            }
            index[key] = Entry(
                fileName: fileName,
                title: song.title,
                artistName: song.artistName,
                sourcePageURL: song.sourcePageURL,
                downloadURL: song.downloadURL,
                lastAccess: Date().timeIntervalSince1970
            )
            evictIfNeeded(&index)
            saveIndex(index)
        }
    }

    private func evictIfNeeded(_ index: inout [String: Entry]) {
        guard index.count > maxEntries else { return }
        let sorted = index.sorted { $0.value.lastAccess < $1.value.lastAccess }
        let removeCount = index.count - maxEntries
        for (key, entry) in sorted.prefix(removeCount) {
            try? FileManager.default.removeItem(at: directory.appendingPathComponent(entry.fileName))
            index[key] = nil
        }
    }

    private func cacheKey(for url: URL) -> String {
        let digest = SHA256.hash(data: Data(url.absoluteString.utf8))
        return digest.map { String(format: "%02x", $0) }.joined()
    }

    private func loadIndex() -> [String: Entry] {
        guard let data = try? Data(contentsOf: indexURL) else { return [:] }
        return (try? JSONDecoder().decode([String: Entry].self, from: data)) ?? [:]
    }

    private func saveIndex(_ index: [String: Entry]) {
        guard let data = try? JSONEncoder().encode(index) else { return }
        try? data.write(to: indexURL, options: .atomic)
    }

    private struct Entry: Codable {
        let fileName: String
        let title: String
        let artistName: String
        let sourcePageURL: URL
        let downloadURL: URL
        var lastAccess: TimeInterval
    }
}
