// Obj-C runtime FFI primitives.
//
// `*void` stands in for the Obj-C `id`/`Class`/`SEL` types. There's no
// sx-level type alias yet, so naming discipline at call sites is the only
// thing keeping them apart.
//
// objc_msgSend has the standard ARM64 calling convention (no varargs path).
// Each call site must invoke through a function pointer of the *exact*
// argument and return shape. The idiom:
//
//     msg_fn : (recv: *void, sel: *void, arg: [*]u8) -> *void = xx objc_msgSend;
//     result := msg_fn(receiver, selector, c_string);

// On macOS libobjc is auto-loaded by libSystem; on iOS it isn't, so we
// link it explicitly. Foundation registers NSString etc. with the runtime,
// also auto-loaded on macOS and required as an explicit framework on iOS.
objc :: #library "objc";
#framework "Foundation";

objc_getClass         :: (name: [*]u8)                  -> *void  #foreign objc;
objc_lookUpClass      :: (name: [*]u8)                  -> *void  #foreign objc;
sel_registerName      :: (name: [*]u8)                  -> *void  #foreign objc;
class_createInstance  :: (cls: *void, extra: usize)     -> *void  #foreign objc;
object_getClass       :: (obj: *void)                   -> *void  #foreign objc;

// Declared with the simplest non-variadic shape. Cast per call site.
objc_msgSend          :: (recv: *void, sel: *void)      -> *void  #foreign objc;

// ─── Dynamic class registration ─────────────────────────────────────────
// Define a new Obj-C class at runtime: allocate the pair, attach methods +
// protocols, then finalize with `objc_registerClassPair`. The class is then
// usable via `class_createInstance` and Obj-C dispatch.
//
// IMPs (method implementations) are function pointers with the implicit
// Obj-C method shape: `(self: *void, _cmd: *void, ...args) -> ret` with
// `callconv(.c)` so they land args in the standard C registers.
//
// Method type encoding strings follow Apple's runtime DSL:
//   v = void   c = char/BOOL   i = int   l = long   f = float   d = double
//   @ = id (object)            : = SEL                # = Class
// Return type comes first, then receiver (`@`), then `_cmd` (`:`), then args.
// Examples:
//   "v@:"     -> void   method(id, SEL)
//   "c@:"     -> BOOL   method(id, SEL)
//   "@@:@"    -> id     method(id, SEL, id)
//   "B@:@@"   -> BOOL   method(id, SEL, id, id)
objc_allocateClassPair :: (super: *void, name: [*]u8, extra: usize) -> *void  #foreign objc;
class_addMethod        :: (cls: *void, sel: *void, imp: *void, types: [*]u8) -> bool   #foreign objc;
class_addProtocol      :: (cls: *void, proto: *void)                          -> bool   #foreign objc;
objc_getProtocol       :: (name: [*]u8)                                       -> *void  #foreign objc;
objc_registerClassPair :: (cls: *void)                                        -> void   #foreign objc;

// Foundation C-API helpers (Foundation is already linked above via #framework).
// NSLog takes an NSString format; the variadic tail is not exposed here.
NSLog :: (fmt: *void) #foreign;

// ─── Convenience helpers ────────────────────────────────────────────────
// These hide the typed-fn-pointer cast for the most common shapes. They
// re-register selectors per call — if you're in a tight loop, cache the SEL.

// Wrap a C string in an autoreleased NSString.
ns_string :: (s: [*]u8) -> *void {
    cls := objc_getClass("NSString".ptr);
    sel := sel_registerName("stringWithUTF8String:".ptr);
    fn_ptr : (*void, *void, [*]u8) -> *void = xx objc_msgSend;
    return fn_ptr(cls, sel, s);
}

// View an NSString's bytes as a C string. The returned pointer's lifetime is
// tied to the NSString; don't free it.
c_string :: (ns: *void) -> [*]u8 {
    sel := sel_registerName("UTF8String".ptr);
    fn_ptr : (*void, *void) -> [*]u8 = xx objc_msgSend;
    return fn_ptr(ns, sel);
}
