import { LoadLibrary, GetClassObject, ObjcObject, GetPointer, FromPointer, CreateProtocolImplementation, DefineClass, CallSuper, CallFunction } from "./native.js";
const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom");
const NATIVE_OBJC_OBJECT = Symbol("nativeObjcObject");
const TYPED_BLOCK_ENCODING = "__nobjcBlockTypeEncoding";
// WeakMap side-channel for O(1) proxy → native object lookup (bypasses Proxy traps)
const nativeObjectMap = new WeakMap();
// Module-scope Set for O(1) lookup instead of per-access array with O(n) .includes()
const BUILT_IN_PROPS = new Set([
    "constructor",
    "valueOf",
    "hasOwnProperty",
    "isPrototypeOf",
    "propertyIsEnumerable",
    "toLocaleString",
    "__proto__",
    "__defineGetter__",
    "__defineSetter__",
    "__lookupGetter__",
    "__lookupSetter__"
]);
// WeakMap cache for NobjcMethod proxies per object to avoid GC pressure
const methodCache = new WeakMap();
class NobjcLibrary {
    constructor(library) {
        const classCache = new Map();
        const handler = {
            wasLoaded: false,
            get(_, className) {
                let cls = classCache.get(className);
                if (cls)
                    return cls;
                if (!this.wasLoaded) {
                    LoadLibrary(library);
                    this.wasLoaded = true;
                }
                const classObject = GetClassObject(className);
                if (classObject === undefined) {
                    // Class not found. Make sure the class exists before trying to access it.
                    return undefined;
                }
                cls = new NobjcObject(classObject);
                classCache.set(className, cls);
                return cls;
            }
        };
        return new Proxy({}, handler);
    }
}
function NobjcMethodNameToObjcSelector(methodName) {
    return methodName.replace(/\$/g, ":");
}
// unused, might be useful for codegen later
function ObjcSelectorToNobjcMethodName(selector) {
    return selector.replace(/:/g, "$");
}
class NobjcObject {
    constructor(object) {
        // Create the proxy handler
        const handler = {
            has(target, p) {
                // Return true for the special Symbol to enable unwrapping
                if (p === NATIVE_OBJC_OBJECT)
                    return true;
                // Return true for inspect symbols so console.log uses custom inspect
                if (p === customInspectSymbol)
                    return true;
                // guard against other symbols
                if (typeof p === "symbol")
                    return Reflect.has(target, p);
                // toString is always present
                if (p === "toString")
                    return true;
                // check if the object responds to the selector
                try {
                    return target.$respondsToSelector(NobjcMethodNameToObjcSelector(p.toString()));
                }
                catch (e) {
                    return false;
                }
            },
            get(target, methodName, receiver) {
                // Return the underlying native object when Symbol is accessed
                if (methodName === NATIVE_OBJC_OBJECT) {
                    return target;
                }
                // Handle customInspectSymbol in get trap instead of mutating native object
                // (avoids hidden class transition that deoptimizes V8 inline caches)
                if (methodName === customInspectSymbol) {
                    return () => proxy.toString();
                }
                // guard against symbols
                if (typeof methodName === "symbol") {
                    return Reflect.get(object, methodName, receiver);
                }
                // handle toString separately (cached to avoid repeated closure allocation and FFI check)
                if (methodName === "toString") {
                    let cache = methodCache.get(object);
                    if (!cache) {
                        cache = new Map();
                        methodCache.set(object, cache);
                    }
                    let fn = cache.get("toString");
                    if (!fn) {
                        // Check directly on native object to avoid triggering proxy has trap
                        const hasUTF8 = target.$respondsToSelector("UTF8String");
                        fn = (hasUTF8
                            ? () => String(object.$msgSend("UTF8String"))
                            : () => String(wrapObjCObjectIfNeeded(object.$msgSend("description"))));
                        cache.set("toString", fn);
                    }
                    return fn;
                }
                // handle other built-in Object.prototype properties (O(1) Set lookup)
                if (BUILT_IN_PROPS.has(methodName)) {
                    return Reflect.get(target, methodName);
                }
                // Return cached method proxy if available, otherwise create and cache
                let cache = methodCache.get(object);
                if (!cache) {
                    cache = new Map();
                    methodCache.set(object, cache);
                }
                let method = cache.get(methodName);
                if (!method) {
                    // Check respondsToSelector on cache miss only, directly on native
                    // object (avoids triggering proxy 'has' trap which would be a second FFI call)
                    const selector = NobjcMethodNameToObjcSelector(methodName);
                    if (!target.$respondsToSelector(selector)) {
                        // special case since JS checks for `.then` on Promise objects
                        if (methodName === "then")
                            return undefined;
                        // Otherwise, throw an error
                        throw new Error(`Method ${methodName} not found on object`);
                    }
                    method = NobjcMethod(object, methodName);
                    cache.set(methodName, method);
                }
                return method;
            }
        };
        // Create the proxy
        const proxy = new Proxy(object, handler);
        // Set custom inspect on the native object so console.log works through the Proxy.
        // Runtimes (Node, Bun) bypass Proxy traps during inspect and read the target directly.
        object[customInspectSymbol] = () => proxy.toString();
        // Store proxy → native mapping in WeakMap for O(1) unwrap (bypasses Proxy traps)
        nativeObjectMap.set(proxy, object);
        // Return the proxy
        return proxy;
    }
}
function normalizeTypedBlockEncoding(signature) {
    if (typeof signature === "string") {
        if (!signature.startsWith("@?")) {
            throw new TypeError("typedBlock(string, fn) expects a full block type encoding starting with '@?'");
        }
        return signature;
    }
    if (signature.types !== undefined) {
        if (!signature.types.startsWith("@?")) {
            throw new TypeError("typedBlock({ types }, fn) expects a full block type encoding starting with '@?'");
        }
        return signature.types;
    }
    const args = signature.args ?? [];
    return `@?<${signature.returns}@?${args.join("")}>`;
}
/**
 * Attach an explicit block signature to a JavaScript function.
 *
 * Use this when Objective-C only exposes `@?` for a block parameter and
 * objc-js would otherwise have to guess the callback argument types.
 *
 * @example
 * ```typescript
 * const handler = typedBlock({ returns: "v", args: ["@", "Q", "^B"] }, (obj, idx, stop) => {
 *   console.log(obj.toString(), idx, stop);
 * });
 * array.enumerateObjectsUsingBlock$(handler);
 * ```
 */
