import Foundation

/// All settings, all non-secret. There are no credentials of any kind: meetings come
/// from the Calendar app on this Mac, which macOS has already authorised.
///
/// Decoding is deliberately lenient (every field `decodeIfPresent`): adding a new
/// setting in a later version must never make an existing config file unreadable and
/// silently reset everything.
struct Config: Codable {
    var calendarEnabled: Bool = true

    /// Fire this many minutes before a meeting starts.
    var meetingLeadMinutes: Int = 5
    /// How often to check both sources.
    var pollSeconds: Int = 60

    /// Which macOS calendars to watch, by `EKCalendar.calendarIdentifier`.
    /// Populated with everything you have the first time access is granted, so
    /// unticking one in Settings is what excludes it.
    var selectedCalendarIds: [String] = []
    /// Guards the one-time "select everything" fill above.
    var calendarsInitialised: Bool = false


    /// Overall size of the alien on screen. 1.0 is subtle, 1.4 films well.
    var sceneScale: Double = 1.4

    /// Start with macOS.
    var openAtLogin: Bool = false

    /// Empty = cycle through the brand colourways on every appearance.
    /// Otherwise a Palette name to stick to.
    var fixedPalette: String = ""

    static var fileURL: URL {
        let dir = FileManager.default
            .homeDirectoryForCurrentUser
            .appendingPathComponent(".config/mission-control-alien", isDirectory: true)
        return dir.appendingPathComponent("config.json")
    }

    init() {}

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        let d = Config()
        calendarEnabled = try c.decodeIfPresent(Bool.self, forKey: .calendarEnabled) ?? d.calendarEnabled
        meetingLeadMinutes = try c.decodeIfPresent(Int.self, forKey: .meetingLeadMinutes) ?? d.meetingLeadMinutes
        pollSeconds = try c.decodeIfPresent(Int.self, forKey: .pollSeconds) ?? d.pollSeconds
        selectedCalendarIds = try c.decodeIfPresent([String].self, forKey: .selectedCalendarIds) ?? d.selectedCalendarIds
        calendarsInitialised = try c.decodeIfPresent(Bool.self, forKey: .calendarsInitialised) ?? d.calendarsInitialised
        sceneScale = try c.decodeIfPresent(Double.self, forKey: .sceneScale) ?? d.sceneScale
        fixedPalette = try c.decodeIfPresent(String.self, forKey: .fixedPalette) ?? d.fixedPalette
        openAtLogin = try c.decodeIfPresent(Bool.self, forKey: .openAtLogin) ?? d.openAtLogin
    }

    /// Loads config, writing a default file on first run.
    static func load() -> Config {
        let url = fileURL
        if let data = try? Data(contentsOf: url),
           let cfg = try? JSONDecoder().decode(Config.self, from: data) {
            return cfg
        }
        let fresh = Config()
        fresh.save()
        return fresh
    }

    func save() {
        let url = Config.fileURL
        try? FileManager.default.createDirectory(
            at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
        if let data = try? encoder.encode(self) {
            try? data.write(to: url, options: .atomic)
        }
    }
}
