const std = @import("std");
const zx = @import("../../root.zig");

pub const ActionFn = *const fn (*zx.server.Action) void;
pub const ServerEventFn = *const fn (*zx.server.Event) void;

// TODO: Make this allocator global and configurable by the user
const allocator = std.heap.page_allocator;

/// A dynamic linear registry that avoids HashMap (and its Wyhash 128-bit
/// multiply) so the module compiles on the native WASM backend without
/// requiring the `__multi3` compiler-rt intrinsic.
fn LinearMap(comptime V: type) type {
    const Entry = struct { key: []const u8, val: V };
    return struct {
        entries: std.ArrayListUnmanaged(Entry) = .empty,

        pub fn put(self: *@This(), key: []const u8, val: V) void {
            for (self.entries.items) |*e| {
                if (std.mem.eql(u8, e.key, key)) {
                    e.val = val;
                    return;
                }
            }
            self.entries.append(allocator, .{ .key = key, .val = val }) catch {};
        }

        pub fn get(self: *const @This(), key: []const u8) ?V {
            for (self.entries.items) |e| {
                if (std.mem.eql(u8, e.key, key)) return e.val;
            }
            return null;
        }
    };
}

var mu: std.Thread.Mutex = .{};
var map: LinearMap(ActionFn) = .{};
var event_map: LinearMap(ServerEventFn) = .{};

/// Register an action handler for a route. Route paths are comptime-stable
/// string literals so no duplication of the key is needed.
pub fn register(route_path: []const u8, action_fn: ActionFn) void {
    mu.lock();
    defer mu.unlock();
    map.put(route_path, action_fn);
}

pub fn registerEvent(route_path: []const u8, handler_id: u32, event_fn: ServerEventFn) void {
    mu.lock();
    defer mu.unlock();

    // Composite key: "route_path:handler_id"
    var buf: [1024]u8 = undefined;
    const key = std.fmt.bufPrint(&buf, "{s}:{d}", .{ route_path, handler_id }) catch return;
    const key_dupe = allocator.dupe(u8, key) catch return;

    event_map.put(key_dupe, event_fn);
}

pub fn getEvent(route_path: []const u8, handler_id: u32) ?ServerEventFn {
    mu.lock();
    defer mu.unlock();

    var buf: [1024]u8 = undefined;
    const key = std.fmt.bufPrint(&buf, "{s}:{d}", .{ route_path, handler_id }) catch return null;

    return event_map.get(key);
}

/// Look up a registered action handler by route path.
pub fn get(route_path: []const u8) ?ActionFn {
    mu.lock();
    defer mu.unlock();
    return map.get(route_path);
}