function typedBlock(signature, fn) {
    Object.defineProperty(fn, TYPED_BLOCK_ENCODING, {
        value: normalizeTypedBlockEncoding(signature),
        enumerable: false,
        configurable: true
    });
    return fn;
}
function unwrapArg(arg) {
    if (arg && typeof arg === "object") {
        return nativeObjectMap.get(arg) ?? arg;
    }
    // Wrap function arguments so that when called from native (e.g., as ObjC blocks),
    // the native ObjcObject args are automatically wrapped in NobjcObject proxies.
    if (typeof arg === "function") {
        const wrapped = function (...nativeArgs) {
            for (let i = 0; i < nativeArgs.length; i++) {
                nativeArgs[i] = wrapObjCObjectIfNeeded(nativeArgs[i]);
            }
            return unwrapArg(arg(...nativeArgs));
        };
        const explicitBlockEncoding = arg[TYPED_BLOCK_ENCODING];
        if (typeof explicitBlockEncoding === "string") {
            Object.defineProperty(wrapped, TYPED_BLOCK_ENCODING, {
                value: explicitBlockEncoding,
                enumerable: false,
                configurable: true
            });
        }
        // Preserve the original function's .length so the native layer can read it
        // (used to infer block parameter count when extended encoding is unavailable)
        Object.defineProperty(wrapped, "length", { value: arg.length });
        return wrapped;
    }
    return arg;
}
function wrapObjCObjectIfNeeded(result) {
    if (typeof result == "object" && result instanceof ObjcObject) {
        return new NobjcObject(result);
    }
    return result;
}
// Note: This is actually a factory function that returns a callable function
const NobjcMethod = function (object, methodName) {
    const selector = NobjcMethodNameToObjcSelector(methodName);
    // H2: Cache SEL + method signature natively via $prepareSend.
    // This avoids re-registering the selector, respondsToSelector:, and
    // method signature lookup on every call.
    const handle = object.$prepareSend(selector);
    // Fast paths for 0-3 args avoid rest param array allocation + spread overhead
    function methodFunc(...args) {
        switch (args.length) {
            case 0:
                return wrapObjCObjectIfNeeded(object.$msgSendPrepared(handle));
            case 1:
                return wrapObjCObjectIfNeeded(object.$msgSendPrepared(handle, unwrapArg(args[0])));
            case 2:
                return wrapObjCObjectIfNeeded(object.$msgSendPrepared(handle, unwrapArg(args[0]), unwrapArg(args[1])));
            case 3:
                return wrapObjCObjectIfNeeded(object.$msgSendPrepared(handle, unwrapArg(args[0]), unwrapArg(args[1]), unwrapArg(args[2])));
            default:
                for (let i = 0; i < args.length; i++) {
                    args[i] = unwrapArg(args[i]);
                }
                return wrapObjCObjectIfNeeded(object.$msgSendPrepared(handle, ...args));
        }
    }
    // Return the function directly — no Proxy wrapper needed (handler was empty)
    return methodFunc;
};
class NobjcProtocol {
    static implement(protocolName, methodImplementations) {
        // Convert method names from $ notation to : notation
        const convertedMethods = {};
        for (const [methodName, impl] of Object.entries(methodImplementations)) {
            const selector = NobjcMethodNameToObjcSelector(methodName);
            // Wrap the implementation to wrap args and unwrap return values
            convertedMethods[selector] = function (...args) {
                // Wrap native ObjcObject arguments in NobjcObject proxies (in-place to avoid allocation)
                for (let i = 0; i < args.length; i++) {
                    args[i] = wrapObjCObjectIfNeeded(args[i]);
                }
                const result = impl(...args);
                // If the result is already a NobjcObject, unwrap it to get the native object
                return unwrapArg(result);
            };
        }
        // Call native implementation
        const nativeObj = CreateProtocolImplementation(protocolName, convertedMethods);
        // Wrap in NobjcObject proxy
        return new NobjcObject(nativeObj);
    }
}
/**
 * Get the raw native pointer for a NobjcObject as a Node Buffer.
 * The pointer is stored in little-endian format (8 bytes on 64-bit macOS).
 *
 * @param obj - The NobjcObject to get the pointer from
 * @returns A Buffer containing the pointer address
 *
 * @example
 * ```typescript
 * const view = window.contentView();
 * const pointerBuffer = getPointer(view);
 * const pointer = pointerBuffer.readBigUInt64LE(0);
 * console.log(`NSView pointer: 0x${pointer.toString(16)}`);
 * ```
 */
