import Foundation enum GiftDateFormatter { static func yyyyMMdd(fromUTC text: String?) -> String? { format(text, format: "yyyy.MM.dd") } static func mmDD(fromUTC text: String?) -> String? { format(text, format: "MM.dd") } private static func format(_ text: String?, format: String) -> String? { guard let text, let date = DateParser.parse(text) else { return nil } let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.timeZone = .autoupdatingCurrent formatter.dateFormat = format return formatter.string(from: date) } } enum GiftDisplayPolicy { static func listCard(from item: GiftListItemResponse) -> GiftListCardDisplayModel? { guard let direction = GiftDirection(rawValue: item.direction), let status = GiftStatus(rawValue: item.status) else { return nil } return GiftListCardDisplayModel( applicationNo: item.applicationNo, direction: direction, status: status, counterpartNickname: item.counterpartNickname, counterpartProfileImageUrl: item.counterpartProfileImageUrl, categoryName: item.categoryName, sizeName: item.sizeName, createdDateText: GiftDateFormatter.yyyyMMdd(fromUTC: item.createdAt), statusDisplay: listStatus(direction: direction, status: status, statusName: item.statusName) ) } static func sentDetail(from detail: GiftDetailResponse) -> SentGiftDetailDisplayModel? { guard let status = status(from: detail, expectedDirection: .sent) else { return nil } return SentGiftDetailDisplayModel( status: status, statusName: detail.statusName, undeliverableReason: detail.delivery.undeliverableReason, recipientCreatorNickname: detail.giftInfo.recipientCreatorNickname, sizeName: detail.giftInfo.sizeName, categoryName: detail.giftInfo.categoryName, applicationNo: detail.applicationNo, paidCan: detail.giftInfo.paidCan, tracking: detail.giftInfo.tracking, shippingRequestedDateText: GiftDateFormatter.yyyyMMdd(fromUTC: detail.giftInfo.shippingRequestedAt), applicationDateText: GiftDateFormatter.yyyyMMdd( fromUTC: detail.statusTimeline.first { $0.status == GiftStatus.received.rawValue }?.occurredAt ), canceledDateText: GiftDateFormatter.yyyyMMdd(fromUTC: detail.delivery.canceledAt), senderInfo: detail.senderInfo.map(GiftAddressDisplayModel.init), mailbox: detail.mailbox.map(GiftAddressDisplayModel.init), trackingRequired: detail.trackingRequired, progress: progress(from: detail.statusTimeline) ) } static func receivedDetail(from detail: GiftDetailResponse) -> ReceivedGiftDetailDisplayModel? { guard let status = status(from: detail, expectedDirection: .received) else { return nil } return ReceivedGiftDetailDisplayModel( status: status, statusName: detail.statusName, undeliverableReason: detail.delivery.undeliverableReason, senderNickname: detail.giftInfo.senderNickname, sizeName: detail.giftInfo.sizeName, categoryName: detail.giftInfo.categoryName, shippingRequestedDateText: GiftDateFormatter.yyyyMMdd(fromUTC: detail.giftInfo.shippingRequestedAt), recipientAddressRequired: status == .undeliverable ? false : detail.recipientAddressRequired, recipientAddress: status == .undeliverable ? nil : detail.recipientAddress.map(GiftAddressDisplayModel.init), progress: progress(from: detail.statusTimeline) ) } static func sizeDescription(for sizeCode: String) -> String? { guard let size = GiftSize(rawValue: sizeCode) else { return nil } switch size { case .small: return "세 변의 합 100cm 이하 · 5kg 이하" case .medium: return "세 변의 합 120cm 이하 · 15kg 이하" case .large: return "세 변의 합 160cm 이하 · 20kg 이하" } } private static func listStatus( direction: GiftDirection, status: GiftStatus, statusName: String ) -> GiftListStatusDisplayModel { switch (direction, status) { case (.sent, .received): return GiftListStatusDisplayModel(title: "운송장 등록 필요", subtitle: nil) case (.sent, .trackingRegistered): return GiftListStatusDisplayModel(title: "발송 확인", subtitle: nil) case (.sent, .arrivedAtMailbox): return GiftListStatusDisplayModel(title: "사서함 도착", subtitle: nil) case (.sent, .inspectionCompleted): return GiftListStatusDisplayModel(title: "선물 검수 완료", subtitle: nil) case (.sent, .delivered): return GiftListStatusDisplayModel(title: "전달 완료", subtitle: nil) case (.sent, .undeliverable): return GiftListStatusDisplayModel(title: "전달 불가", subtitle: nil) case (.sent, .canceled): return GiftListStatusDisplayModel(title: "신청 취소", subtitle: nil) case (.received, .trackingRegistered): return GiftListStatusDisplayModel(title: "배송지 입력 필요", subtitle: "전달 예정") case (.received, .arrivedAtMailbox): return GiftListStatusDisplayModel(title: "선물 검수", subtitle: "선물 확인 중") case (.received, .inspectionCompleted): return GiftListStatusDisplayModel(title: "선물 검수 완료", subtitle: "배송 중") case (.received, .delivered): return GiftListStatusDisplayModel(title: "전달 완료", subtitle: nil) case (.received, .undeliverable): return GiftListStatusDisplayModel(title: "전달 불가", subtitle: nil) default: return GiftListStatusDisplayModel(title: statusName, subtitle: nil) } } private static func status( from detail: GiftDetailResponse, expectedDirection: GiftDirection ) -> GiftStatus? { guard GiftDirection(rawValue: detail.direction) == expectedDirection else { return nil } return GiftStatus(rawValue: detail.status) } private static func progress(from timeline: [GiftStatusTimelineResponse]) -> [GiftProgressDisplayModel] { GiftProgressStep.allCases.map { step in let occurredAt = timeline.first { $0.status == step.rawValue }?.occurredAt return GiftProgressDisplayModel( step: step, occurredDateText: GiftDateFormatter.mmDD(fromUTC: occurredAt) ) } } } private extension GiftAddressDisplayModel { init(_ senderInfo: GiftSenderInfoResponse) { self.init(name: senderInfo.name, phoneNumber: senderInfo.phoneNumber, address: senderInfo.address) } init(_ recipientAddress: GiftRecipientAddressResponse) { self.init(name: recipientAddress.name, phoneNumber: recipientAddress.phoneNumber, address: recipientAddress.address) } init(_ mailbox: GiftMailboxResponse) { self.init(name: mailbox.name, phoneNumber: mailbox.phoneNumber, address: mailbox.address) } }