import Foundation

/// Decodes deterministic CBOR bytes (from `CBOREncoder`) back into `Decodable` values.
/// Numeric decoding is type-flexible: a CBOR integer satisfies a requested `Double`
/// (so `JSONValue`'s number probing works) and vice-versa where lossless.
public struct CBORDecoder: Sendable {
    public init() {}

    public func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
        let root = try CBORValue.parse(Array(data))
        let decoder = _CBORDecoder(value: root, codingPath: [])
        return try T(from: decoder)
    }
}

private struct _CBORDecoder: Decoder {
    let value: CBORValue
    var codingPath: [CodingKey]
    let userInfo: [CodingUserInfoKey: Any] = [:]

    func container<Key: CodingKey>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
        guard case .map(let pairs) = value else {
            throw typeMismatch([String: Any].self, "keyed container")
        }
        var dict: [String: CBORValue] = [:]
        for (k, v) in pairs { if case .text(let key) = k { dict[key] = v } }
        return KeyedDecodingContainer(CBORKeyedDecoding(codingPath: codingPath, dict: dict))
    }

    func unkeyedContainer() throws -> UnkeyedDecodingContainer {
        guard case .array(let items) = value else {
            throw typeMismatch([Any].self, "unkeyed container")
        }
        return CBORUnkeyedDecoding(codingPath: codingPath, items: items)
    }

    func singleValueContainer() throws -> SingleValueDecodingContainer {
        CBORSingleValueDecoding(codingPath: codingPath, value: value)
    }

    private func typeMismatch(_ type: Any.Type, _ desc: String) -> DecodingError {
        DecodingError.typeMismatch(
            type, .init(codingPath: codingPath, debugDescription: "Expected \(desc), got \(value)"))
    }
}

// MARK: - Numeric coercion helpers

private enum CBORNumber {
    static func double(_ v: CBORValue, _ path: [CodingKey]) throws -> Double {
        switch v {
        case .float(let d): return d
        case .unsigned(let n): return Double(n)
        case .negative(let n): return -1 - Double(n)
        default: throw mismatch(Double.self, v, path)
        }
    }

    static func int64(_ v: CBORValue, _ path: [CodingKey]) throws -> Int64 {
        switch v {
        case .unsigned(let n):
            guard n <= UInt64(Int64.max) else { throw overflow(Int64.self, path) }
            return Int64(n)
        case .negative(let n):
            guard n <= UInt64(Int64.max) else { throw overflow(Int64.self, path) }
            return -1 - Int64(n)
        case .float(let d) where d == d.rounded():
            return Int64(d)
        default:
            throw mismatch(Int64.self, v, path)
        }
    }

    static func uint64(_ v: CBORValue, _ path: [CodingKey]) throws -> UInt64 {
        switch v {
        case .unsigned(let n): return n
        case .float(let d) where d >= 0 && d == d.rounded(): return UInt64(d)
        default: throw mismatch(UInt64.self, v, path)
        }
    }

    static func mismatch(_ t: Any.Type, _ v: CBORValue, _ path: [CodingKey]) -> DecodingError {
        DecodingError.typeMismatch(
            t, .init(codingPath: path, debugDescription: "Cannot decode \(t) from \(v)"))
    }
    static func overflow(_ t: Any.Type, _ path: [CodingKey]) -> DecodingError {
        DecodingError.dataCorrupted(.init(codingPath: path, debugDescription: "\(t) overflow"))
    }
}

// MARK: - Single value

private struct CBORSingleValueDecoding: SingleValueDecodingContainer {
    var codingPath: [CodingKey]
    let value: CBORValue

    func decodeNil() -> Bool {
        if case .null = value { return true }
        if case .undefined = value { return true }
        return false
    }
    func decode(_ type: Bool.Type) throws -> Bool {
        guard case .bool(let b) = value else { throw CBORNumber.mismatch(Bool.self, value, codingPath) }
        return b
    }
    func decode(_ type: String.Type) throws -> String {
        guard case .text(let s) = value else { throw CBORNumber.mismatch(String.self, value, codingPath) }
        return s
    }
    func decode(_ type: Double.Type) throws -> Double { try CBORNumber.double(value, codingPath) }
    func decode(_ type: Float.Type) throws -> Float { Float(try CBORNumber.double(value, codingPath)) }
    func decode(_ type: Int.Type) throws -> Int { Int(try CBORNumber.int64(value, codingPath)) }
    func decode(_ type: Int8.Type) throws -> Int8 { Int8(try CBORNumber.int64(value, codingPath)) }
    func decode(_ type: Int16.Type) throws -> Int16 { Int16(try CBORNumber.int64(value, codingPath)) }
    func decode(_ type: Int32.Type) throws -> Int32 { Int32(try CBORNumber.int64(value, codingPath)) }
    func decode(_ type: Int64.Type) throws -> Int64 { try CBORNumber.int64(value, codingPath) }
    func decode(_ type: UInt.Type) throws -> UInt { UInt(try CBORNumber.uint64(value, codingPath)) }
    func decode(_ type: UInt8.Type) throws -> UInt8 { UInt8(try CBORNumber.uint64(value, codingPath)) }
    func decode(_ type: UInt16.Type) throws -> UInt16 { UInt16(try CBORNumber.uint64(value, codingPath)) }
    func decode(_ type: UInt32.Type) throws -> UInt32 { UInt32(try CBORNumber.uint64(value, codingPath)) }
    func decode(_ type: UInt64.Type) throws -> UInt64 { try CBORNumber.uint64(value, codingPath) }
    func decode<T: Decodable>(_ type: T.Type) throws -> T {
        try T(from: _CBORDecoder(value: value, codingPath: codingPath))
    }
}

// MARK: - Keyed

