import SwiftUI /// Pitch type breakdown for a pitcher showing distribution and average velocity struct PitchArsenalView: View { let allPlays: [LiveFeedPlay] let pitcherName: String private var pitchSummary: [PitchTypeSummary] { var grouped: [String: (count: Int, totalSpeed: Double, speedCount: Int)] = [:] for play in allPlays { guard let events = play.playEvents else { continue } for event in events where event.isPitch == true { let type = event.pitchTypeDescription var entry = grouped[type, default: (0, 0, 0)] entry.count += 1 if let speed = event.speedMPH { entry.totalSpeed += speed entry.speedCount += 1 } grouped[type] = entry } } let total = grouped.values.reduce(0) { $0 + $1.count } guard total > 0 else { return [] } return grouped.map { type, data in PitchTypeSummary( name: type, count: data.count, percentage: Double(data.count) / Double(total) * 100, avgSpeed: data.speedCount > 0 ? data.totalSpeed / Double(data.speedCount) : nil ) } .sorted { $0.count > $1.count } } var body: some View { let summary = pitchSummary if !summary.isEmpty { DataPanel(.standard) { VStack(alignment: .leading, spacing: 12) { HStack { Text("PITCH ARSENAL") .dataLabelStyle() Spacer() Text(pitcherName) .font(DS.Fonts.bodySmall) .foregroundStyle(DS.Colors.textTertiary) } ForEach(summary, id: \.name) { pitch in pitchRow(pitch, maxCount: summary.first?.count ?? 1) } } } } } @ViewBuilder private func pitchRow(_ pitch: PitchTypeSummary, maxCount: Int) -> some View { HStack(spacing: 10) { Text(pitch.name) .font(pitchNameFont) .foregroundStyle(DS.Colors.textSecondary) .frame(width: nameWidth, alignment: .leading) .lineLimit(1) GeometryReader { geo in let fraction = maxCount > 0 ? CGFloat(pitch.count) / CGFloat(maxCount) : 0 RoundedRectangle(cornerRadius: 3) .fill(barColor(for: pitch.name)) .frame(width: geo.size.width * fraction) } .frame(height: barHeight) Text("\(Int(pitch.percentage))%") .font(DS.Fonts.dataValueCompact) .foregroundStyle(DS.Colors.textPrimary) .frame(width: pctWidth, alignment: .trailing) if let speed = pitch.avgSpeed { Text("\(Int(speed))") .font(DS.Fonts.dataValueCompact) .foregroundStyle(DS.Colors.textTertiary) .frame(width: speedWidth, alignment: .trailing) Text("mph") .dataLabelStyle() } } } private func barColor(for pitchType: String) -> Color { let lowered = pitchType.lowercased() if lowered.contains("fastball") || lowered.contains("sinker") || lowered.contains("cutter") { return DS.Colors.live } if lowered.contains("slider") || lowered.contains("sweep") { return DS.Colors.interactive } if lowered.contains("curve") || lowered.contains("knuckle") { return DS.Colors.media } if lowered.contains("change") || lowered.contains("split") { return DS.Colors.positive } return DS.Colors.warning } #if os(tvOS) private var nameWidth: CGFloat { 140 } private var pctWidth: CGFloat { 44 } private var speedWidth: CGFloat { 36 } private var barHeight: CGFloat { 14 } private var pitchNameFont: Font { DS.Fonts.bodySmall } #else private var nameWidth: CGFloat { 100 } private var pctWidth: CGFloat { 36 } private var speedWidth: CGFloat { 30 } private var barHeight: CGFloat { 10 } private var pitchNameFont: Font { .system(size: 12, weight: .medium) } #endif } private struct PitchTypeSummary { let name: String let count: Int let percentage: Double let avgSpeed: Double? }