import Foundation

/// Anonymous daily-active-installs (DAI) heartbeat (see `docs/CLOUD_INFRA.md` §4). No PII, no
/// account, no IP — just enough to count active installs and slice them by platform / channel /
/// version. The install ID is per-install, so this counts installs, not users (the relay counts
/// users; §2).
/// Lives in NucleicProtocol so the macOS host and the iOS client share one wire shape; the
/// `platform` field is the primary split between the two.
public struct Heartbeat: Codable, Sendable, Equatable {
    public let installId: String
    /// `"macos"` | `"ios"`.
    public let platform: String
    public let osVersion: String
    public let channel: String
    public let appVersion: String
    public let build: String
    public let arch: String
    public let locale: String

    public init(
        installId: String, platform: String, osVersion: String, channel: String,
        appVersion: String, build: String, arch: String, locale: String
    ) {
        self.installId = installId
        self.platform = platform
        self.osVersion = osVersion
        self.channel = channel
        self.appVersion = appVersion
        self.build = build
        self.arch = arch
        self.locale = locale
    }
}

/// The opt-out switch backing the "Share anonymous usage" Settings toggle. Default **on**
/// (opt-out posture); both apps read it through this one key so the toggle and the reporter
/// can never drift apart.
public enum HeartbeatSettings {
    public static let shareAnonymousUsageKey = "nucleic.shareAnonymousUsage"

    /// True unless the user has explicitly turned the toggle off.
    public static func isEnabled(_ defaults: UserDefaults = .standard) -> Bool {
        defaults.object(forKey: shareAnonymousUsageKey) as? Bool ?? true
    }
}

/// Pure "send at most once per UTC day" gate. No networking here — each platform's reporter owns
/// the URLSession call; this only decides whether today's beat is still owed and records it once
/// sent. UTC day keys avoid double-counting across timezone shifts.
public enum HeartbeatSchedule {
    public static let endpoint = URL(string: "https://api.nucleic.blakeslee.xyz/v1/heartbeat")!
    static let lastDayKey = "nucleic.heartbeat.lastDay"

    public static func isDue(_ defaults: UserDefaults = .standard, now: Date = Date()) -> Bool {
        defaults.string(forKey: lastDayKey) != dayKey(now)
    }

    public static func markSent(_ defaults: UserDefaults = .standard, now: Date = Date()) {
        defaults.set(dayKey(now), forKey: lastDayKey)
    }

    /// `yyyy-MM-dd` in UTC.
    static func dayKey(_ date: Date) -> String {
        var cal = Calendar(identifier: .gregorian)
        cal.timeZone = TimeZone(identifier: "UTC")!
        let c = cal.dateComponents([.year, .month, .day], from: date)
        return String(format: "%04d-%02d-%02d", c.year ?? 0, c.month ?? 0, c.day ?? 0)
    }
}
