import Foundation struct AppOrderSummary: Decodable, Identifiable { let id: String let orderId: String? let realId: String? let shortId: String? let total: Double? let status: String? let statusDetailed: String? let statusLabel: String? let nextAction: 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 orderId case realId case shortId case total case status case statusDetailed case statusLabel case nextAction case paymentStatus case paymentMethod case deliveryType case storeName case date case createdAt case updatedAt } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) orderId = ApiService.decodeFlexibleString(from: container, keys: [.orderId]) id = ApiService.decodeFlexibleString(from: container, keys: [.orderId, .realId, .id]) ?? UUID().uuidString realId = ApiService.decodeFlexibleString(from: container, keys: [.realId]) shortId = ApiService.decodeFlexibleString(from: container, keys: [.shortId]) total = ApiService.decodeFlexibleDouble(from: container, keys: [.total]) status = ApiService.decodeFlexibleString(from: container, keys: [.status]) statusDetailed = ApiService.decodeFlexibleString(from: container, keys: [.statusDetailed]) statusLabel = ApiService.decodeFlexibleString(from: container, keys: [.statusLabel]) nextAction = ApiService.decodeFlexibleString(from: container, keys: [.nextAction]) paymentStatus = ApiService.decodeFlexibleString(from: container, keys: [.paymentStatus]) paymentMethod = ApiService.decodeFlexibleString(from: container, keys: [.paymentMethod]) deliveryType = ApiService.decodeFlexibleString(from: container, keys: [.deliveryType]) storeName = ApiService.decodeFlexibleString(from: container, keys: [.storeName]) let fallbackDate = ApiService.decodeFlexibleString(from: container, keys: [.date]) createdAt = ApiService.decodeFlexibleString(from: container, keys: [.createdAt]) ?? fallbackDate updatedAt = ApiService.decodeFlexibleString(from: container, keys: [.updatedAt]) ?? fallbackDate } } struct PublicOrderResult: Codable, Identifiable { let id: String let shortId: String? let realId: String? let status: String? let paymentStatus: String? let paymentConfirmed: Bool? let paymentMethod: String? let paymentMethodCode: String? let paymentPayload: CreateOrderPaymentPayload? let payment: CreateOrderPaymentInfo? let deliveryType: String? let deliveryTypeLabel: String? let total: Double? let storeName: String? let createdAt: String? let updatedAt: String? let otp: String? let customerOtp: String? let confirmOtp: String? let cancellationReason: String? let items: [PublicOrderItem] let timeline: [PublicOrderTimelineEvent] enum CodingKeys: String, CodingKey { case id case shortId case realId case status case paymentStatus case paymentConfirmed case paymentMethod case paymentMethodCode case paymentPayload case payment case deliveryType case deliveryTypeLabel case total case storeName case createdAt case updatedAt case otp case customerOtp case confirmOtp case cancellationReason case items case timeline } init( id: String, shortId: String? = nil, realId: String? = nil, status: String? = nil, paymentStatus: String? = nil, paymentConfirmed: Bool? = nil, paymentMethod: String? = nil, paymentMethodCode: String? = nil, paymentPayload: CreateOrderPaymentPayload? = nil, payment: CreateOrderPaymentInfo? = nil, deliveryType: String? = nil, deliveryTypeLabel: String? = nil, total: Double? = nil, storeName: String? = nil, createdAt: String? = nil, updatedAt: String? = nil, otp: String? = nil, customerOtp: String? = nil, confirmOtp: String? = nil, cancellationReason: String? = nil, items: [PublicOrderItem] = [], timeline: [PublicOrderTimelineEvent] = [] ) { self.id = id self.shortId = shortId self.realId = realId self.status = status self.paymentStatus = paymentStatus self.paymentConfirmed = paymentConfirmed self.paymentMethod = paymentMethod self.paymentMethodCode = paymentMethodCode self.paymentPayload = paymentPayload self.payment = payment self.deliveryType = deliveryType self.deliveryTypeLabel = deliveryTypeLabel self.total = total self.storeName = storeName self.createdAt = createdAt self.updatedAt = updatedAt self.otp = otp self.customerOtp = customerOtp self.confirmOtp = confirmOtp self.cancellationReason = cancellationReason self.items = items self.timeline = timeline } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = ApiService.decodeFlexibleString(from: container, keys: [.id]) ?? UUID().uuidString shortId = ApiService.decodeFlexibleString(from: container, keys: [.shortId]) realId = ApiService.decodeFlexibleString(from: container, keys: [.realId]) status = ApiService.decodeFlexibleString(from: container, keys: [.status]) paymentStatus = ApiService.decodeFlexibleString(from: container, keys: [.paymentStatus]) paymentConfirmed = try? container.decode(Bool.self, forKey: .paymentConfirmed) paymentMethod = ApiService.decodeFlexibleString(from: container, keys: [.paymentMethod]) paymentMethodCode = ApiService.decodeFlexibleString(from: container, keys: [.paymentMethodCode]) payment = try? container.decode(CreateOrderPaymentInfo.self, forKey: .payment) if let objectPayload = try? container.decode(CreateOrderPaymentPayload.self, forKey: .paymentPayload) { paymentPayload = objectPayload } else if let stringPayload = try? container.decode(String.self, forKey: .paymentPayload) { paymentPayload = CreateOrderPaymentPayload( copyPaste: stringPayload, qrCodeImage: nil, expirationDate: nil ) } else { paymentPayload = nil } deliveryType = ApiService.decodeFlexibleString(from: container, keys: [.deliveryType]) deliveryTypeLabel = ApiService.decodeFlexibleString(from: container, keys: [.deliveryTypeLabel]) total = ApiService.decodeFlexibleDouble(from: container, keys: [.total]) storeName = ApiService.decodeFlexibleString(from: container, keys: [.storeName]) createdAt = ApiService.decodeFlexibleString(from: container, keys: [.createdAt]) updatedAt = ApiService.decodeFlexibleString(from: container, keys: [.updatedAt]) otp = ApiService.decodeFlexibleString(from: container, keys: [.otp]) customerOtp = ApiService.decodeFlexibleString(from: container, keys: [.customerOtp]) confirmOtp = ApiService.decodeFlexibleString(from: container, keys: [.confirmOtp]) cancellationReason = ApiService.decodeFlexibleString(from: container, keys: [.cancellationReason]) 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 { if let paymentConfirmed { return paymentConfirmed } 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 label: String? let active: Bool? let completed: Bool? let message: String? let time: String? enum CodingKeys: String, CodingKey { case id case status case label case active case completed case message case time case createdAt case updatedAt } init( id: String = UUID().uuidString, status: String?, label: String? = nil, active: Bool? = nil, completed: Bool? = nil, message: String?, time: String? ) { self.id = id self.status = status self.label = label self.active = active self.completed = completed 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) label = try? container.decode(String.self, forKey: .label) active = try? container.decode(Bool.self, forKey: .active) completed = try? container.decode(Bool.self, forKey: .completed) 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(label, forKey: .label) try container.encodeIfPresent(active, forKey: .active) try container.encodeIfPresent(completed, forKey: .completed) 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? enum CodingKeys: String, CodingKey { case id case shortId case storeId case userId case status case paymentStatus case updatedAt } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = ApiService.decodeFlexibleString(from: container, keys: [.id]) shortId = ApiService.decodeFlexibleString(from: container, keys: [.shortId]) storeId = ApiService.decodeFlexibleString(from: container, keys: [.storeId]) userId = ApiService.decodeFlexibleString(from: container, keys: [.userId]) status = ApiService.decodeFlexibleString(from: container, keys: [.status]) paymentStatus = ApiService.decodeFlexibleString(from: container, keys: [.paymentStatus]) updatedAt = ApiService.decodeFlexibleString(from: container, keys: [.updatedAt]) } } extension CreateOrderResult { func asPublicOrderResult() -> PublicOrderResult { PublicOrderResult( id: id ?? UUID().uuidString, shortId: shortId, status: status, paymentStatus: paymentStatus, paymentConfirmed: paymentConfirmed, paymentMethod: paymentMethod, paymentPayload: paymentPayload, payment: payment ) } }