1*a2ddfabcSTomasz Sapeta // Copyright 2023-present 650 Industries. All rights reserved.
2*a2ddfabcSTomasz Sapeta 
3*a2ddfabcSTomasz Sapeta /**
4*a2ddfabcSTomasz Sapeta  Represents a JavaScript function that can be called by the native code and that must return the given generic `ReturnType`.
5*a2ddfabcSTomasz Sapeta  */
6*a2ddfabcSTomasz Sapeta public final class JavaScriptFunction<ReturnType>: AnyArgument, AnyJavaScriptValue {
7*a2ddfabcSTomasz Sapeta   /**
8*a2ddfabcSTomasz Sapeta    Raw representation of the JavaScript function that doesn't impose any restrictions on the returned type.
9*a2ddfabcSTomasz Sapeta    */
10*a2ddfabcSTomasz Sapeta   private let rawFunction: RawJavaScriptFunction
11*a2ddfabcSTomasz Sapeta 
12*a2ddfabcSTomasz Sapeta   /**
13*a2ddfabcSTomasz Sapeta    Weak reference to the app context that is necessary to convert some arguments associated with the context (e.g. shared objects).
14*a2ddfabcSTomasz Sapeta    */
15*a2ddfabcSTomasz Sapeta   private weak var appContext: AppContext?
16*a2ddfabcSTomasz Sapeta 
17*a2ddfabcSTomasz Sapeta   init(rawFunction: RawJavaScriptFunction, appContext: AppContext) {
18*a2ddfabcSTomasz Sapeta     self.rawFunction = rawFunction
19*a2ddfabcSTomasz Sapeta     self.appContext = appContext
20*a2ddfabcSTomasz Sapeta   }
21*a2ddfabcSTomasz Sapeta 
22*a2ddfabcSTomasz Sapeta   // MARK: - Calling
23*a2ddfabcSTomasz Sapeta 
24*a2ddfabcSTomasz Sapeta   /**
25*a2ddfabcSTomasz Sapeta    Calls the function with the given `this` object and arguments.
26*a2ddfabcSTomasz Sapeta    */
callnull27*a2ddfabcSTomasz Sapeta   public func call(_ arguments: Any..., usingThis this: JavaScriptObject? = nil) throws -> ReturnType {
28*a2ddfabcSTomasz Sapeta     return try call(withArguments: arguments, asConstructor: false, usingThis: this)
29*a2ddfabcSTomasz Sapeta   }
30*a2ddfabcSTomasz Sapeta 
31*a2ddfabcSTomasz Sapeta   /**
32*a2ddfabcSTomasz Sapeta    Calls the function as a constructor with the given arguments. It's like calling a function with the `new` keyword.
33*a2ddfabcSTomasz Sapeta    */
callAsConstructornull34*a2ddfabcSTomasz Sapeta   public func callAsConstructor(_ arguments: Any...) throws -> ReturnType {
35*a2ddfabcSTomasz Sapeta     return try call(withArguments: arguments, asConstructor: true, usingThis: nil)
36*a2ddfabcSTomasz Sapeta   }
37*a2ddfabcSTomasz Sapeta 
38*a2ddfabcSTomasz Sapeta   /**
39*a2ddfabcSTomasz Sapeta    Universal function that calls the function with given arguments, this object and whether to call it as a constructor.
40*a2ddfabcSTomasz Sapeta    */
callnull41*a2ddfabcSTomasz Sapeta   private func call(withArguments arguments: [Any] = [], asConstructor: Bool = false, usingThis this: JavaScriptObject? = nil) throws -> ReturnType {
42*a2ddfabcSTomasz Sapeta     guard let appContext else {
43*a2ddfabcSTomasz Sapeta       throw AppContextLostException()
44*a2ddfabcSTomasz Sapeta     }
45*a2ddfabcSTomasz Sapeta     let value = rawFunction.call(withArguments: arguments, thisObject: this, asConstructor: false)
46*a2ddfabcSTomasz Sapeta     let dynamicType = ~ReturnType.self
47*a2ddfabcSTomasz Sapeta 
48*a2ddfabcSTomasz Sapeta     guard let result = try dynamicType.cast(jsValue: value, appContext: appContext) as? ReturnType else {
49*a2ddfabcSTomasz Sapeta       throw UnexpectedReturnType(dynamicType.description)
50*a2ddfabcSTomasz Sapeta     }
51*a2ddfabcSTomasz Sapeta     return result
52*a2ddfabcSTomasz Sapeta   }
53*a2ddfabcSTomasz Sapeta 
54*a2ddfabcSTomasz Sapeta   // MARK: - AnyJavaScriptValue
55*a2ddfabcSTomasz Sapeta 
convertnull56*a2ddfabcSTomasz Sapeta   internal static func convert(from value: JavaScriptValue, appContext: AppContext) throws -> Self {
57*a2ddfabcSTomasz Sapeta     guard value.kind == .function else {
58*a2ddfabcSTomasz Sapeta       throw Conversions.ConvertingException<JavaScriptFunction<ReturnType>>(value)
59*a2ddfabcSTomasz Sapeta     }
60*a2ddfabcSTomasz Sapeta     return Self(rawFunction: value.getFunction(), appContext: appContext)
61*a2ddfabcSTomasz Sapeta   }
62*a2ddfabcSTomasz Sapeta }
63*a2ddfabcSTomasz Sapeta 
64*a2ddfabcSTomasz Sapeta private final class UnexpectedReturnType: GenericException<String> {
65*a2ddfabcSTomasz Sapeta   override var reason: String {
66*a2ddfabcSTomasz Sapeta     return "The function returned a value that cannot be converted to \(param)"
67*a2ddfabcSTomasz Sapeta   }
68*a2ddfabcSTomasz Sapeta }
69