function getPointer(obj) {
    // Unwrap the NobjcObject to get the native ObjcObject via WeakMap
    const nativeObj = nativeObjectMap.get(obj);
    if (nativeObj) {
        return GetPointer(nativeObj);
    }
    throw new TypeError("Argument must be a NobjcObject instance");
}
/**
 * Create a NobjcObject from a raw native pointer.
 *
 * @param pointer - A Buffer or BigInt containing the pointer address
 * @returns A NobjcObject wrapping the native object
 *
 * @example
 * ```typescript
 * // From a Buffer
 * const pointerBuffer = getPointer(originalObject);
 * const reconstructed = fromPointer(pointerBuffer);
 *
 * // From a BigInt
 * const pointer = 0x12345678n;
 * const obj = fromPointer(pointer);
 *
 * // Round-trip example
 * const original = NSString.stringWithUTF8String$("Hello");
 * const ptr = getPointer(original).readBigUInt64LE(0);
 * const restored = fromPointer(ptr);
 * console.log(restored.toString()); // "Hello"
 * ```
 *
 * @warning This is unsafe! The pointer must point to a valid Objective-C object.
 * Using an invalid pointer will cause a crash. The object must still be alive
 * (not deallocated) when you call this function.
 */
function fromPointer(pointer) {
    const nativeObj = FromPointer(pointer);
    if (nativeObj === null) {
        throw new Error("Cannot create object from null pointer");
    }
    return new NobjcObject(nativeObj);
}
/**
 * API for defining new Objective-C classes at runtime.
 *
 * @example
 * ```typescript
 * // Define a subclass of NSObject
 * const MyClass = NobjcClass.define({
 *   name: "MyClass",
 *   superclass: "NSObject",
 *   protocols: ["NSCopying"],
 *   methods: {
 *     "init": {
 *       types: "@@:",
 *       implementation: (self) => {
 *         return NobjcClass.super(self, "init");
 *       }
 *     },
 *     "myMethod:": {
 *       types: "@@:@",
 *       implementation: (self, arg) => {
 *         console.log("myMethod called with", arg);
 *         return arg;
 *       }
 *     }
 *   }
 * });
 *
 * // Create an instance
 * const instance = MyClass.alloc().init();
 * instance.myMethod$(someArg);
 * ```
 */
