import Foundation
import MessagePack

// MARK: - Dynamic MessagePack Value

/// Represents any MessagePack value. Used for complex/polymorphic incoming data
/// (e.g. RemoteForm widgets) where the structure is too dynamic for static Codable types.
/// Decoded via the msgpack-swift library's `MessagePackDecoder`.
indirect enum MsgPackValue: Sendable {
    case `nil`
    case bool(Bool)
    case int(Int64)
    case uint(UInt64)
    case float(Float)
    case double(Double)
    case string(String)
    case binary(Data)
    case array([MsgPackValue])
    case map([(MsgPackValue, MsgPackValue)])

    // MARK: Convenience Accessors

    var isNil: Bool {
        if case .nil = self { return true }
        return false
    }

    var boolValue: Bool? {
        if case .bool(let v) = self { return v }
        return nil
    }

    var intValue: Int? {
        switch self {
        case .int(let v): return Int(exactly: v)
        case .uint(let v): return Int(exactly: v)
        default: return nil
        }
    }

    var int64Value: Int64? {
        switch self {
        case .int(let v): return v
        case .uint(let v): return Int64(exactly: v)
        default: return nil
        }
    }

    var stringValue: String? {
        if case .string(let v) = self { return v }
        return nil
    }

    var arrayValue: [MsgPackValue]? {
        if case .array(let v) = self { return v }
        return nil
    }

    var dataValue: Data? {
        if case .binary(let v) = self { return v }
        return nil
    }

    /// Look up value by integer key in an array or map
    subscript(key: Int) -> MsgPackValue? {
        switch self {
        case .array(let items):
            guard key >= 0, key < items.count else { return nil }
            return items[key]
        case .map(let pairs):
            for (k, v) in pairs {
                if k.intValue == key { return v }
            }
            return nil
        default:
            return nil
        }
    }

    /// Look up value by string key in a map
    subscript(key: String) -> MsgPackValue? {
        guard case .map(let pairs) = self else { return nil }
        for (k, v) in pairs {
            if k.stringValue == key { return v }
        }
        return nil
    }
}

// MARK: - Decodable

private struct DynamicCodingKey: CodingKey {
    var stringValue: String
    var intValue: Int?
    init?(stringValue: String) { self.stringValue = stringValue; self.intValue = nil }
    init?(intValue: Int) { self.stringValue = "\(intValue)"; self.intValue = intValue }
}

extension MsgPackValue: Decodable {
    init(from decoder: Decoder) throws {
        // Try map (keyed container)
        if let keyed = try? decoder.container(keyedBy: DynamicCodingKey.self) {
            var pairs: [(MsgPackValue, MsgPackValue)] = []
            for key in keyed.allKeys {
                let val = try keyed.decode(MsgPackValue.self, forKey: key)
                let k: MsgPackValue = key.intValue.map { .int(Int64($0)) } ?? .string(key.stringValue)
                pairs.append((k, val))
            }
            self = .map(pairs)
            return
        }

        // Try array (unkeyed container)
        if var unkeyed = try? decoder.unkeyedContainer() {
            var items: [MsgPackValue] = []
            while !unkeyed.isAtEnd {
                items.append(try unkeyed.decode(MsgPackValue.self))
            }
            self = .array(items)
            return
        }

        // Scalar value
        let c = try decoder.singleValueContainer()
        if c.decodeNil() { self = .nil; return }
        if let v = try? c.decode(Bool.self) { self = .bool(v); return }
        if let v = try? c.decode(String.self) { self = .string(v); return }
        if let v = try? c.decode(UInt64.self) { self = .uint(v); return }
        if let v = try? c.decode(Int64.self) { self = .int(v); return }
        if let v = try? c.decode(Double.self) { self = .double(v); return }
        if let v = try? c.decode(Float.self) { self = .float(v); return }
        if let v = try? c.decode(Data.self) { self = .binary(v); return }
        // MessagePack ext types (e.g. timestamps) — decode as Date, store as string
        if let v = try? c.decode(Date.self) { self = .string(v.description); return }

        throw DecodingError.dataCorrupted(
            DecodingError.Context(codingPath: decoder.codingPath,
                                  debugDescription: "Cannot decode MsgPackValue")
        )
    }
}
