import Foundation

/// Mirrors lib/widget_data/widget_data.dart. JSON keys must stay in sync —
/// the bridge is one-way: Dart writes, Swift reads.
enum WidgetLessonStatus: String, Codable {
    case regular
    case ongoing
    case past
    case cancelled
    case irregular
    case teacherChanged
    case event
    case duty

    /// Unknown future statuses degrade to `.regular` instead of failing the
    /// whole payload decode (mirrors WidgetData.kt's fromWire fallback) — a
    /// single new enum value must never blank the widget to the placeholder.
    init(from decoder: Decoder) throws {
        let raw = try decoder.singleValueContainer().decode(String.self)
        self = WidgetLessonStatus(rawValue: raw) ?? .regular
    }
}

struct WidgetLesson: Codable {
    let start: Date
    let end: Date
    let subjectShort: String
    let subjectLong: String?
    let room: String?
    // On teacher accounts this carries the class label ("7a") instead of the
    // teacher short name (originalTeacher is nil then) — mapped in Dart.
    let teacher: String?
    let originalTeacher: String?
    let status: WidgetLessonStatus
    let customColor: String?
    let siblingCount: Int?
}

struct WidgetPeriod: Codable {
    let name: String
    let startMinutes: Int
    let endMinutes: Int
    let virtualStartMinutes: Int
    let virtualEndMinutes: Int
}

struct WidgetDayInfo: Codable {
    let date: Date
    let isHoliday: Bool
    let holidayName: String?
}

struct WidgetTimetableData: Codable {
    let fetchedAt: Date
    let anchorDate: Date
    let lessons: [WidgetLesson]
    let periods: [WidgetPeriod]
    let isHoliday: Bool
    let holidayName: String?
    /// Week payload (v2) only; optional so day payloads keep decoding.
    let days: [WidgetDayInfo]?
}

/// Mirrors lib/widget_data/widget_sync.dart (the canonical key list) — a
/// schema bump must land in Dart, Kotlin (WidgetRenderer.kt) and here
/// together, or the out-of-sync platform silently blanks to the placeholder.
enum WidgetDataKey {
    static let appGroupId = "group.eu.mhsl.marianum.mobile.client.widget"
    static let dayData = "widget_data_day_v1"
    static let weekData = "widget_data_week_v2"
    static let loggedIn = "widget_data_logged_in_v1"
    static let themeMode = "widget_setting_theme_mode_v1"
}

enum WidgetDataLoader {
    /// Dart's `DateTime.toIso8601String()` on a non-UTC DateTime drops the
    /// trailing Z and ships local wall-clock time. ISO8601DateFormatter's
    /// default treats that as UTC and shifts every lesson by the local TZ
    /// offset — dispatch by suffix instead, mirroring WidgetDataParser.kt.
    private static func parseDartDate(_ raw: String) -> Date? {
        let hasTzSuffix = raw.hasSuffix("Z")
            || raw.range(of: #"[+-]\d{2}:?\d{2}$"#, options: .regularExpression) != nil
        if hasTzSuffix {
            let iso = ISO8601DateFormatter()
            iso.formatOptions = [.withFullDate, .withFullTime, .withFractionalSeconds]
            if let d = iso.date(from: raw) { return d }
            iso.formatOptions = [.withFullDate, .withFullTime]
            return iso.date(from: raw)
        }
        for pattern in [
            "yyyy-MM-dd'T'HH:mm:ss.SSSSSS",
            "yyyy-MM-dd'T'HH:mm:ss.SSS",
            "yyyy-MM-dd'T'HH:mm:ss",
        ] {
            let f = DateFormatter()
            f.dateFormat = pattern
            f.timeZone = TimeZone.current
            f.locale = Locale(identifier: "en_US_POSIX")
            if let d = f.date(from: raw) { return d }
        }
        return nil
    }

    private static func decoder() -> JSONDecoder {
        let dec = JSONDecoder()
        dec.dateDecodingStrategy = .custom { decoder in
            let container = try decoder.singleValueContainer()
            let raw = try container.decode(String.self)
            if let d = parseDartDate(raw) { return d }
            throw DecodingError.dataCorruptedError(
                in: container,
                debugDescription: "Unparseable date: \(raw)"
            )
        }
        return dec
    }

    static func loadDay() -> WidgetTimetableData? {
        load(key: WidgetDataKey.dayData)
    }

    static func loadWeek() -> WidgetTimetableData? {
        load(key: WidgetDataKey.weekData)
    }

    static func isLoggedIn() -> Bool {
        let defaults = UserDefaults(suiteName: WidgetDataKey.appGroupId)
        return defaults?.bool(forKey: WidgetDataKey.loggedIn) ?? false
    }

    /// "light" / "dark" / "system". The view's `.environment(\.colorScheme)`
    /// reads this so the App's theme choice wins over the OS-level setting.
    static func themeMode() -> String {
        let defaults = UserDefaults(suiteName: WidgetDataKey.appGroupId)
        return defaults?.string(forKey: WidgetDataKey.themeMode) ?? "system"
    }

    private static func load(key: String) -> WidgetTimetableData? {
        guard let defaults = UserDefaults(suiteName: WidgetDataKey.appGroupId),
              let raw = defaults.string(forKey: key),
              let data = raw.data(using: .utf8) else {
            return nil
        }
        do {
            return try decoder().decode(WidgetTimetableData.self, from: data)
        } catch {
            return nil
        }
    }
}
