import Foundation
import React

@objc(LayersAdServices)
class LayersAdServices: NSObject {

  @objc
  func getAttributionToken(_ resolve: @escaping (Any?) -> Void, rejecter reject: @escaping (String?, String?, Error?) -> Void) {
    if #available(iOS 14.3, *) {
      // Use dynamic dispatch to avoid hard linking AdServices framework
      // (which would cause crashes on iOS < 14.3)
      guard let cls = NSClassFromString("AAAttribution") else {
        resolve(nil)
        return
      }
      let sel = NSSelectorFromString("attributionTokenWithError:")
      guard cls.responds(to: sel) else {
        resolve(nil)
        return
      }
      // Use method(for:) + unsafeBitCast to invoke the throwing ObjC method
      // that takes an NSError** parameter. perform(_:) cannot handle this.
      //
      // The out-parameter MUST be typed `Unmanaged<NSError>?`, not `NSError?`.
      // Per Cocoa's NSError** convention the callee writes the error
      // autoreleased (+0). Storing that through a raw pointer into an
      // ARC-managed `NSError?` bypasses ARC's retain-on-write while ARC still
      // emits a balancing `release` at scope exit, so the autoreleased error is
      // released twice — a crash when the autorelease pool drains (EXC_BAD_ACCESS
      // in objc_autoreleasePoolPop). On the Simulator the AAAttribution class is
      // present (so the guards above pass) but the attribution call itself fails —
      // there is no attribution environment — and writes a non-nil error on every
      // launch, firing the over-release every time. `Unmanaged` opts out of ARC
      // management, leaving the pool as the sole owner. The error is never read,
      // so nothing needs balancing here.
      var error: Unmanaged<NSError>?
      let token = withUnsafeMutablePointer(to: &error) { errorPtr -> String? in
        let imp = cls.method(for: sel)
        typealias MethodType = @convention(c) (AnyClass, Selector, UnsafeMutablePointer<Unmanaged<NSError>?>) -> NSString?
        let method = unsafeBitCast(imp, to: MethodType.self)
        let result = method(cls, sel, errorPtr)
        return result as String?
      }
      resolve(token)
    } else {
      resolve(nil)
    }
  }

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return false
  }
}
