//! A standard interface to a WebGPU implementation.
//!
//! Like std.mem.Allocator, but representing a WebGPU implementation.
const std = @import("std");

const Surface = @import("Surface.zig");
const Adapter = @import("Adapter.zig");
const PowerPreference = @import("enums.zig").PowerPreference;

const Interface = @This();

/// The type erased pointer to the Interface implementation
ptr: *anyopaque,
vtable: *const VTable,

pub const VTable = struct {
    reference: fn (ptr: *anyopaque) void,
    release: fn (ptr: *anyopaque) void,
    requestAdapter: fn requestAdapter(
        ptr: *anyopaque,
        options: *const RequestAdapterOptions,
        callback: *RequestAdapterCallback,
    ) void,
};

pub inline fn reference(interface: Interface) void {
    interface.vtable.reference(interface.ptr);
}

pub inline fn release(interface: Interface) void {
    interface.vtable.release(interface.ptr);
}

pub const RequestAdapterOptions = struct {
    power_preference: PowerPreference,
    force_fallback_adapter: bool = false,

    /// Only respected by native WebGPU implementations.
    compatible_surface: ?Surface = null,
};

pub const RequestAdapterErrorCode = error{
    Unavailable,
    Error,
    Unknown,
};

pub const RequestAdapterError = struct {
    message: []const u8,
    code: RequestAdapterErrorCode,
};

pub const RequestAdapterResponseTag = enum {
    adapter,
    err,
};

pub const RequestAdapterResponse = union(RequestAdapterResponseTag) {
    adapter: Adapter,
    err: RequestAdapterError,
};

pub fn requestAdapter(
    interface: Interface,
    options: *const RequestAdapterOptions,
    callback: *RequestAdapterCallback,
) void {
    interface.vtable.requestAdapter(interface.ptr, options, callback);
}

pub const RequestAdapterCallback = struct {
    type_erased_ctx: *anyopaque,
    type_erased_callback: fn (ctx: *anyopaque, response: RequestAdapterResponse) callconv(.Inline) void,

    pub fn init(
        comptime Context: type,
        ctx: Context,
        comptime callback: fn (ctx: Context, response: RequestAdapterResponse) void,
    ) RequestAdapterCallback {
        const erased = (struct {
            pub inline fn erased(type_erased_ctx: *anyopaque, response: RequestAdapterResponse) void {
                callback(if (Context == void) {} else @ptrCast(Context, @alignCast(std.meta.alignment(Context), type_erased_ctx)), response);
            }
        }).erased;

        return .{
            .type_erased_ctx = if (Context == void) undefined else ctx,
            .type_erased_callback = erased,
        };
    }
};

/// A helper which invokes requestAdapter and blocks until the adapter is recieved.
pub fn waitForAdapter(interface: Interface, options: *const RequestAdapterOptions) RequestAdapterResponse {
    var response: RequestAdapterResponse = undefined;
    var callback = RequestAdapterCallback.init(*RequestAdapterResponse, &response, (struct {
        pub fn callback(ctx: *RequestAdapterResponse, callback_response: RequestAdapterResponse) void {
            ctx.* = callback_response;
        }
    }).callback);

    interface.requestAdapter(options, &callback);

    // TODO: FUTURE: Once crbug.com/dawn/1122 is fixed, we should process events here otherwise our
    // callback would not be invoked:
    //c.wgpuInstanceProcessEvents(interface.instance)

    return response;
}

test {
    _ = VTable;
    _ = reference;
    _ = release;
    _ = RequestAdapterOptions;
    _ = RequestAdapterErrorCode;
    _ = RequestAdapterError;
    _ = RequestAdapterResponse;
    _ = requestAdapter;
    _ = waitForAdapter;
}
