//
//  ScenicQueueModels.swift
//  suixinkan
//
//  Created by Codex on 2026/6/25.
//

import Foundation

/// 排队统计响应。
struct ScenicQueueStatsData: Decodable, Equatable {
    let queueCount: Int
    let avgWaitMin: Double
    let time: String

    enum CodingKeys: String, CodingKey {
        case queueCount = "queue_count"
        case avgWaitMin = "avg_wait_min"
        case time
    }

    /// 创建排队统计，主要用于空响应兜底。
    init(queueCount: Int = 0, avgWaitMin: Double = 0, time: String = "") {
        self.queueCount = queueCount
        self.avgWaitMin = avgWaitMin
        self.time = time
    }

    /// 宽松解码统计字段。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        queueCount = try container.decodeLossyInt(forKey: .queueCount) ?? 0
        avgWaitMin = try container.decodeLossyDouble(forKey: .avgWaitMin) ?? 0
        time = try container.decodeLossyString(forKey: .time)
    }
}

/// 排队首页响应，兼容 list 为数组或分页对象两种形态。
struct ScenicQueueHomeData: Decodable, Equatable {
    let stats: ScenicQueueHomeStats?
    let list: ScenicQueueHomeListBlock?
    let time: String?

    enum CodingKeys: String, CodingKey {
        case stats
        case list
        case time
    }

    /// 创建排队首页数据，主要用于空响应兜底。
    init(stats: ScenicQueueHomeStats? = nil, list: ScenicQueueHomeListBlock? = nil, time: String? = nil) {
        self.stats = stats
        self.list = list
        self.time = time
    }

    /// 宽松解码排队首页数据。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        stats = try container.decodeIfPresent(ScenicQueueHomeStats.self, forKey: .stats)
        if let block = try? container.decodeIfPresent(ScenicQueueHomeListBlock.self, forKey: .list) {
            list = block
        } else if let tickets = try? container.decodeIfPresent([ScenicQueueTicket].self, forKey: .list) {
            list = ScenicQueueHomeListBlock(list: tickets)
        } else {
            list = nil
        }
        time = try container.decodeIfPresent(String.self, forKey: .time)
    }
}

/// 排队首页统计块。
struct ScenicQueueHomeStats: Decodable, Equatable {
    let type: Int

    enum CodingKeys: String, CodingKey {
        case type
    }

    /// 创建统计块。
    init(type: Int = 0) {
        self.type = type
    }

    /// 宽松解码统计块。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        type = try container.decodeLossyInt(forKey: .type) ?? 0
    }
}

/// 排队列表分页块。
struct ScenicQueueHomeListBlock: Decodable, Equatable {
    let list: [ScenicQueueTicket]
    let total: Int
    let page: Int
    let pageSize: Int

    enum CodingKeys: String, CodingKey {
        case list
        case total
        case page
        case pageSize = "page_size"
    }

    /// 创建分页块。
    init(list: [ScenicQueueTicket] = [], total: Int? = nil, page: Int = 1, pageSize: Int = 20) {
        self.list = list
        self.total = total ?? list.count
        self.page = page
        self.pageSize = pageSize
    }

    /// 宽松解码分页块。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        list = try container.decodeIfPresent([ScenicQueueTicket].self, forKey: .list) ?? []
        total = try container.decodeLossyInt(forKey: .total) ?? list.count
        page = try container.decodeLossyInt(forKey: .page) ?? 1
        pageSize = try container.decodeLossyInt(forKey: .pageSize) ?? max(list.count, 20)
    }
}

/// 单个排队取号记录。
struct ScenicQueueTicket: Decodable, Equatable, Identifiable {
    let id: Int64
    let queueCode: String
    let mobile: String
    let status: Int
    let statusText: String
    let waitMin: Int
    let aheadCount: Int
    let isCalled: Int
    let createdAt: String
    let calledAt: String
    let expiredAt: String
    let finishedAt: String
    let queueBanLabel: String
    let identityTag: String
    let queueTime: String
    let queueCountToday: Int
    let uid: Int64
    let markAsPhotog: Int
    let markAsFreelancePhotog: Int
    let isMissRequeue: Int
    let missRequeueText: String