class NobjcClass {
    /**
     * Define a new Objective-C class at runtime.
     *
     * @param definition The class definition
     * @returns A NobjcObject representing the new Class (can be used to alloc/init instances)
     *
     * @warning For private methods (like _requestContextWithRequests:error:), you must provide
     * the correct type encoding manually since it cannot be introspected from protocols.
     */
    static define(definition) {
        // Convert method implementations to wrap args and unwrap returns
        const nativeDefinition = {
            name: definition.name,
            superclass: typeof definition.superclass === "string" ? definition.superclass : unwrapArg(definition.superclass),
            protocols: definition.protocols
        };
        if (definition.methods) {
            nativeDefinition.methods = {};
            for (const [selector, methodDef] of Object.entries(definition.methods)) {
                const normalizedSelector = NobjcMethodNameToObjcSelector(selector);
                nativeDefinition.methods[normalizedSelector] = {
                    types: methodDef.types,
                    implementation: (nativeSelf, ...nativeArgs) => {
                        // Wrap self
                        const wrappedSelf = wrapObjCObjectIfNeeded(nativeSelf);
                        // Wrap args in-place to avoid allocation (preserve out-param objects)
                        for (let i = 0; i < nativeArgs.length; i++) {
                            const arg = nativeArgs[i];
                            if (arg && typeof arg === "object" && typeof arg.set === "function") {
                                nativeArgs[i] = {
                                    set: (error) => arg.set(unwrapArg(error)),
                                    get: () => wrapObjCObjectIfNeeded(arg.get())
                                };
                            }
                            else {
                                nativeArgs[i] = wrapObjCObjectIfNeeded(arg);
                            }
                        }
                        // Call the user's implementation
                        const result = methodDef.implementation(wrappedSelf, ...nativeArgs);
                        // Unwrap the return value
                        return unwrapArg(result);
                    }
                };
            }
        }
        // Call native DefineClass
        const nativeClass = DefineClass(nativeDefinition);
        // Return wrapped Class object
        return new NobjcObject(nativeClass);
    }
    /**
     * Call the superclass implementation of a method.
     * Use this inside a method implementation to invoke super.
     *
     * @param self The instance (the first argument to your implementation)
     * @param selector The selector string (e.g., "init" or "_requestContextWithRequests:error:")
     * @param args Additional arguments to pass to super
     * @returns The result of the super call
     *
     * @example
     * ```typescript
     * methods: {
     *   "init": {
     *     types: "@@:",
     *     implementation: (self) => {
     *       // Call [super init]
     *       const result = NobjcClass.super(self, "init");
     *       // Do additional setup...
     *       return result;
     *     }
     *   }
     * }
     * ```
     */
    static super(self, selector, ...args) {
        const normalizedSelector = NobjcMethodNameToObjcSelector(selector);
        const nativeSelf = unwrapArg(self);
        // Mutate args in-place to avoid allocation
        for (let i = 0; i < args.length; i++) {
            args[i] = unwrapArg(args[i]);
        }
        const result = CallSuper(nativeSelf, normalizedSelector, ...args);
        return wrapObjCObjectIfNeeded(result);
    }
}
/**
 * Parse a combined type encoding string into individual type encodings.
 * Handles multi-character encodings: ^v (pointer), {CGRect=dd} (struct), etc.
 */