private struct CBORKeyedDecoding<Key: CodingKey>: KeyedDecodingContainerProtocol {
    var codingPath: [CodingKey]
    let dict: [String: CBORValue]
    var allKeys: [Key] { dict.keys.compactMap { Key(stringValue: $0) } }

    func contains(_ key: Key) -> Bool { dict[key.stringValue] != nil }

    private func sub(_ key: Key) throws -> _CBORDecoder {
        guard let v = dict[key.stringValue] else {
            throw DecodingError.keyNotFound(
                key, .init(codingPath: codingPath, debugDescription: "No value for \(key.stringValue)"))
        }
        return _CBORDecoder(value: v, codingPath: codingPath + [key])
    }

    func decodeNil(forKey key: Key) throws -> Bool {
        guard let v = dict[key.stringValue] else { return true }
        if case .null = v { return true }
        if case .undefined = v { return true }
        return false
    }
    func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { try sub(key).singleValueContainer().decode(Bool.self) }
    func decode(_ type: String.Type, forKey key: Key) throws -> String { try sub(key).singleValueContainer().decode(String.self) }
    func decode(_ type: Double.Type, forKey key: Key) throws -> Double { try sub(key).singleValueContainer().decode(Double.self) }
    func decode(_ type: Float.Type, forKey key: Key) throws -> Float { try sub(key).singleValueContainer().decode(Float.self) }
    func decode(_ type: Int.Type, forKey key: Key) throws -> Int { try sub(key).singleValueContainer().decode(Int.self) }
    func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { try sub(key).singleValueContainer().decode(Int8.self) }
    func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { try sub(key).singleValueContainer().decode(Int16.self) }
    func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { try sub(key).singleValueContainer().decode(Int32.self) }
    func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { try sub(key).singleValueContainer().decode(Int64.self) }
    func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { try sub(key).singleValueContainer().decode(UInt.self) }
    func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { try sub(key).singleValueContainer().decode(UInt8.self) }
    func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { try sub(key).singleValueContainer().decode(UInt16.self) }
    func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { try sub(key).singleValueContainer().decode(UInt32.self) }
    func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { try sub(key).singleValueContainer().decode(UInt64.self) }
    func decode<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T { try T(from: sub(key)) }

    func nestedContainer<NestedKey: CodingKey>(
        keyedBy type: NestedKey.Type, forKey key: Key
    ) throws -> KeyedDecodingContainer<NestedKey> {
        try sub(key).container(keyedBy: NestedKey.self)
    }
    func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
        try sub(key).unkeyedContainer()
    }
    func superDecoder() throws -> Decoder { _CBORDecoder(value: .map([]), codingPath: codingPath) }
    func superDecoder(forKey key: Key) throws -> Decoder { try sub(key) }
}

// MARK: - Unkeyed

private struct CBORUnkeyedDecoding: UnkeyedDecodingContainer {
    var codingPath: [CodingKey]
    let items: [CBORValue]
    var count: Int? { items.count }
    var isAtEnd: Bool { currentIndex >= items.count }
    var currentIndex: Int = 0

    private mutating func next() throws -> _CBORDecoder {
        guard !isAtEnd else {
            throw DecodingError.valueNotFound(
                Any.self, .init(codingPath: codingPath, debugDescription: "Unkeyed container at end"))
        }
        defer { currentIndex += 1 }
        return _CBORDecoder(value: items[currentIndex], codingPath: codingPath)
    }

    mutating func decodeNil() throws -> Bool {
        if isAtEnd { return false }
        if case .null = items[currentIndex] { currentIndex += 1; return true }
        return false
    }
    mutating func decode(_ type: Bool.Type) throws -> Bool { try next().singleValueContainer().decode(Bool.self) }
    mutating func decode(_ type: String.Type) throws -> String { try next().singleValueContainer().decode(String.self) }
    mutating func decode(_ type: Double.Type) throws -> Double { try next().singleValueContainer().decode(Double.self) }
    mutating func decode(_ type: Float.Type) throws -> Float { try next().singleValueContainer().decode(Float.self) }
    mutating func decode(_ type: Int.Type) throws -> Int { try next().singleValueContainer().decode(Int.self) }
    mutating func decode(_ type: Int8.Type) throws -> Int8 { try next().singleValueContainer().decode(Int8.self) }
    mutating func decode(_ type: Int16.Type) throws -> Int16 { try next().singleValueContainer().decode(Int16.self) }
    mutating func decode(_ type: Int32.Type) throws -> Int32 { try next().singleValueContainer().decode(Int32.self) }
    mutating func decode(_ type: Int64.Type) throws -> Int64 { try next().singleValueContainer().decode(Int64.self) }
    mutating func decode(_ type: UInt.Type) throws -> UInt { try next().singleValueContainer().decode(UInt.self) }
    mutating func decode(_ type: UInt8.Type) throws -> UInt8 { try next().singleValueContainer().decode(UInt8.self) }
    mutating func decode(_ type: UInt16.Type) throws -> UInt16 { try next().singleValueContainer().decode(UInt16.self) }
    mutating func decode(_ type: UInt32.Type) throws -> UInt32 { try next().singleValueContainer().decode(UInt32.self) }
    mutating func decode(_ type: UInt64.Type) throws -> UInt64 { try next().singleValueContainer().decode(UInt64.self) }
    mutating func decode<T: Decodable>(_ type: T.Type) throws -> T { try T(from: next()) }

    mutating func nestedContainer<NestedKey: CodingKey>(
        keyedBy type: NestedKey.Type
    ) throws -> KeyedDecodingContainer<NestedKey> {
        try next().container(keyedBy: NestedKey.self)
    }
    mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
        try next().unkeyedContainer()
    }
    mutating func superDecoder() throws -> Decoder { try next() }
}