    var phoneMasked: String {
        let digits = mobile.filter(\.isNumber)
        if mobile.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return "--" }
        guard digits.count >= 7 else { return mobile }
        return "\(digits.prefix(3))****\(digits.suffix(4))"
    }

    var dialPhoneDigits: String {
        let digits = mobile.filter(\.isNumber)
        return digits.isEmpty ? mobile.filter { !$0.isWhitespace } : digits
    }

    var queueTimeDisplay: String {
        Self.formatQueueTime(queueTime.nonEmptyOrDefault(createdAt))
    }

    var skippedTimeDisplay: String {
        Self.formatQueueTime(expiredAt.nonEmptyOrDefault(calledAt.nonEmptyOrDefault(createdAt)))
    }

    var shouldShowIdentityTag: Bool {
        let text = identityTag.trimmingCharacters(in: .whitespacesAndNewlines)
        return !text.isEmpty && text != "普通用户"
    }

    var isMissRequeueRecord: Bool {
        isMissRequeue == 1
    }

    enum CodingKeys: String, CodingKey {
        case id
        case queueCode = "queue_code"
        case mobile
        case status
        case statusText = "status_text"
        case waitMin = "wait_min"
        case aheadCount = "ahead_count"
        case isCalled = "is_called"
        case createdAt = "created_at"
        case calledAt = "called_at"
        case expiredAt = "expired_at"
        case finishedAt = "finished_at"
        case queueBanLabel = "queue_ban_label"
        case identityTag = "identity_tag"
        case queueTime = "queue_time"
        case queueCountToday = "queue_count_today"
        case uid
        case markAsPhotog = "mark_as_photog"
        case markAsFreelancePhotog = "mark_as_freelance_photog"
        case isMissRequeue = "is_miss_requeue"
        case missRequeueText = "is_miss_requeue_text"
    }

    enum AlternateCodingKeys: String, CodingKey {
        case queueNo = "queue_no"
        case queueNumber = "queue_number"
        case code
        case phone
        case statusName = "status_name"
    }

    /// 创建取号记录，主要用于本地状态替换和测试。
    init(
        id: Int64,
        queueCode: String,
        mobile: String,
        status: Int,
        statusText: String,
        waitMin: Int,
        aheadCount: Int,
        isCalled: Int,
        createdAt: String,
        calledAt: String,
        expiredAt: String,
        finishedAt: String,
        queueBanLabel: String = "",
        identityTag: String = "",
        queueTime: String = "",
        queueCountToday: Int = 0,
        uid: Int64 = 0,
        markAsPhotog: Int = 0,
        markAsFreelancePhotog: Int = 0,
        isMissRequeue: Int = 0,
        missRequeueText: String = ""
    ) {
        self.id = id
        self.queueCode = queueCode
        self.mobile = mobile
        self.status = status
        self.statusText = statusText
        self.waitMin = waitMin
        self.aheadCount = aheadCount
        self.isCalled = isCalled
        self.createdAt = createdAt
        self.calledAt = calledAt
        self.expiredAt = expiredAt
        self.finishedAt = finishedAt
        self.queueBanLabel = queueBanLabel
        self.identityTag = identityTag
        self.queueTime = queueTime
        self.queueCountToday = queueCountToday
        self.uid = uid
        self.markAsPhotog = markAsPhotog
        self.markAsFreelancePhotog = markAsFreelancePhotog
        self.isMissRequeue = isMissRequeue
        self.missRequeueText = missRequeueText
    }

    /// 宽松解码取号记录，兼容 Android 历史字段别名。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let alternate = try decoder.container(keyedBy: AlternateCodingKeys.self)
        id = Int64(try container.decodeLossyInt(forKey: .id) ?? 0)
        queueCode = try container.decodeLossyString(forKey: .queueCode)
            .nonEmptyOrDefault(
                try alternate.decodeLossyString(forKey: .queueNo)
                    .nonEmptyOrDefault(
                        try alternate.decodeLossyString(forKey: .queueNumber)
                            .nonEmptyOrDefault(try alternate.decodeLossyString(forKey: .code))
                    )
            )
        mobile = try container.decodeLossyString(forKey: .mobile)
            .nonEmptyOrDefault(try alternate.decodeLossyString(forKey: .phone))
        status = try container.decodeLossyInt(forKey: .status) ?? 0
        statusText = try container.decodeLossyString(forKey: .statusText)
            .nonEmptyOrDefault(try alternate.decodeLossyString(forKey: .statusName))
        waitMin = try container.decodeLossyInt(forKey: .waitMin) ?? 0
        aheadCount = try container.decodeLossyInt(forKey: .aheadCount) ?? 0
        isCalled = try container.decodeLossyInt(forKey: .isCalled) ?? 0
        createdAt = try container.decodeLossyString(forKey: .createdAt)
        calledAt = try container.decodeLossyString(forKey: .calledAt)
        expiredAt = try container.decodeLossyString(forKey: .expiredAt)
        finishedAt = try container.decodeLossyString(forKey: .finishedAt)
        queueBanLabel = try container.decodeLossyString(forKey: .queueBanLabel).trimmingCharacters(in: .whitespacesAndNewlines)
        identityTag = try container.decodeLossyString(forKey: .identityTag).trimmingCharacters(in: .whitespacesAndNewlines)
        queueTime = try container.decodeLossyString(forKey: .queueTime)
        queueCountToday = max(try container.decodeLossyInt(forKey: .queueCountToday) ?? 0, 0)
        uid = Int64(try container.decodeLossyInt(forKey: .uid) ?? 0)
        markAsPhotog = try container.decodeLossyInt(forKey: .markAsPhotog) ?? 0
        markAsFreelancePhotog = try container.decodeLossyInt(forKey: .markAsFreelancePhotog) ?? 0
        isMissRequeue = try container.decodeLossyInt(forKey: .isMissRequeue) ?? 0
        missRequeueText = try container.decodeLossyString(forKey: .missRequeueText).trimmingCharacters(in: .whitespacesAndNewlines)
    }

    private static func formatQueueTime(_ raw: String) -> String {
        let text = raw.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !text.isEmpty else { return "--" }
        if text.range(of: #"^\d{2}-\d{2}\s+\d{2}:\d{2}$"#, options: .regularExpression) != nil {
            return text
        }
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        for format in ["yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm"] {
            formatter.dateFormat = format
            if let date = formatter.date(from: text) {
                formatter.dateFormat = "MM-dd HH:mm"
                return formatter.string(from: date)
            }
        }
        return text
    }
}