function parseTypeEncodings(typeStr) {
    const result = [];
    let i = 0;
    while (i < typeStr.length) {
        const start = i;
        const ch = typeStr[i];
        if (ch === "{" || ch === "[" || ch === "(") {
            const close = ch === "{" ? "}" : ch === "[" ? "]" : ")";
            let depth = 1;
            i++;
            while (i < typeStr.length && depth > 0) {
                if (typeStr[i] === ch)
                    depth++;
                else if (typeStr[i] === close)
                    depth--;
                i++;
            }
        }
        else if (ch === "^") {
            i++;
            if (i < typeStr.length) {
                if (typeStr[i] === "{" || typeStr[i] === "[" || typeStr[i] === "(") {
                    // Pointer to compound type — parse the inner type
                    const inner = parseTypeEncodings(typeStr.substring(i));
                    i += inner[0]?.length ?? 1;
                }
                else {
                    i++; // pointer to simple type (^v, ^@, etc.)
                }
            }
        }
        else {
            i++;
        }
        result.push(typeStr.substring(start, i));
    }
    return result;
}
/**
 * Infer the ObjC type encoding for a JS argument value.
 * - NobjcObject / object → "@" (id)
 * - string → "@" (auto-converted to NSString by native code)
 * - boolean → "B" (BOOL)
 * - number → "d" (double/CGFloat)
 * - null/undefined → "@" (nil)
 *
 * Note: numbers always infer as "d" (double). For integer params (NSInteger, etc.),
 * specify the type explicitly via { args: ["q"] } or { types: "..." }.
 */
function inferArgType(arg) {
    if (arg === null || arg === undefined)
        return "@";
    if (typeof arg === "boolean")
        return "B";
    if (typeof arg === "number")
        return "d";
    return "@";
}
/**
 * Check if a value is a CallFunctionOptions object (not a NobjcObject or struct).
 */
function isCallOptions(value) {
    if (value === null || value === undefined || typeof value !== "object")
        return false;
    if (nativeObjectMap.has(value))
        return false; // It's a NobjcObject proxy
    return "returns" in value || "types" in value || "args" in value;
}
/**
 * Resolve return type and arg types from options + values.
 */
function resolveTypes(options, args) {
    if (options?.types) {
        const encodings = parseTypeEncodings(options.types);
        const returnType = encodings[0] || "v";
        const explicitArgTypes = encodings.slice(1);
        return {
            returnType,
            argTypes: explicitArgTypes.length > 0 ? explicitArgTypes : args.map(inferArgType)
        };
    }
    return {
        returnType: options?.returns || "v",
        argTypes: options?.args || args.map(inferArgType)
    };
}
/**
 * Call a C function by name.
 *
 * The framework containing the function must be loaded first (e.g., via `new NobjcLibrary(...)`).
 * Uses `dlsym` to look up the function symbol and `libffi` to call it with the correct ABI.
 *
 * Argument types are inferred from JS values by default:
 * - NobjcObject → `@` (id)
 * - string → `@` (auto-converted to NSString)
 * - boolean → `B` (BOOL)
 * - number → `d` (double/CGFloat)
 * - null → `@` (nil)
 *
 * Return type defaults to `"v"` (void). Pass an options object to specify return/arg types.
 *
 * @param name - The function name (e.g., "NSLog", "NSHomeDirectory")
 * @param optionsOrFirstArg - Either a CallFunctionOptions object or the first function argument
 * @param args - The actual arguments to pass to the function
 * @returns The return value converted to a JavaScript type, or undefined for void functions
 *
 * @example
 * ```typescript
 * import { NobjcLibrary, callFunction } from "objc-js";
 *
 * const Foundation = new NobjcLibrary(
 *   "/System/Library/Frameworks/Foundation.framework/Foundation"
 * );
 * const NSString = Foundation.NSString;
 *
 * // Void function — simplest form, no options needed
 * const msg = NSString.stringWithUTF8String$("Hello!");
 * callFunction("NSLog", msg);
 *
 * // Function that returns a value — specify { returns }
 * const homeDir = callFunction("NSHomeDirectory", { returns: "@" });
 * console.log(homeDir.toString());
 *
 * // Explicit arg types when inference isn't enough
 * const selName = callFunction("NSStringFromSelector", { returns: "@", args: [":"] }, "description");
 *
 * // Combined type string shorthand (return + args)
 * const className = callFunction("NSStringFromClass", { types: "@#" }, NSString);
 * ```
 */
