import Foundation

/// On-disk JSON shape for each aggregate. Independent from the SwiftData cache
/// entities so the wire format can evolve without dragging the cache schema.
///
/// One document = one aggregate root:
///   • `SplitDocument`   embeds its `[ExerciseDocument]`   → `Splits/<ULID>.json`
///   • `WorkoutDocument` embeds its `[WorkoutLogDocument]` → `Workouts/YYYY/MM/<ULID>.json`
///
/// `schemaVersion` lets us migrate old files on read without forcing a rewrite
/// at sync time, and forward-gates files written by a newer app version.
/// These documents are also the wire format for the iPhone↔Watch bridge.

// MARK: - Split

struct SplitDocument: Codable, Sendable, Equatable, Identifiable {
    var schemaVersion: Int
    var id: String                 // ULID
    var name: String
    var color: String
    var systemImage: String
    var order: Int
    var createdAt: Date
    var updatedAt: Date
    var exercises: [ExerciseDocument]

    static let currentSchema = 1

    var relativePath: String { "Splits/\(id).json" }
}

struct ExerciseDocument: Codable, Sendable, Equatable, Identifiable {
    var id: String                 // ULID
    var name: String
    var order: Int
    var sets: Int
    var reps: Int
    var weight: Int
    var loadType: Int
    var durationSeconds: Int       // total seconds (0 when not a timed exercise)
    var weightLastUpdated: Date?
    var weightReminderWeeks: Int
}

// MARK: - Workout

struct WorkoutDocument: Codable, Sendable, Equatable, Identifiable {
    var schemaVersion: Int
    var id: String                 // ULID (chronological)
    var splitID: String?
    var splitName: String?
    var start: Date
    var end: Date?
    var status: String             // WorkoutStatus raw value
    var createdAt: Date
    var updatedAt: Date
    var logs: [WorkoutLogDocument]

    static let currentSchema = 1

    var relativePath: String { Self.relativePath(id: id, start: start) }

    static func relativePath(id: String, start: Date) -> String {
        let cal = Calendar(identifier: .gregorian)
        let comps = cal.dateComponents([.year, .month], from: start)
        let year = comps.year ?? 1970
        let month = comps.month ?? 1
        return String(format: "Workouts/%04d/%02d/%@.json", year, month, id)
    }
}

struct WorkoutLogDocument: Codable, Sendable, Equatable, Identifiable {
    var id: String                 // ULID
    var exerciseName: String
    var order: Int
    var sets: Int
    var reps: Int
    var weight: Int
    var loadType: Int
    var durationSeconds: Int       // total seconds (0 when not a timed exercise)
    var currentStateIndex: Int
    var completed: Bool
    var status: String             // WorkoutStatus raw value
    var notes: String?
    var date: Date
}

// MARK: - Forward-compatibility gate

/// A file whose `schemaVersion` exceeds the reader's `currentSchema` was written
/// by a newer app version; it must be quarantined, not partially decoded (Codable
/// silently drops unknown keys) and later rewritten — which would downgrade it
/// and lose the newer fields.
protocol VersionedDocument {
    var schemaVersion: Int { get }
    static var currentSchema: Int { get }
}

extension SplitDocument: VersionedDocument {}
extension WorkoutDocument: VersionedDocument {}

extension VersionedDocument {
    /// True if this build can safely read (and rewrite) the document.
    var isReadable: Bool { schemaVersion <= Self.currentSchema }
}

// MARK: - Soft-delete tombstone

/// Lives at `Stubs/<id>.json` and tells every device "this aggregate has been
/// deleted" — important when a remote device missed the `.removed` event for the
/// live file because it was offline. After `gracePeriod` any device can prune it.
struct Tombstone: Codable, Sendable, Equatable {
    enum Kind: String, Codable, Sendable {
        case split
        case workout
    }

    var id: String                 // the aggregate's ULID
    var kind: Kind
    var deletedAt: Date

    var relativePath: String { "Stubs/\(id).json" }

    static let gracePeriod: TimeInterval = 30 * 24 * 60 * 60
}

// MARK: - JSON coding

/// Shared encoder/decoder. ISO-8601 dates and sorted keys for human-readable,
/// diff-friendly files when the container is browsed via the Files app.
enum DocumentCoder {
    static let encoder: JSONEncoder = {
        let e = JSONEncoder()
        e.outputFormatting = [.prettyPrinted, .sortedKeys]
        e.dateEncodingStrategy = .iso8601
        return e
    }()

    static let decoder: JSONDecoder = {
        let d = JSONDecoder()
        d.dateDecodingStrategy = .iso8601
        return d
    }()
}