/// 排队动作请求体。
struct ScenicQueueActionRequest: Encodable {
    let id: Int64
}

/// socket token 响应。
struct SocketTokenResponse: Decodable, Equatable {
    let socketToken: String

    enum CodingKeys: String, CodingKey {
        case socketToken = "socket_token"
    }

    /// 创建 socket token 响应。
    init(socketToken: String = "") {
        self.socketToken = socketToken
    }

    /// 宽松解码 socket token。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        socketToken = try container.decodeLossyString(forKey: .socketToken)
    }
}

/// 排队动作响应。
struct ScenicQueueActionData: Decodable, Equatable {
    let id: Int64
    let status: Int
    let statusText: String
    let calledAt: String?

    enum CodingKeys: String, CodingKey {
        case id
        case status
        case statusText = "status_text"
        case calledAt = "called_at"
    }

    /// 创建动作响应。
    init(id: Int64 = 0, status: Int = 0, statusText: String = "", calledAt: String? = nil) {
        self.id = id
        self.status = status
        self.statusText = statusText
        self.calledAt = calledAt
    }

    /// 宽松解码动作响应。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = Int64(try container.decodeLossyInt(forKey: .id) ?? 0)
        status = try container.decodeLossyInt(forKey: .status) ?? 0
        statusText = try container.decodeLossyString(forKey: .statusText)
        calledAt = try container.decodeIfPresent(String.self, forKey: .calledAt)
    }
}