function callFunction(name, ...rest) {
    let options = null;
    let args;
    if (rest.length > 0 && isCallOptions(rest[0])) {
        options = rest[0];
        args = rest.slice(1);
    }
    else {
        args = rest;
    }
    const { returnType, argTypes } = resolveTypes(options, args);
    // Unwrap NobjcObject proxies to native objects
    for (let i = 0; i < args.length; i++) {
        args[i] = unwrapArg(args[i]);
    }
    const result = CallFunction(name, returnType, argTypes, argTypes.length, ...args);
    return wrapObjCObjectIfNeeded(result);
}
/**
 * Call a variadic C function by name.
 *
 * Correctly handles variadic calling conventions (important on Apple Silicon / ARM64
 * where variadic args go on the stack while fixed args go in registers).
 *
 * @param name - The function name (e.g., "NSLog")
 * @param optionsOrFixedCount - Either a CallFunctionOptions object or the fixedArgCount directly
 * @param fixedArgCountOrFirstArg - Number of fixed (non-variadic) arguments, or first arg if options were provided
 * @param args - The actual arguments to pass to the function
 * @returns The return value converted to a JavaScript type
 *
 * @example
 * ```typescript
 * import { NobjcLibrary, callVariadicFunction } from "objc-js";
 *
 * const Foundation = new NobjcLibrary(
 *   "/System/Library/Frameworks/Foundation.framework/Foundation"
 * );
 * const NSString = Foundation.NSString;
 *
 * // Simplest: void return, inferred args, fixedArgCount = 1
 * const format = NSString.stringWithUTF8String$("Hello, %@!");
 * const name = NSString.stringWithUTF8String$("World");
 * callVariadicFunction("NSLog", 1, format, name);
 *
 * // With explicit types
 * callVariadicFunction("NSLog", { returns: "v", args: ["@", "i"] }, 1, format, 42);
 * ```
 */
function callVariadicFunction(name, ...rest) {
    let options = null;
    let restIdx = 0;
    if (rest.length > 0 && isCallOptions(rest[0])) {
        options = rest[0];
        restIdx = 1;
    }
    // fixedArgCount must be a number
    if (restIdx >= rest.length || typeof rest[restIdx] !== "number") {
        throw new Error("callVariadicFunction requires fixedArgCount as a number parameter");
    }
    const fixedArgCount = rest[restIdx];
    restIdx++;
    const args = rest.slice(restIdx);
    const { returnType, argTypes } = resolveTypes(options, args);
    // Unwrap NobjcObject proxies to native objects
    for (let i = 0; i < args.length; i++) {
        args[i] = unwrapArg(args[i]);
    }
    const result = CallFunction(name, returnType, argTypes, fixedArgCount, ...args);
    return wrapObjCObjectIfNeeded(result);
}
/**
 * Utilities for pumping the macOS CFRunLoop from a Node.js/Bun event loop.
 *
 * Required for async Objective-C callbacks (completion handlers, AppKit events, etc.)
 * to be delivered, since Node.js/Bun don't automatically pump the CFRunLoop.
 *
 * Usage:
 *   RunLoop.run();   // Start pumping (returns a cleanup function)
 *   RunLoop.pump();  // Pump once (non-blocking)
 */
