import Foundation struct AppOrderSummary: Decodable, Identifiable { let id: String let shortId: String? let total: Double? let status: String? let paymentStatus: String? let paymentMethod: String? let deliveryType: String? let storeName: String? let createdAt: String? let updatedAt: String? enum CodingKeys: String, CodingKey { case id case shortId case total case status case paymentStatus case paymentMethod case deliveryType case storeName case createdAt case updatedAt } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = (try? container.decode(String.self, forKey: .id)) ?? UUID().uuidString shortId = try? container.decode(String.self, forKey: .shortId) total = ApiService.decodeFlexibleDouble(from: container, keys: [.total]) status = try? container.decode(String.self, forKey: .status) paymentStatus = try? container.decode(String.self, forKey: .paymentStatus) paymentMethod = try? container.decode(String.self, forKey: .paymentMethod) deliveryType = try? container.decode(String.self, forKey: .deliveryType) storeName = try? container.decode(String.self, forKey: .storeName) createdAt = try? container.decode(String.self, forKey: .createdAt) updatedAt = try? container.decode(String.self, forKey: .updatedAt) } } struct PublicOrderResult: Codable, Identifiable { let id: String let shortId: String? let status: String? let paymentStatus: String? let paymentMethod: String? let deliveryType: String? let total: Double? let storeName: String? let createdAt: String? let updatedAt: String? let otp: String? let customerOtp: String? let confirmOtp: String? let items: [PublicOrderItem] let timeline: [PublicOrderTimelineEvent] enum CodingKeys: String, CodingKey { case id case shortId case status case paymentStatus case paymentMethod case deliveryType case total case storeName case createdAt case updatedAt case otp case customerOtp case confirmOtp case items case timeline } init( id: String, shortId: String? = nil, status: String? = nil, paymentStatus: String? = nil, paymentMethod: String? = nil, deliveryType: String? = nil, total: Double? = nil, storeName: String? = nil, createdAt: String? = nil, updatedAt: String? = nil, otp: String? = nil, customerOtp: String? = nil, confirmOtp: String? = nil, items: [PublicOrderItem] = [], timeline: [PublicOrderTimelineEvent] = [] ) { self.id = id self.shortId = shortId self.status = status self.paymentStatus = paymentStatus self.paymentMethod = paymentMethod self.deliveryType = deliveryType self.total = total self.storeName = storeName self.createdAt = createdAt self.updatedAt = updatedAt self.otp = otp self.customerOtp = customerOtp self.confirmOtp = confirmOtp self.items = items self.timeline = timeline } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = (try? container.decode(String.self, forKey: .id)) ?? UUID().uuidString shortId = try? container.decode(String.self, forKey: .shortId) status = try? container.decode(String.self, forKey: .status) paymentStatus = try? container.decode(String.self, forKey: .paymentStatus) paymentMethod = try? container.decode(String.self, forKey: .paymentMethod) deliveryType = try? container.decode(String.self, forKey: .deliveryType) total = ApiService.decodeFlexibleDouble(from: container, keys: [.total]) storeName = try? container.decode(String.self, forKey: .storeName) createdAt = try? container.decode(String.self, forKey: .createdAt) updatedAt = try? container.decode(String.self, forKey: .updatedAt) otp = try? container.decode(String.self, forKey: .otp) customerOtp = try? container.decode(String.self, forKey: .customerOtp) confirmOtp = try? container.decode(String.self, forKey: .confirmOtp) items = (try? container.decode([PublicOrderItem].self, forKey: .items)) ?? [] timeline = (try? container.decode([PublicOrderTimelineEvent].self, forKey: .timeline)) ?? [] } var displayOtpCode: String? { let values = [customerOtp, otp, confirmOtp] for value in values { let trimmed = (value ?? "").trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty == false { return trimmed } } return nil } var isInDeliveryRoute: Bool { let normalized = (status ?? "").uppercased() if normalized.contains("OUT_FOR_DELIVERY") { return true } if normalized.contains("EM_ROTA") { return true } if normalized.contains("ON_ROUTE") { return true } if normalized.contains("ROTA") { return true } return false } var isFinalStatus: Bool { let normalized = (status ?? "").uppercased() return normalized == "COMPLETED" || normalized == "CANCELED" || normalized == "REFUNDED" } var isPaymentConfirmed: Bool { let payment = (paymentStatus ?? "").uppercased() let currentStatus = (status ?? "").uppercased() if Self.looksConfirmed(payment) || Self.looksConfirmed(currentStatus) { return true } // Fallback: alguns ambientes atualizam apenas a timeline primeiro. if timeline.contains(where: { event in let statusValue = (event.status ?? "").uppercased() let messageValue = (event.message ?? "").uppercased() return Self.looksConfirmed(statusValue) || Self.looksConfirmed(messageValue) }) { return true } return false } private static func looksConfirmed(_ value: String) -> Bool { if value.isEmpty { return false } if value.contains("PENDING") || value.contains("AWAIT") { return false } if value.contains("FAILED") || value.contains("ERROR") { return false } if value.contains("CANCEL") || value.contains("REFUND") { return false } if value.contains("CONFIRM") { return true } if value.contains("APPROV") { return true } if value.contains("PAID") { return true } if value.contains("RECEIV") { return true } return value == "SUCCESS" || value == "DONE" } } struct PublicOrderItem: Codable, Identifiable { let id: String let name: String? let qty: Int? let price: Double? enum CodingKeys: String, CodingKey { case id case name case qty case quantity case price } init(id: String = UUID().uuidString, name: String?, qty: Int?, price: Double?) { self.id = id self.name = name self.qty = qty self.price = price } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = (try? container.decode(String.self, forKey: .id)) ?? UUID().uuidString name = try? container.decode(String.self, forKey: .name) qty = ApiService.decodeFlexibleInt(from: container, keys: [.qty, .quantity]) price = ApiService.decodeFlexibleDouble(from: container, keys: [.price]) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encodeIfPresent(name, forKey: .name) try container.encodeIfPresent(qty, forKey: .qty) try container.encodeIfPresent(price, forKey: .price) } } struct PublicOrderTimelineEvent: Codable, Identifiable { let id: String let status: String? let message: String? let time: String? enum CodingKeys: String, CodingKey { case id case status case message case time case createdAt case updatedAt } init(id: String = UUID().uuidString, status: String?, message: String?, time: String?) { self.id = id self.status = status self.message = message self.time = time } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = (try? container.decode(String.self, forKey: .id)) ?? UUID().uuidString status = try? container.decode(String.self, forKey: .status) message = try? container.decode(String.self, forKey: .message) time = (try? container.decode(String.self, forKey: .time)) ?? (try? container.decode(String.self, forKey: .createdAt)) ?? (try? container.decode(String.self, forKey: .updatedAt)) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encodeIfPresent(status, forKey: .status) try container.encodeIfPresent(message, forKey: .message) try container.encodeIfPresent(time, forKey: .time) } } struct OrderRealtimeUpdate: Decodable { let id: String? let shortId: String? let storeId: String? let userId: String? let status: String? let paymentStatus: String? let updatedAt: String? } extension CreateOrderResult { func asPublicOrderResult() -> PublicOrderResult { PublicOrderResult( id: id ?? UUID().uuidString, shortId: shortId, status: status, paymentStatus: paymentStatus, paymentMethod: paymentMethod ) } }