/// Types conforming to this protocol can be constructed from `JSValue`.
public protocol ConstructibleFromJSValue {
    /// Construct an instance of `Self`, if possible, from the given `JSValue`.
    /// Return `nil` if the value is not compatible with the conforming Swift type.
    ///
    /// - Parameter value: The `JSValue` to decode
    /// - Returns: An instance of `Self`, if one was successfully constructed from the value.
    static func construct(from value: JSValue) -> Self?
}

extension Bool: ConstructibleFromJSValue {
    public static func construct(from value: JSValue) -> Bool? {
        value.boolean
    }
}

extension String: ConstructibleFromJSValue {
    public static func construct(from value: JSValue) -> String? {
        value.string
    }
}

extension JSString: ConstructibleFromJSValue {
    public static func construct(from value: JSValue) -> JSString? {
        value.jsString
    }
}

extension BinaryFloatingPoint where Self: ConstructibleFromJSValue {
    public static func construct(from value: JSValue) -> Self? {
        value.number.map(Self.init)
    }
}
extension Double: ConstructibleFromJSValue {}
extension Float: ConstructibleFromJSValue {}

extension SignedInteger where Self: ConstructibleFromJSValue {
    /// Construct an instance of `SignedInteger` from the given `JSBigIntExtended`.
    ///
    /// If the value is too large to fit in the `Self` type, `nil` is returned.
    ///
    /// - Parameter bigInt: The `JSBigIntExtended` to decode
    public init?(exactly bigInt: some JSBigIntExtended) {
        self.init(exactly: bigInt.int64Value)
    }

    /// Construct an instance of `SignedInteger` from the given `JSBigIntExtended`.
    ///
    /// Crash if the value is too large to fit in the `Self` type.
    ///
    /// - Parameter bigInt: The `JSBigIntExtended` to decode
    public init(_ bigInt: some JSBigIntExtended) {
        self.init(bigInt.int64Value)
    }

    /// Construct an instance of `SignedInteger` from the given `JSValue`.
    ///
    /// Returns `nil` if one of the following conditions is met:
    /// - The value is not a number or a bigint.
    /// - The value is a number that does not fit or cannot be represented
    ///   in the `Self` type (e.g. NaN, Infinity).
    /// - The value is a bigint that does not fit in the `Self` type.
    ///
    /// If the value is a number, it is rounded towards zero before conversion.
    ///
    /// - Parameter value: The `JSValue` to decode
    public static func construct(from value: JSValue) -> Self? {
        if let number = value.number {
            return Self(exactly: number.rounded(.towardZero))
        }
#if !hasFeature(Embedded)
        if let bigInt = value.bigInt as? JSBigIntExtended {
            return Self(exactly: bigInt)
        }
#endif
        return nil
    }
}
extension Int: ConstructibleFromJSValue {}
extension Int8: ConstructibleFromJSValue {}
extension Int16: ConstructibleFromJSValue {}
extension Int32: ConstructibleFromJSValue {}
extension Int64: ConstructibleFromJSValue {}

extension UnsignedInteger where Self: ConstructibleFromJSValue {

    /// Construct an instance of `UnsignedInteger` from the given `JSBigIntExtended`.
    ///
    /// Returns `nil` if the value is negative or too large to fit in the `Self` type.
    ///
    /// - Parameter bigInt: The `JSBigIntExtended` to decode
    public init?(exactly bigInt: some JSBigIntExtended) {
        self.init(exactly: bigInt.uInt64Value)
    }

    /// Construct an instance of `UnsignedInteger` from the given `JSBigIntExtended`.
    ///
    /// Crash if the value is negative or too large to fit in the `Self` type.
    ///
    /// - Parameter bigInt: The `JSBigIntExtended` to decode
    public init(_ bigInt: some JSBigIntExtended) {
        self.init(bigInt.uInt64Value)
    }

    /// Construct an instance of `UnsignedInteger` from the given `JSValue`.
    ///
    /// Returns `nil` if one of the following conditions is met:
    /// - The value is not a number or a bigint.
    /// - The value is a number that does not fit or cannot be represented
    ///  in the `Self` type (e.g. NaN, Infinity).
    /// - The value is a bigint that does not fit in the `Self` type.
    /// - The value is negative.
    ///
    /// - Parameter value: The `JSValue` to decode
    public static func construct(from value: JSValue) -> Self? {
        if let number = value.number {
            return Self(exactly: number.rounded(.towardZero))
        }
#if !hasFeature(Embedded)
        if let bigInt = value.bigInt as? JSBigIntExtended {
            return Self(exactly: bigInt)
        }
#endif
        return nil
    }
}
extension UInt: ConstructibleFromJSValue {}
extension UInt8: ConstructibleFromJSValue {}
extension UInt16: ConstructibleFromJSValue {}
extension UInt32: ConstructibleFromJSValue {}
extension UInt64: ConstructibleFromJSValue {}
