import Foundation import SwiftUI enum ClimbType: String, CaseIterable, Codable { case rope = "ROPE" case boulder = "BOULDER" var displayName: String { switch self { case .rope: return "Rope" case .boulder: return "Bouldering" } } } enum DifficultySystem: String, CaseIterable, Codable { case vScale = "V_SCALE" case font = "FONT" case yds = "YDS" case custom = "CUSTOM" var displayName: String { switch self { case .vScale: return "V Scale" case .font: return "Font Scale" case .yds: return "YDS (Yosemite)" case .custom: return "Custom" } } var isBoulderingSystem: Bool { switch self { case .vScale, .font: return true case .yds: return false case .custom: return true } } var isRopeSystem: Bool { switch self { case .yds: return true case .vScale, .font: return false case .custom: return true } } var availableGrades: [String] { switch self { case .vScale: return [ "VB", "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9", "V10", "V11", "V12", "V13", "V14", "V15", "V16", "V17", ] case .font: return [ "3", "4A", "4B", "4C", "5A", "5B", "5C", "6A", "6A+", "6B", "6B+", "6C", "6C+", "7A", "7A+", "7B", "7B+", "7C", "7C+", "8A", "8A+", "8B", "8B+", "8C", "8C+", ] case .yds: return [ "5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "5.6", "5.7", "5.8", "5.9", "5.10a", "5.10b", "5.10c", "5.10d", "5.11a", "5.11b", "5.11c", "5.11d", "5.12a", "5.12b", "5.12c", "5.12d", "5.13a", "5.13b", "5.13c", "5.13d", "5.14a", "5.14b", "5.14c", "5.14d", "5.15a", "5.15b", "5.15c", "5.15d", ] case .custom: return [] } } static func systemsForClimbType(_ climbType: ClimbType) -> [DifficultySystem] { switch climbType { case .boulder: return allCases.filter { $0.isBoulderingSystem } case .rope: return allCases.filter { $0.isRopeSystem } } } } enum AttemptResult: String, CaseIterable, Codable { case success = "SUCCESS" case fall = "FALL" case noProgress = "NO_PROGRESS" case flash = "FLASH" var displayName: String { switch self { case .success: return "Success" case .fall: return "Fall" case .noProgress: return "No Progress" case .flash: return "Flash" } } var isSuccessful: Bool { return self == .success || self == .flash } } enum SessionStatus: String, CaseIterable, Codable { case active = "ACTIVE" case completed = "COMPLETED" case paused = "PAUSED" var displayName: String { switch self { case .active: return "Active" case .completed: return "Completed" case .paused: return "Paused" } } } struct DifficultyGrade: Codable, Hashable { let system: DifficultySystem let grade: String let numericValue: Int init(system: DifficultySystem, grade: String) { self.system = system self.grade = grade self.numericValue = Self.calculateNumericValue(system: system, grade: grade) } private static func calculateNumericValue(system: DifficultySystem, grade: String) -> Int { switch system { case .vScale: if grade == "VB" { return 0 } return Int(grade.replacingOccurrences(of: "V", with: "")) ?? 0 case .font: let fontMapping: [String: Int] = [ "3": 3, "4A": 4, "4B": 5, "4C": 6, "5A": 7, "5B": 8, "5C": 9, "6A": 10, "6A+": 11, "6B": 12, "6B+": 13, "6C": 14, "6C+": 15, "7A": 16, "7A+": 17, "7B": 18, "7B+": 19, "7C": 20, "7C+": 21, "8A": 22, "8A+": 23, "8B": 24, "8B+": 25, "8C": 26, "8C+": 27, ] return fontMapping[grade] ?? 0 case .yds: let ydsMapping: [String: Int] = [ "5.0": 50, "5.1": 51, "5.2": 52, "5.3": 53, "5.4": 54, "5.5": 55, "5.6": 56, "5.7": 57, "5.8": 58, "5.9": 59, "5.10a": 60, "5.10b": 61, "5.10c": 62, "5.10d": 63, "5.11a": 64, "5.11b": 65, "5.11c": 66, "5.11d": 67, "5.12a": 68, "5.12b": 69, "5.12c": 70, "5.12d": 71, "5.13a": 72, "5.13b": 73, "5.13c": 74, "5.13d": 75, "5.14a": 76, "5.14b": 77, "5.14c": 78, "5.14d": 79, "5.15a": 80, "5.15b": 81, "5.15c": 82, "5.15d": 83, ] return ydsMapping[grade] ?? 0 case .custom: return Int(grade) ?? 0 } } } struct Gym: Identifiable, Codable, Hashable { let id: UUID let name: String let location: String? let supportedClimbTypes: [ClimbType] let difficultySystems: [DifficultySystem] let customDifficultyGrades: [String] let notes: String? let createdAt: Date let updatedAt: Date init( name: String, location: String? = nil, supportedClimbTypes: [ClimbType], difficultySystems: [DifficultySystem], customDifficultyGrades: [String] = [], notes: String? = nil ) { self.id = UUID() self.name = name self.location = location self.supportedClimbTypes = supportedClimbTypes self.difficultySystems = difficultySystems self.customDifficultyGrades = customDifficultyGrades self.notes = notes let now = Date() self.createdAt = now self.updatedAt = now } func updated( name: String? = nil, location: String? = nil, supportedClimbTypes: [ClimbType]? = nil, difficultySystems: [DifficultySystem]? = nil, customDifficultyGrades: [String]? = nil, notes: String? = nil ) -> Gym { return Gym( id: self.id, name: name ?? self.name, location: location ?? self.location, supportedClimbTypes: supportedClimbTypes ?? self.supportedClimbTypes, difficultySystems: difficultySystems ?? self.difficultySystems, customDifficultyGrades: customDifficultyGrades ?? self.customDifficultyGrades, notes: notes ?? self.notes, createdAt: self.createdAt, updatedAt: Date() ) } private init( id: UUID, name: String, location: String?, supportedClimbTypes: [ClimbType], difficultySystems: [DifficultySystem], customDifficultyGrades: [String], notes: String?, createdAt: Date, updatedAt: Date ) { self.id = id self.name = name self.location = location self.supportedClimbTypes = supportedClimbTypes self.difficultySystems = difficultySystems self.customDifficultyGrades = customDifficultyGrades self.notes = notes self.createdAt = createdAt self.updatedAt = updatedAt } static func fromImport( id: UUID, name: String, location: String?, supportedClimbTypes: [ClimbType], difficultySystems: [DifficultySystem], customDifficultyGrades: [String], notes: String?, createdAt: Date, updatedAt: Date ) -> Gym { return Gym( id: id, name: name, location: location, supportedClimbTypes: supportedClimbTypes, difficultySystems: difficultySystems, customDifficultyGrades: customDifficultyGrades, notes: notes, createdAt: createdAt, updatedAt: updatedAt ) } } struct Problem: Identifiable, Codable, Hashable { let id: UUID let gymId: UUID let name: String? let description: String? let climbType: ClimbType let difficulty: DifficultyGrade let tags: [String] let location: String? let imagePaths: [String] let isActive: Bool let dateSet: Date? let notes: String? let createdAt: Date let updatedAt: Date init( gymId: UUID, name: String? = nil, description: String? = nil, climbType: ClimbType, difficulty: DifficultyGrade, tags: [String] = [], location: String? = nil, imagePaths: [String] = [], dateSet: Date? = nil, notes: String? = nil ) { self.id = UUID() self.gymId = gymId self.name = name self.description = description self.climbType = climbType self.difficulty = difficulty self.tags = tags self.location = location self.imagePaths = imagePaths self.isActive = true self.dateSet = dateSet self.notes = notes let now = Date() self.createdAt = now self.updatedAt = now } func updated( name: String? = nil, description: String? = nil, climbType: ClimbType? = nil, difficulty: DifficultyGrade? = nil, tags: [String]? = nil, location: String? = nil, imagePaths: [String]? = nil, isActive: Bool? = nil, dateSet: Date? = nil, notes: String? = nil ) -> Problem { return Problem( id: self.id, gymId: self.gymId, name: name ?? self.name, description: description ?? self.description, climbType: climbType ?? self.climbType, difficulty: difficulty ?? self.difficulty, tags: tags ?? self.tags, location: location ?? self.location, imagePaths: imagePaths ?? self.imagePaths, isActive: isActive ?? self.isActive, dateSet: dateSet ?? self.dateSet, notes: notes ?? self.notes, createdAt: self.createdAt, updatedAt: Date() ) } private init( id: UUID, gymId: UUID, name: String?, description: String?, climbType: ClimbType, difficulty: DifficultyGrade, tags: [String], location: String?, imagePaths: [String], isActive: Bool, dateSet: Date?, notes: String?, createdAt: Date, updatedAt: Date ) { self.id = id self.gymId = gymId self.name = name self.description = description self.climbType = climbType self.difficulty = difficulty self.tags = tags self.location = location self.imagePaths = imagePaths self.isActive = isActive self.dateSet = dateSet self.notes = notes self.createdAt = createdAt self.updatedAt = updatedAt } static func fromImport( id: UUID, gymId: UUID, name: String?, description: String?, climbType: ClimbType, difficulty: DifficultyGrade, tags: [String], location: String?, imagePaths: [String], isActive: Bool, dateSet: Date?, notes: String?, createdAt: Date, updatedAt: Date ) -> Problem { return Problem( id: id, gymId: gymId, name: name, description: description, climbType: climbType, difficulty: difficulty, tags: tags, location: location, imagePaths: imagePaths, isActive: isActive, dateSet: dateSet, notes: notes, createdAt: createdAt, updatedAt: updatedAt ) } } struct ClimbSession: Identifiable, Codable, Hashable { let id: UUID let gymId: UUID let date: Date let startTime: Date? let endTime: Date? let duration: Int? // Duration in minutes let status: SessionStatus let notes: String? let createdAt: Date let updatedAt: Date init(gymId: UUID, notes: String? = nil) { self.id = UUID() self.gymId = gymId let now = Date() self.date = now self.startTime = now self.endTime = nil self.duration = nil self.status = .active self.notes = notes self.createdAt = now self.updatedAt = now } func completed() -> ClimbSession { let endTime = Date() let durationMinutes = startTime != nil ? Int(endTime.timeIntervalSince(startTime!) / 60) : nil return ClimbSession( id: self.id, gymId: self.gymId, date: self.date, startTime: self.startTime, endTime: endTime, duration: durationMinutes, status: .completed, notes: self.notes, createdAt: self.createdAt, updatedAt: Date() ) } func updated(notes: String? = nil, status: SessionStatus? = nil) -> ClimbSession { return ClimbSession( id: self.id, gymId: self.gymId, date: self.date, startTime: self.startTime, endTime: self.endTime, duration: self.duration, status: status ?? self.status, notes: notes ?? self.notes, createdAt: self.createdAt, updatedAt: Date() ) } private init( id: UUID, gymId: UUID, date: Date, startTime: Date?, endTime: Date?, duration: Int?, status: SessionStatus, notes: String?, createdAt: Date, updatedAt: Date ) { self.id = id self.gymId = gymId self.date = date self.startTime = startTime self.endTime = endTime self.duration = duration self.status = status self.notes = notes self.createdAt = createdAt self.updatedAt = updatedAt } static func fromImport( id: UUID, gymId: UUID, date: Date, startTime: Date?, endTime: Date?, duration: Int?, status: SessionStatus, notes: String?, createdAt: Date, updatedAt: Date ) -> ClimbSession { return ClimbSession( id: id, gymId: gymId, date: date, startTime: startTime, endTime: endTime, duration: duration, status: status, notes: notes, createdAt: createdAt, updatedAt: updatedAt ) } } struct Attempt: Identifiable, Codable, Hashable { let id: UUID let sessionId: UUID let problemId: UUID let result: AttemptResult let highestHold: String? let notes: String? let duration: Int? let restTime: Int? let timestamp: Date let createdAt: Date let updatedAt: Date init( sessionId: UUID, problemId: UUID, result: AttemptResult, highestHold: String? = nil, notes: String? = nil, duration: Int? = nil, restTime: Int? = nil, timestamp: Date = Date() ) { let now = Date() self.id = UUID() self.sessionId = sessionId self.problemId = problemId self.result = result self.highestHold = highestHold self.notes = notes self.duration = duration self.restTime = restTime self.timestamp = timestamp self.createdAt = now self.updatedAt = now } func updated( problemId: UUID? = nil, result: AttemptResult? = nil, highestHold: String? = nil, notes: String? = nil, duration: Int? = nil, restTime: Int? = nil ) -> Attempt { return Attempt( id: self.id, sessionId: self.sessionId, problemId: problemId ?? self.problemId, result: result ?? self.result, highestHold: highestHold ?? self.highestHold, notes: notes ?? self.notes, duration: duration ?? self.duration, restTime: restTime ?? self.restTime, timestamp: self.timestamp, createdAt: self.createdAt, updatedAt: Date() ) } private init( id: UUID, sessionId: UUID, problemId: UUID, result: AttemptResult, highestHold: String?, notes: String?, duration: Int?, restTime: Int?, timestamp: Date, createdAt: Date, updatedAt: Date ) { self.id = id self.sessionId = sessionId self.problemId = problemId self.result = result self.highestHold = highestHold self.notes = notes self.duration = duration self.restTime = restTime self.timestamp = timestamp self.createdAt = createdAt self.updatedAt = updatedAt } static func fromImport( id: UUID, sessionId: UUID, problemId: UUID, result: AttemptResult, highestHold: String?, notes: String?, duration: Int?, restTime: Int?, timestamp: Date, createdAt: Date, updatedAt: Date ) -> Attempt { return Attempt( id: id, sessionId: sessionId, problemId: problemId, result: result, highestHold: highestHold, notes: notes, duration: duration, restTime: restTime, timestamp: timestamp, createdAt: createdAt, updatedAt: updatedAt ) } } extension DifficultyGrade: Comparable { static func < (lhs: DifficultyGrade, rhs: DifficultyGrade) -> Bool { if lhs.system != rhs.system { return false // Can't compare different systems } return lhs.numericValue < rhs.numericValue } }