import Foundation
import OpenAPIRuntime

extension Decodable {
    init(source: any Encodable) throws {
        self = try _decode(from: source)
    }
}

extension Encodable {
    func decode<T: Decodable>(_ keyPath: KeyPath<Self, OpenAPIValueContainer?>, fallback: @autoclosure () -> T) throws -> T {
        let source = self[keyPath: keyPath]
        guard let source else { return fallback() }
        return try _decode(from: source)
    }

    func decode<T: Decodable>(_ keyPath: KeyPath<Self, OpenAPIValueContainer?>) throws -> T? {
        let source = self[keyPath: keyPath]
        guard let source else { return nil }
        return try _decode(from: source)
    }

    func decodeRequired<T: Decodable>(_ keyPath: KeyPath<Self, OpenAPIValueContainer?>, file: StaticString = #file, line: UInt = #line) throws -> T {
        let source = self[keyPath: keyPath]
        guard let source else {
            #if DEBUG
                fatalError("Required property not found", file: file, line: line)
            #else
                throw DecodingError.valueNotFound(T.self, .init(codingPath: [], debugDescription: "Required property not found"))
            #endif
        }
        return try _decode(from: source)
    }

    func decodeRequired<T: Decodable>(_ keyPath: KeyPath<Self, T?>, file: StaticString = #file, line: UInt = #line) throws -> T {
        let source = self[keyPath: keyPath]
        guard let source else {
            #if DEBUG
                fatalError("Required property not found", file: file, line: line)
            #else
                throw DecodingError.valueNotFound(T.self, .init(codingPath: [], debugDescription: "Required property not found"))
            #endif
        }
        return source
    }

    func decode<T: Decodable>(_ keyPath: KeyPath<Self, OpenAPIValueContainer>) throws -> T {
        return try _decode(from: self[keyPath: keyPath])
    }
}

private func _decode<T: Decodable>(from source: any Encodable) throws -> T {
    if let value = (source as? OpenAPIValueContainer)?.value as? T {
        return value
    }
    let data = try JSONEncoder().encode(source)
    return try JSONDecoder().decode(T.self, from: data)
}