const RunLoop = {
    _timer: null,
    _mainRunLoop: null,
    _defaultMode: null,
    _NSDate: null,
    /**
     * Lazily initialise NSRunLoop and NSDefaultRunLoopMode references.
     * Foundation is already loaded by the time any ObjC work happens, so
     * GetClassObject will succeed without an explicit LoadLibrary call.
     *
     * We use proxy-wrapped NobjcObjects (via wrapObjCObjectIfNeeded) rather
     * than raw native ObjcObjects + $msgSend, because Bun's N-API crashes
     * when CFRunLoopRunInMode is triggered from the $msgSend C++ path.
     * The proxy path ($prepareSend + $msgSendPrepared) works on both runtimes.
     */
    _ensureRunLoop() {
        if (this._mainRunLoop === null) {
            // Ensure Foundation is loaded — NobjcLibrary uses lazy loading,
            // so Foundation may not be loaded yet if no class has been accessed.
            LoadLibrary("/System/Library/Frameworks/Foundation.framework/Foundation");
            const NSRunLoopRaw = GetClassObject("NSRunLoop");
            if (!NSRunLoopRaw) {
                throw new Error("Foundation framework is not loaded. Create a NobjcLibrary for Foundation before using RunLoop.");
            }
            const NSRunLoop = wrapObjCObjectIfNeeded(NSRunLoopRaw);
            this._mainRunLoop = NSRunLoop.mainRunLoop();
            const NSStringRaw = GetClassObject("NSString");
            const NSString = wrapObjCObjectIfNeeded(NSStringRaw);
            this._defaultMode = NSString.stringWithUTF8String$("kCFRunLoopDefaultMode");
            const NSDateRaw = GetClassObject("NSDate");
            this._NSDate = wrapObjCObjectIfNeeded(NSDateRaw);
        }
    },
    /**
     * Pump the run loop once. Processes any pending run loop sources
     * (AppKit events, dispatch_async to main queue, timers, etc.)
     * without blocking.
     *
     * @param timeout Optional timeout in seconds (default: 0)
     * @returns true if a source was processed
     */
    pump(timeout) {
        this._ensureRunLoop();
        const limitDate = this._NSDate.dateWithTimeIntervalSinceNow$(timeout ?? 0);
        const handled = this._mainRunLoop.runMode$beforeDate$(this._defaultMode, limitDate);
        return !!handled;
    },
    /**
     * Start continuously pumping the run loop on a regular interval.
     * This enables async Objective-C callbacks to be delivered.
     *
     * @param intervalMs Pump interval in milliseconds (default: 10)
     * @returns A cleanup function that stops pumping
     */
    run(intervalMs = 10) {
        if (this._timer !== null) {
            clearInterval(this._timer);
        }
        // Eagerly initialise so the first interval tick is cheap
        this._ensureRunLoop();
        this._timer = setInterval(() => {
            const limitDate = this._NSDate.dateWithTimeIntervalSinceNow$(0);
            this._mainRunLoop.runMode$beforeDate$(this._defaultMode, limitDate);
        }, intervalMs);
        // Unref the timer so it doesn't prevent the process from exiting
        // when there are no other active handles
        if (this._timer && typeof this._timer === "object" && "unref" in this._timer) {
            this._timer.unref();
        }
        const stop = () => this.stop();
        return stop;
    },
    /**
     * Stop pumping the run loop.
     */
    stop() {
        if (this._timer !== null) {
            clearInterval(this._timer);
            this._timer = null;
        }
    }
};
export { NobjcLibrary, NobjcObject, NobjcMethod, NobjcProtocol, NobjcClass, typedBlock, RunLoop, getPointer, fromPointer, callFunction, callVariadicFunction };