typealias ScenicQueueCallData = ScenicQueueActionData
typealias ScenicQueuePassData = ScenicQueueActionData
typealias ScenicQueueFinishData = ScenicQueueActionData
typealias QueueItem = ScenicQueueTicket

/// 重新排队请求体。
struct ScenicQueueRequeueInsertBeforeRequest: Encodable {
    let recordId: Int64
    let operatorId: Int

    enum CodingKeys: String, CodingKey {
        case recordId = "record_id"
        case operatorId = "operator_id"
    }
}

/// 用户标记请求体。
struct ScenicQueueUserMarkRequest: Encodable, Equatable {
    let uid: Int64
    let scenicId: Int64
    let markAsFreelancePhotog: Int
    let queueBanDays: Int?
    let operatorId: Int?

    enum CodingKeys: String, CodingKey {
        case uid
        case scenicId = "scenic_id"
        case markAsFreelancePhotog = "mark_as_freelance_photog"
        case queueBanDays = "queue_ban_days"
        case operatorId = "operator_id"
    }
}

/// 排队列表类型。
enum QueueListType: Int, CaseIterable, Identifiable {
    case queueing = 1
    case passed = 2

    var id: Int { rawValue }

    /// 分段标题。
    var title: String {
        switch self {
        case .queueing:
            return "当前排队"
        case .passed:
            return "过号列表"
        }
    }

    /// 空态文案。
    var emptyText: String {
        switch self {
        case .queueing:
            return "暂无排队"
        case .passed:
            return "暂无过号"
        }
    }
}

/// 远端叫号提示。
struct ScenicQueueRemoteCallAnnouncement: Identifiable, Equatable {
    let id: Int64
    let queueCode: String
}

private extension KeyedDecodingContainer {
    func decodeLossyString(forKey key: Key) throws -> String {
        if let value = try? decodeIfPresent(String.self, forKey: key) {
            return value
        }
        if let value = try? decodeIfPresent(Int.self, forKey: key) {
            return String(value)
        }
        if let value = try? decodeIfPresent(Double.self, forKey: key) {
            return String(value)
        }
        if let value = try? decodeIfPresent(Bool.self, forKey: key) {
            return value ? "1" : "0"
        }
        return ""
    }

    func decodeLossyInt(forKey key: Key) throws -> Int? {
        if let value = try? decodeIfPresent(Int.self, forKey: key) {
            return value
        }
        if let value = try? decodeIfPresent(Double.self, forKey: key) {
            return Int(value)
        }
        if let value = try? decodeIfPresent(String.self, forKey: key),
           case let text = value.trimmingCharacters(in: .whitespacesAndNewlines),
           !text.isEmpty {
            return Int(text) ?? Int(Double(text) ?? 0)
        }
        if let value = try? decodeIfPresent(Bool.self, forKey: key) {
            return value ? 1 : 0
        }
        return nil
    }

    func decodeLossyDouble(forKey key: Key) throws -> Double? {
        if let value = try? decodeIfPresent(Double.self, forKey: key) {
            return value
        }
        if let value = try? decodeIfPresent(Int.self, forKey: key) {
            return Double(value)
        }
        if let value = try? decodeIfPresent(String.self, forKey: key),
           case let text = value.trimmingCharacters(in: .whitespacesAndNewlines),
           !text.isEmpty {
            return Double(text)
        }
        return nil
    }
}

private extension String {
    func nonEmptyOrDefault(_ fallback: String) -> String {
        let text = trimmingCharacters(in: .whitespacesAndNewlines)
        return text.isEmpty ? fallback : text
    }
}
