// Obj-C blocks bridged to sx closures.
//
// Apple's block ABI (clang's "Block Implementation Specification"): a block
// pointer is a struct whose first five fields are { isa, flags, reserved,
// invoke, descriptor } followed by per-block captured state. When an API
// like `[UIView animateWithDuration:animations:]` receives a block, the
// runtime reads `invoke` and calls it with the block pointer as the first
// argument. UIKit / Foundation callers always `_Block_copy` synchronously
// before returning, so a stack-allocated block is safe to pass directly.
//
// We layer the sx closure onto Apple's layout by appending two pointer
// fields to the standard 32-byte header: `sx_env` (the closure's captured
// environment pointer) and `sx_fn` (the closure trampoline). The per-
// signature `__block_invoke_*` C-ABI fn knows the offsets and calls
// through to `sx_fn(sx_env, args...)`.
//
// ── Lifetime contract ───────────────────────────────────────────────────
// `xx <closure> : *Block` returns a pointer into the surrounding sx
// function's stack frame. Same rule as `&local_var`: pass it directly to
// a callee that consumes it immediately or `_Block_copy`s internally
// (UIKit/Foundation always do). Don't store the pointer to use after the
// caller returns. If you need that, ship a `Block_copy`-backed sibling
// API and use it instead.

// Standard 32-byte block header plus two trailing slots for the sx closure
// it wraps. Total = 48 bytes.
Block :: struct {
    isa: *void;
    flags: s32;
    reserved: s32;
    invoke: *void;
    descriptor: *void;
    sx_env: *void;
    sx_fn: *void;
}

// Per-block-shape metadata. The runtime reads `size` when copying the
// block to the heap, so it must equal the actual instance size.
BlockDescriptor :: struct {
    reserved: u64;
    size: u64;
}

// libSystem isa pointer for stack-allocated blocks. Resolved at link time
// (auto-linked on every Apple target via libSystem).
_NSConcreteStackBlock : *void #foreign;

// Shared descriptor for the 48-byte sx-block layout. All Into impls below
// point their `descriptor` field at this.
__sx_block_descriptor : BlockDescriptor = .{
    reserved = 0,
    size = 48,
};

// Per-signature invoke trampolines. Each one reads sx_env + sx_fn from
// its block_self argument and tail-calls the closure through a typed
// fn-ptr cast. One per Apple block signature we support.
//
// Signature: `void (^)(void)` — no args, no return. The single most
// common Apple block shape (UIView animation bodies, dispatch_async, etc).
__block_invoke_void :: (block_self: *Block) callconv(.c) {
    // `sx_fn` is the closure trampoline — an sx-side function with the
    // implicit __sx_ctx at slot 0 and env at slot 1. We're a callconv(.c)
    // entry, so the call site needs ctx prepended; the typed fn-pointer
    // type stays default-conv to enable that.
    typed_fn : (*void) -> void = xx block_self.sx_fn;
    typed_fn(block_self.sx_env);
}

impl Into(Block) for Closure() -> void {
    convert :: (self: Closure() -> void) -> Block {
        .{
            isa = @_NSConcreteStackBlock,
            flags = 0,
            reserved = 0,
            invoke = xx @__block_invoke_void,
            descriptor = xx @__sx_block_descriptor,
            sx_env = self.env,
            sx_fn = self.fn_ptr,
        };
    }
}

// Signature: `void (^)(BOOL)` — UIView animation completion handlers and
// similar one-arg-bool callbacks.
__block_invoke_bool :: (block_self: *Block, arg0: bool) callconv(.c) {
    typed_fn : (*void, bool) -> void = xx block_self.sx_fn;
    typed_fn(block_self.sx_env, arg0);
}

impl Into(Block) for Closure(bool) -> void {
    convert :: (self: Closure(bool) -> void) -> Block {
        .{
            isa = @_NSConcreteStackBlock,
            flags = 0,
            reserved = 0,
            invoke = xx @__block_invoke_bool,
            descriptor = xx @__sx_block_descriptor,
            sx_env = self.env,
            sx_fn = self.fn_ptr,
        };
    }
}
