const std = @import("std");
const assert = std.debug.assert;
const process = std.process;
const posix = std.posix;
const fs = std.fs;
const fmt = std.fmt;
const Io = std.Io;
const mem = std.mem;
const fmtId = std.zig.fmtId;

const log = std.log.scoped(.@"zig-wayland");

const xml = @import("xml.zig");

pub const Target = struct {
    /// Name of the target global interface
    name: []const u8,
    /// Interface version for which to generate code.
    /// If the version found in the protocol xml is less than this version,
    /// an error will be printed and code generation will fail.
    /// This version applies to interfaces that may be created through the
    /// global interface as well.
    version: u32,
};

pub fn main(init: std.process.Init) !void {
    const io = init.io;
    const gpa = init.gpa;

    var protocols: std.ArrayList([]const u8) = .empty;
    defer protocols.deinit(gpa);

    var targets: std.ArrayList(Target) = .empty;
    defer targets.deinit(gpa);

    var ffi_import: ?[]const u8 = null;
    var out_path_opt: ?[]const u8 = null;

    var args = init.minimal.args.iterate();
    while (args.next()) |arg| {
        if (mem.eql(u8, arg, "-i")) {
            const protocol_path = args.next() orelse return error.MissingArg;
            try protocols.append(gpa, protocol_path);
        } else if (mem.eql(u8, arg, "-g")) {
            const name = args.next() orelse return error.MissingArg;
            const version = args.next() orelse return error.MissingArg;
            try targets.append(gpa, .{
                .name = name,
                .version = try fmt.parseInt(u32, version, 10),
            });
        } else if (mem.eql(u8, arg, "-o")) {
            out_path_opt = args.next() orelse return error.MissingArg;
        } else if (mem.eql(u8, arg, "-f")) {
            ffi_import = args.next() orelse return error.MissingArg;
        }
    }

    const out_path = out_path_opt orelse return error.MissingArg;

    var buffer: Io.Writer.Allocating = .init(gpa);

    try scan(io, gpa, &buffer.writer, protocols.items, targets.items, ffi_import);

    const generated = try buffer.toOwnedSliceSentinel(0);
    defer gpa.free(generated);

    var tree = try std.zig.Ast.parse(gpa, generated, .zig);
    defer tree.deinit(gpa);

    if (tree.errors.len != 0) {
        try std.zig.printAstErrorsToStderr(gpa, io, tree, "generated", .auto);
        return error.ParseError;
    }

    const out_file = try Io.Dir.createFileAbsolute(io, out_path, .{});
    defer out_file.close(io);

    var out_buf: [4096]u8 = undefined;
    var out_writer = out_file.writer(io, &out_buf);

    try tree.render(gpa, &out_writer.interface, .{});

    try out_writer.interface.flush();
}

fn scan(
    io: Io,
    gpa: mem.Allocator,
    writer: *Io.Writer,
    protocols: []const []const u8,
    targets: []const Target,
    ffi_import: ?[]const u8,
) !void {
    var scanner = try Scanner.init(io, gpa, targets);
    defer scanner.deinit();

    for (protocols) |xml_path| {
        try scanner.scanProtocol(xml_path);
    }

    if (scanner.remaining_targets.items.len != 0) {
        process.fatal("requested global interface '{s}' not found in provided protocol xml", .{
            scanner.remaining_targets.items[0].name,
        });
    }

    try writer.writeAll(
        \\//! Generated by zig-wayland
        \\
        \\const std = @import("std");
        \\const assert = std.debug.assert;
        \\const posix = std.posix;
        \\const wayland = @This();
        \\
        \\pub const client = struct {
    );

    {
        var iter = scanner.client.iterator();
        while (iter.next()) |entry| {
            try writer.print("pub const {s} = struct {{", .{entry.key_ptr.*});
            if (mem.eql(u8, entry.key_ptr.*, "wl")) {
                try writer.writeAll(@embedFile("wayland_client_core.zig"));
            }
            try writer.writeAll(entry.value_ptr.written());
            try writer.writeAll("};");
        }
    }

    try writer.writeAll(
        \\};
        \\
        \\pub const server = struct {
    );

    {
        var iter = scanner.server.iterator();
        while (iter.next()) |entry| {
            try writer.print("pub const {s} = struct {{", .{entry.key_ptr.*});
            if (mem.eql(u8, entry.key_ptr.*, "wl")) {
                try writer.writeAll(@embedFile("wayland_server_core.zig"));
            }
            try writer.writeAll(entry.value_ptr.written());
            try writer.writeAll("};");
        }
    }

    try writer.writeAll(
        \\};
        \\
        \\const common = struct {
    );

    {
        try writer.writeAll(@embedFile("common_core.zig"));

        var iter = scanner.common.iterator();
        while (iter.next()) |entry| {
            try writer.print("const {s} = struct {{", .{entry.key_ptr.*});
            try writer.writeAll(entry.value_ptr.written());
            try writer.writeAll("};");
        }
    }
    try writer.writeAll("};");

    if (ffi_import) |import| {
        try writer.print("const ffi = @import(\"{s}\");", .{import});
    } else {
        try writer.writeAll("const ffi = struct {");
        try writer.writeAll(@embedFile("ffi.zig"));
        try writer.writeAll("};");
    }
}

const Side = enum {
    client,
    server,
};

const Scanner = struct {
    /// Map from namespace to source code content of the namespace.
    const Map = std.array_hash_map.String(Io.Writer.Allocating);

    io: Io,
    gpa: mem.Allocator,

    client: Map = .empty,
    server: Map = .empty,
    common: Map = .empty,

    remaining_targets: std.ArrayList(Target),

    fn init(io: Io, gpa: mem.Allocator, targets: []const Target) !Scanner {
        return Scanner{
            .io = io,
            .gpa = gpa,
            .remaining_targets = .{
                .items = try gpa.dupe(Target, targets),
                .capacity = targets.len,
            },
        };
    }

    fn deinit(scanner: *Scanner) void {
        deinit_map(scanner.gpa, &scanner.client);
        deinit_map(scanner.gpa, &scanner.server);
        deinit_map(scanner.gpa, &scanner.common);

        scanner.remaining_targets.deinit(scanner.gpa);
    }

    fn deinit_map(gpa: mem.Allocator, map: *Map) void {
        for (map.keys()) |namespace| gpa.free(namespace);
        for (map.values()) |*aw| {
            aw.deinit();
        }
        map.deinit(gpa);
    }

    fn scanProtocol(scanner: *Scanner, xml_path: []const u8) !void {
        const xml_file = try Io.Dir.cwd().openFile(scanner.io, xml_path, .{});
        defer xml_file.close(scanner.io);

        const gpa = scanner.gpa;
        var arena = std.heap.ArenaAllocator.init(gpa);
        defer arena.deinit();

        var xml_file_reader = xml_file.reader(scanner.io, &.{});
        const xml_bytes = try xml_file_reader.interface.allocRemaining(gpa, .unlimited);
        defer gpa.free(xml_bytes);

        const protocol = Protocol.parseXML(gpa, arena.allocator(), xml_bytes) catch |err| {
            process.fatal("failed to parse {s}: {s}", .{ xml_path, @errorName(err) });
        };

        {
            const gop = try scanner.client.getOrPutValue(gpa, protocol.namespace, .init(gpa));
            if (!gop.found_existing) {
                gop.key_ptr.* = try gpa.dupe(u8, protocol.namespace);
            }
            try protocol.emit(.client, scanner.remaining_targets.items, &gop.value_ptr.writer);
        }

        {
            const gop = try scanner.server.getOrPutValue(gpa, protocol.namespace, .init(gpa));
            if (!gop.found_existing) {
                gop.key_ptr.* = try gpa.dupe(u8, protocol.namespace);
            }
            try protocol.emit(.server, scanner.remaining_targets.items, &gop.value_ptr.writer);
        }

        {
            const gop = try scanner.common.getOrPutValue(gpa, protocol.namespace, .init(gpa));
            if (!gop.found_existing) {
                gop.key_ptr.* = try gpa.dupe(u8, protocol.namespace);
            }
            try protocol.emitCommon(scanner.remaining_targets.items, &gop.value_ptr.writer);
        }

        {
            var i: usize = 0;
            outer: while (i < scanner.remaining_targets.items.len) {
                const target = scanner.remaining_targets.items[i];
                for (protocol.globals) |global| {
                    if (mem.eql(u8, target.name, global.interface.name)) {
                        // We check this in emitClient() which is called first.
                        assert(global.interface.version >= target.version);
                        _ = scanner.remaining_targets.swapRemove(i);
                        continue :outer;
                    }
                }
                i += 1;
            }
        }
    }
};

// TODO Parse the summary attribute, and return some kind of Description object.
fn parseDescription(gpa: mem.Allocator, parser: *xml.Parser) !?[]const u8 {
    var description: std.ArrayList(u8) = .empty;
    defer description.deinit(gpa);

    while (parser.next()) |ev| switch (ev) {
        .attribute => continue,
        .character_data => |data| try description.appendSlice(gpa, data),
        .close_tag => |tag| if (mem.eql(u8, tag, "description")) {
            // A description may have only a summary attribute and no body.
            // Since we don't parse summaries yet, return null.
            if (description.items.len == 0) {
                return null;
            }
            return try description.toOwnedSlice(gpa);
        },
        else => return error.BadDescription,
    };

    return error.UnexpectedEndOfFile;
}

fn emitVersion(name: []const u8, since: u32, writer: *Io.Writer) !void {
    try writer.print("pub const @\"{s}_since_version\" = {d};\n", .{ name, since });
}

/// All data in this struct is immutable after creation in parse().
const Protocol = struct {
    const Global = struct {
        interface: Interface,
        children: []const Interface,
    };

    const InterfaceMap = std.array_hash_map.String(Interface);

    name: []const u8,
    namespace: []const u8,
    copyright: ?[]const u8,
    toplevel_description: ?[]const u8,

    version_locked_interfaces: []const Interface,
    globals: []const Global,

    fn parseXML(gpa: mem.Allocator, arena: mem.Allocator, xml_bytes: []const u8) !Protocol {
        var parser = xml.Parser.init(xml_bytes);
        while (parser.next()) |ev| switch (ev) {
            .open_tag => |tag| if (mem.eql(u8, tag, "protocol")) return parse(gpa, arena, &parser),
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn parse(gpa: mem.Allocator, arena: mem.Allocator, parser: *xml.Parser) !Protocol {
        var name: ?[]const u8 = null;
        var copyright: ?[]const u8 = null;
        var toplevel_description: ?[]const u8 = null;
        var version_locked_interfaces: std.ArrayList(Interface) = .empty;
        defer version_locked_interfaces.deinit(gpa);
        var interfaces: InterfaceMap = .empty;
        defer interfaces.deinit(gpa);

        while (parser.next()) |ev| switch (ev) {
            .open_tag => |tag| {
                if (mem.eql(u8, tag, "copyright")) {
                    if (copyright != null)
                        return error.DuplicateCopyright;
                    const e = parser.next() orelse return error.UnexpectedEndOfFile;
                    switch (e) {
                        .character_data => |data| copyright = try arena.dupe(u8, data),
                        else => return error.BadCopyright,
                    }
                } else if (mem.eql(u8, tag, "description")) {
                    if (toplevel_description != null)
                        return error.DuplicateToplevelDescription;
                    toplevel_description = try parseDescription(arena, parser);
                } else if (mem.eql(u8, tag, "interface")) {
                    const interface = try Interface.parse(gpa, arena, parser);
                    if (Interface.version_locked(interface.name)) {
                        try version_locked_interfaces.append(gpa, interface);
                    } else {
                        const gop = try interfaces.getOrPut(gpa, interface.name);
                        if (gop.found_existing) return error.DuplicateInterfaceName;
                        gop.value_ptr.* = interface;
                    }
                }
            },
            .attribute => |attr| if (mem.eql(u8, attr.name, "name")) {
                if (name != null) return error.DuplicateName;
                name = try attr.dupeValue(arena);
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "protocol")) {
                if (interfaces.count() == 0) return error.NoInterfaces;

                const globals = try find_globals(gpa, arena, interfaces);
                if (globals.len == 0) return error.NoGlobals;

                const namespace = prefix(interfaces.values()[0].name) orelse return error.NoNamespace;
                for (interfaces.values()) |interface| {
                    const other = prefix(interface.name) orelse return error.NoNamespace;
                    if (!mem.eql(u8, namespace, other)) return error.InconsistentNamespaces;
                }

                return Protocol{
                    .name = name orelse return error.MissingName,
                    .namespace = namespace,

                    // Missing copyright or toplevel description is bad style, but not illegal.
                    .copyright = copyright,
                    .toplevel_description = toplevel_description,
                    .version_locked_interfaces = try arena.dupe(Interface, version_locked_interfaces.items),
                    .globals = globals,
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn find_globals(
        gpa: mem.Allocator,
        arena: mem.Allocator,
        interfaces: InterfaceMap,
    ) ![]const Global {
        var non_globals = std.StringHashMap(void).init(gpa);
        defer non_globals.deinit();

        for (interfaces.values()) |interface| {
            assert(!Interface.version_locked(interface.name));
            for (interface.requests) |message| {
                if (message.kind == .constructor) {
                    if (message.kind.constructor) |child_interface_name| {
                        try non_globals.put(child_interface_name, {});
                    }
                }
            }
            for (interface.events) |message| {
                if (message.kind == .constructor) {
                    if (message.kind.constructor) |child_interface_name| {
                        try non_globals.put(child_interface_name, {});
                    }
                }
            }
        }

        var globals: std.ArrayList(Global) = .empty;
        defer globals.deinit(gpa);

        for (interfaces.values()) |interface| {
            if (!non_globals.contains(interface.name)) {
                var children: InterfaceMap = .empty;
                defer children.deinit(gpa);

                try find_children(gpa, interface, interfaces, &children);

                try globals.append(gpa, .{
                    .interface = interface,
                    .children = try arena.dupe(Interface, children.values()),
                });
            }
        }

        return arena.dupe(Global, globals.items);
    }

    fn find_children(
        gpa: mem.Allocator,
        parent: Interface,
        interfaces: InterfaceMap,
        children: *InterfaceMap,
    ) error{ OutOfMemory, InvalidInterface }!void {
        for ([_][]const Message{ parent.requests, parent.events }) |messages| {
            for (messages) |message| {
                if (message.kind == .constructor) {
                    if (message.kind.constructor) |child_name| {
                        if (Interface.version_locked(child_name)) continue;

                        const child = interfaces.get(child_name) orelse {
                            log.err("interface '{s}' constructed by message '{s}' not defined in the protocol and not wl_callback or wl_buffer", .{
                                child_name,
                                message.name,
                            });
                            return error.InvalidInterface;
                        };
                        try children.put(gpa, child_name, child);
                        try find_children(gpa, child, interfaces, children);
                    }
                }
            }
        }
    }

    fn emit(protocol: Protocol, side: Side, targets: []const Target, writer: *Io.Writer) !void {
        for (protocol.version_locked_interfaces) |interface| {
            assert(interface.version == 1);
            try interface.emit(side, 1, protocol.namespace, writer);
        }

        for (targets) |target| {
            for (protocol.globals) |global| {
                if (mem.eql(u8, target.name, global.interface.name)) {
                    if (global.interface.version < target.version) {
                        process.fatal("requested {s} version {d} but only version {d} is available in provided xml", .{
                            target.name,
                            target.version,
                            global.interface.version,
                        });
                    }
                    try global.interface.emit(side, target.version, protocol.namespace, writer);
                    for (global.children) |child| {
                        try child.emit(side, target.version, protocol.namespace, writer);
                    }
                }
            }
        }
    }

    fn emitCommon(protocol: Protocol, targets: []const Target, writer: *Io.Writer) !void {
        for (protocol.version_locked_interfaces) |interface| {
            assert(interface.version == 1);
            try interface.emitCommon(1, writer);
        }

        for (protocol.globals) |global| {
            for (targets) |target| {
                if (mem.eql(u8, target.name, global.interface.name)) {
                    // We check this in emitClient() which is called first.
                    assert(global.interface.version >= target.version);

                    try global.interface.emitCommon(target.version, writer);
                    for (global.children) |child| {
                        try child.emitCommon(target.version, writer);
                    }
                    break;
                }
            } else {
                try global.interface.emitCommon(null, writer);
                for (global.children) |child| {
                    try child.emitCommon(null, writer);
                }
            }
        }
    }
};

/// All data in this struct is immutable after creation in parse().
const Interface = struct {
    name: []const u8,
    description: ?[]const u8,
    version: u32,
    requests: []const Message,
    events: []const Message,
    enums: []const Enum,

    // These interfaces are special in that their version may never be increased.
    // That is, they are pinned to version 1 forever. They also may break the
    // normally required tree object creation hierarchy.
    //
    // TODO we should automatically add objects that have multiple independent
    // constructors to this list. Protocol extensions may add version locked
    // interfaces, for example ext_image_capture_source_v1.
    const version_locked_interfaces = std.StaticStringMap(void).initComptime(.{
        .{"wl_display"},
        .{"wl_registry"},
        .{"wl_callback"},
        .{"wl_buffer"},
        .{"ext_image_capture_source_v1"},
    });
    fn version_locked(interface_name: []const u8) bool {
        return version_locked_interfaces.has(interface_name);
    }

    fn parse(gpa: mem.Allocator, arena: mem.Allocator, parser: *xml.Parser) !Interface {
        var name: ?[]const u8 = null;
        var description: ?[]const u8 = null;
        var version: ?u32 = null;
        var requests: std.ArrayList(Message) = .empty;
        defer requests.deinit(gpa);
        var events: std.ArrayList(Message) = .empty;
        defer events.deinit(gpa);
        var enums: std.ArrayList(Enum) = .empty;
        defer enums.deinit(gpa);

        while (parser.next()) |ev| switch (ev) {
            .open_tag => |tag| {
                if (mem.eql(u8, tag, "description")) {
                    if (description != null) return error.DuplicateDescription;
                    description = try parseDescription(arena, parser);
                } else if (mem.eql(u8, tag, "request"))
                    try requests.append(gpa, try Message.parse(gpa, arena, parser))
                else if (mem.eql(u8, tag, "event"))
                    try events.append(gpa, try Message.parse(gpa, arena, parser))
                else if (mem.eql(u8, tag, "enum"))
                    try enums.append(gpa, try Enum.parse(gpa, arena, parser));
            },
            .attribute => |attr| {
                if (mem.eql(u8, attr.name, "name")) {
                    if (name != null) return error.DuplicateName;
                    name = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "version")) {
                    if (version != null) return error.DuplicateVersion;
                    version = try fmt.parseInt(u32, try attr.dupeValue(arena), 10);
                }
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "interface")) {
                return Interface{
                    .name = name orelse return error.MissingName,
                    .description = description,
                    .version = version orelse return error.MissingVersion,
                    .requests = try arena.dupe(Message, requests.items),
                    .events = try arena.dupe(Message, events.items),
                    .enums = try arena.dupe(Enum, enums.items),
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn emit(interface: Interface, side: Side, target_version: u32, namespace: []const u8, writer: *Io.Writer) !void {
        if (interface.description) |desc| {
            try writer.writeByte('\n');
            var iter = mem.splitScalar(u8, mem.trimEnd(u8, desc, &std.ascii.whitespace), '\n');
            while (iter.next()) |line| {
                try writer.print("/// {s}\n", .{mem.trim(u8, line, &std.ascii.whitespace)});
            }
        }

        try writer.print(
            \\pub const {[type]f} = opaque {{
            \\ pub const generated_version = {[version]};
            \\ pub const interface = &common.{[namespace]f}.{[interface]f}.interface;
        , .{
            .type = titleCaseTrim(interface.name),
            .version = @min(interface.version, target_version),
            .namespace = fmtId(namespace),
            .interface = fmtId(trimPrefix(interface.name)),
        });

        for (interface.enums) |e| {
            if (e.since <= target_version) {
                try writer.print("pub const {[type]f} = common.{[namespace]f}.{[interface]f}.{[type]f};\n", .{
                    .type = titleCase(e.name),
                    .namespace = fmtId(namespace),
                    .interface = fmtId(trimPrefix(interface.name)),
                });
            }
        }

        if (side == .client) {
            inline for (.{
                .{ .name = "getId", .return_type = "u32" },
                .{ .name = "getVersion", .return_type = "u32" },
                .{ .name = "getUserData", .return_type = "?*anyopaque" },
            }) |func| {
                try writer.print(
                    \\pub inline fn {[function]s}(_{[interface]f}: *{[type]f}) {[return_type]s} {{
                    \\    return @as(*client.wl.Proxy, @ptrCast(_{[interface]f})).{[function]s}();
                    \\}}
                , .{
                    .function = func.name,
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                    .return_type = func.return_type,
                });
            }

            try writer.print(
                \\pub inline fn setQueue(_{[interface]f}: *{[type]f}, _queue: *client.wl.EventQueue) void {{
                \\    const _proxy: *client.wl.Proxy = @ptrCast(_{[interface]f});
                \\    _proxy.setQueue(_queue);
                \\}}
            , .{
                .interface = fmtId(trimPrefix(interface.name)),
                .type = titleCaseTrim(interface.name),
            });

            const has_event = for (interface.events) |event| {
                if (event.since <= target_version) break true;
            } else false;

            if (has_event) {
                try writer.writeAll("pub const Event = union(enum) {");
                for (interface.events) |event| {
                    if (event.since <= target_version) {
                        try emitVersion(event.name, event.since, writer);
                    }
                }
                try writer.writeByte('\n');

                for (interface.events) |event| {
                    if (event.since <= target_version) {
                        try event.emitField(.client, writer);
                    }
                }
                try writer.writeAll("};\n");
                try writer.print(
                    \\pub inline fn setListener(
                    \\    _{[interface]f}: *{[type]f},
                    \\    comptime T: type,
                    \\    _listener: *const fn ({[interface]f}: *{[type]f}, event: Event, data: T) void,
                    \\    _data: T,
                    \\) void {{
                    \\    const _proxy: *client.wl.Proxy = @ptrCast(_{[interface]f});
                    \\    const _mut_data: ?*anyopaque = @ptrFromInt(@intFromPtr(_data));
                    \\    _proxy.addDispatcher(common.Dispatcher({[type]f}, T).dispatcher, _listener, _mut_data);
                    \\}}
                , .{
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                });
            }

            var has_destroy = false;
            for (interface.requests, 0..) |request, opcode| {
                if (request.since <= target_version) {
                    if (mem.eql(u8, request.name, "destroy")) has_destroy = true;
                    try request.emitFn(side, writer, interface, opcode);
                    try emitVersion(request.name, request.since, writer);
                }
            }

            if (mem.eql(u8, interface.name, "wl_display")) {
                try writer.writeAll(@embedFile("client_display_functions.zig"));
            } else if (!has_destroy) {
                try writer.print(
                    \\pub inline fn destroy(_{[interface]f}: *{[type]f}) void {{
                    \\    const _proxy: *client.wl.Proxy = @ptrCast(_{[interface]f});
                    \\    _proxy.destroy();
                    \\}}
                , .{
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                });
            }
            // side == .server
        } else {
            try writer.print(
                \\pub inline fn create(_client: *server.wl.Client, _version: u32, _id: u32) error{{ResourceCreateFailed}}!*{[type]f} {{
                \\    return @ptrCast(try server.wl.Resource.create(_client, {[type]f}, _version, _id));
                \\}}pub inline fn destroy(_{[interface]f}: *{[type]f}) void {{
                \\    return @as(*server.wl.Resource, @ptrCast(_{[interface]f})).destroy();
                \\}}pub inline fn fromLink(_link: *server.wl.list.Link) *{[type]f} {{
                \\    return @ptrCast(server.wl.Resource.fromLink(_link));
                \\}}
            , .{
                .type = titleCaseTrim(interface.name),
                .interface = fmtId(trimPrefix(interface.name)),
            });

            inline for (.{
                .{ .name = "getLink", .return_type = "*server.wl.list.Link" },
                .{ .name = "getClient", .return_type = "*server.wl.Client" },
                .{ .name = "getId", .return_type = "u32" },
                .{ .name = "getVersion", .return_type = "u32" },
                .{ .name = "postNoMemory", .return_type = "void" },
                .{ .name = "getUserData", .return_type = "?*anyopaque" },
            }) |func|
                try writer.print(
                    \\pub inline fn {[function]s}(_{[interface]f}: *{[type]f}) {[return_type]s} {{
                    \\    return @as(*server.wl.Resource, @ptrCast(_{[interface]f})).{[function]s}();
                    \\}}
                , .{
                    .function = func.name,
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                    .return_type = func.return_type,
                });

            const has_error = for (interface.enums) |e| {
                if (mem.eql(u8, e.name, "error") and e.since <= target_version) {
                    break true;
                }
            } else false;
            if (has_error) {
                try writer.print(
                    \\pub inline fn postError(_{[interface]f}: *{[type]f}, _err: Error, _message: [*:0]const u8) void {{
                    \\    return @as(*server.wl.Resource, @ptrCast(_{[interface]f})).postError(@intCast(@intFromEnum(_err)), _message);
                    \\}}
                , .{
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                });
            }

            const has_request = for (interface.requests) |request| {
                if (request.since <= target_version) break true;
            } else false;

            if (has_request) {
                try writer.writeAll("pub const Request = union(enum) {");

                for (interface.requests) |request| {
                    if (request.since <= target_version) {
                        try emitVersion(request.name, request.since, writer);
                    }
                }

                for (interface.requests) |request| {
                    if (request.since <= target_version) {
                        try request.emitField(.server, writer);
                    }
                }
                try writer.writeAll("};\n");
                @setEvalBranchQuota(2500);
                try writer.print(
                    \\pub inline fn setHandler(
                    \\    _{[interface]f}: *{[type]f},
                    \\    comptime T: type,
                    \\    handle_request: *const fn (_{[interface]f}: *{[type]f}, request: Request, data: T) void,
                    \\    comptime handle_destroy: ?fn (_{[interface]f}: *{[type]f}, data: T) void,
                    \\    _data: T,
                    \\) void {{
                    \\    const _resource: *server.wl.Resource = @ptrCast(_{[interface]f});
                    \\    _resource.setDispatcher(
                    \\        common.Dispatcher({[type]f}, T).dispatcher,
                    \\        handle_request,
                    \\        @ptrFromInt(@intFromPtr(_data)),
                    \\        if (handle_destroy) |_handler| struct {{
                    \\            fn _wrapper(__resource: *server.wl.Resource) callconv(.c) void {{
                    \\                _handler(
                    \\                    @as(*{[type]f}, @ptrCast(__resource)),
                    \\                    @as(T, @ptrCast(@alignCast(__resource.getUserData()))),
                    \\                );
                    \\            }}
                    \\        }}._wrapper else null,
                    \\    );
                    \\}}
                , .{
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                });
            } else {
                try writer.print(
                    \\pub inline fn setHandler(
                    \\    _{[interface]f}: *{[type]f},
                    \\    comptime T: type,
                    \\    comptime handle_destroy: ?fn (_{[interface]f}: *{[type]f}, data: T) void,
                    \\    _data: T,
                    \\) void {{
                    \\    const _resource: *server.wl.Resource = @ptrCast(_{[interface]f});
                    \\    _resource.setDispatcher(
                    \\        null,
                    \\        null,
                    \\        @ptrFromInt(@intFromPtr(_data)),
                    \\        if (handle_destroy) |_handler| struct {{
                    \\            fn _wrapper(__resource: *server.wl.Resource) callconv(.c) void {{
                    \\                _handler(
                    \\                    @as(*{[type]f}, @ptrCast(__resource)),
                    \\                    @as(?*anyopaque, @ptrFromInt(@intFromPtr(__resource.getUserData()))),
                    \\                );
                    \\            }}
                    \\        }}._wrapper else null,
                    \\    );
                    \\}}
                , .{
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                });
            }

            for (interface.events, 0..) |event, opcode| {
                if (event.since <= target_version) {
                    try event.emitFn(side, writer, interface, opcode);
                    try emitVersion(event.name, event.since, writer);
                }
            }
        }

        try writer.writeAll("};\n");
    }

    fn emitCommon(interface: Interface, target_version: ?u32, writer: *Io.Writer) !void {
        try writer.print("const {f} = struct {{", .{fmtId(trimPrefix(interface.name))});

        try writer.print(
            \\const interface: common.Interface = .{{
            \\    .name = "{[name]s}",
            \\    .version = {[version]d},
            \\    .method_count = {[requests_len]d},
        , .{
            .name = interface.name,
            .version = interface.version,
            .requests_len = interface.requests.len,
        });
        if (interface.requests.len == 0) {
            try writer.writeAll(".methods = null,");
        } else {
            try writer.writeAll(".methods = &.{");
            for (interface.requests) |request| {
                try request.emitCommon(writer);
            }
            try writer.writeAll("},");
        }
        try writer.print(".event_count = {d},", .{interface.events.len});
        if (interface.events.len == 0) {
            try writer.writeAll(".events = null,");
        } else {
            try writer.writeAll(".events = &.{");
            for (interface.events) |event| {
                try event.emitCommon(writer);
            }
            try writer.writeAll("},");
        }
        try writer.writeAll("};");

        if (target_version) |target| {
            for (interface.enums) |e| {
                if (e.since <= target) {
                    try e.emit(target, writer);
                }
            }
        }

        try writer.writeAll("};");
    }
};

/// All data in this struct is immutable after creation in parse().
const Message = struct {
    name: []const u8,
    description: ?[]const u8,
    since: u32,
    args: []const Arg,
    kind: union(enum) {
        normal: void,
        constructor: ?[]const u8,
        destructor: void,
    },

    fn parse(gpa: mem.Allocator, arena: mem.Allocator, parser: *xml.Parser) !Message {
        var name: ?[]const u8 = null;
        var description: ?[]const u8 = null;
        var since: ?u32 = null;
        var args: std.ArrayList(Arg) = .empty;
        defer args.deinit(gpa);
        var destructor = false;

        while (parser.next()) |ev| switch (ev) {
            .open_tag => |tag| {
                if (mem.eql(u8, tag, "description")) {
                    if (description != null) return error.DuplicateDescription;
                    description = try parseDescription(arena, parser);
                } else if (mem.eql(u8, tag, "arg"))
                    try args.append(gpa, try Arg.parse(arena, parser));
            },
            .attribute => |attr| {
                if (mem.eql(u8, attr.name, "name")) {
                    if (name != null) return error.DuplicateName;
                    name = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "since")) {
                    if (since != null) return error.DuplicateSince;
                    since = try fmt.parseInt(u32, try attr.dupeValue(arena), 10);
                } else if (mem.eql(u8, attr.name, "type")) {
                    if (attr.valueEql("destructor")) {
                        destructor = true;
                    } else {
                        return error.InvalidType;
                    }
                }
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "request") or mem.eql(u8, tag, "event")) {
                return Message{
                    .name = name orelse return error.MissingName,
                    .description = description,
                    .since = since orelse 1,
                    .args = try arena.dupe(Arg, args.items),
                    .kind = blk: {
                        if (destructor) break :blk .destructor;
                        for (args.items) |arg|
                            if (arg.kind == .new_id) break :blk .{ .constructor = arg.kind.new_id };
                        break :blk .normal;
                    },
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn emitField(message: Message, side: Side, writer: *Io.Writer) !void {
        if (message.description) |desc| {
            try writer.writeByte('\n');
            var iter = mem.splitScalar(u8, mem.trimEnd(u8, desc, &std.ascii.whitespace), '\n');
            while (iter.next()) |line| {
                try writer.print("/// {s}\n", .{mem.trim(u8, line, &std.ascii.whitespace)});
            }
        }

        try writer.print("{f}", .{fmtId(message.name)});
        if (message.args.len == 0) {
            try writer.writeAll(": void,");
            return;
        }
        try writer.writeAll(": struct {");
        for (message.args) |arg| {
            if (side == .server and arg.kind == .new_id and arg.kind.new_id == null) {
                try writer.print("interface_name: [*:0]const u8, version: u32,{f}: u32", .{fmtId(arg.name)});
            } else if (side == .client and arg.kind == .new_id) {
                try writer.print("{f}: *", .{fmtId(arg.name)});
                try printAbsolute(.client, writer, arg.kind.new_id.?);
                assert(!arg.allow_null);
            } else {
                try writer.print("{f}:", .{fmtId(arg.name)});
                // See notes on NULL in doc comment for wl_message in wayland-util.h
                if (side == .client and arg.kind == .object and !arg.allow_null)
                    try writer.writeByte('?');
                try arg.emitType(side, writer);
            }
            try writer.writeByte(',');
        }
        try writer.writeAll("},\n");
    }

    fn emitFn(message: Message, side: Side, writer: *Io.Writer, interface: Interface, opcode: usize) !void {
        if (message.description) |desc| {
            try writer.writeByte('\n');
            var iter = mem.splitScalar(u8, mem.trimEnd(u8, desc, &std.ascii.whitespace), '\n');
            while (iter.next()) |line| {
                try writer.print("/// {s}\n", .{mem.trim(u8, line, &std.ascii.whitespace)});
            }
        }

        try writer.writeAll("pub fn ");
        if (side == .server) {
            if (message.kind == .destructor) {
                try writer.print("destroySend{f}", .{titleCase(message.name)});
            } else {
                try writer.print("send{f}", .{titleCase(message.name)});
            }
        } else {
            try writer.print("{f}", .{camelCase(message.name)});
        }
        try writer.print("(_{f}: *{f}", .{
            fmtId(trimPrefix(interface.name)),
            titleCaseTrim(interface.name),
        });
        for (message.args) |arg| {
            if (side == .server and arg.kind == .new_id) {
                try writer.print(", _{s}:", .{arg.name});
                if (arg.allow_null) try writer.writeByte('?');
                try writer.writeByte('*');
                if (arg.kind.new_id) |iface| {
                    try printAbsolute(side, writer, iface);
                } else {
                    try writer.writeAll("server.wl.Resource");
                }
            } else if (side == .client and arg.kind == .new_id) {
                if (arg.kind.new_id == null) try writer.writeAll(", comptime T: type, _version: u32");
            } else {
                try writer.print(", _{s}:", .{arg.name});
                try arg.emitType(side, writer);
            }
        }
        if (side == .server or message.kind != .constructor) {
            try writer.writeAll(") void {");
        } else if (message.kind.constructor) |new_iface| {
            try writer.writeAll(") !*");
            try printAbsolute(side, writer, new_iface);
            try writer.writeByte('{');
        } else {
            try writer.writeAll(") !*T {");
        }
        if (side == .server) {
            try writer.writeAll("const _resource: *server.wl.Resource = @ptrCast(_");
        } else {
            // wl_registry.bind for example needs special handling
            if (message.kind == .constructor and message.kind.constructor == null) {
                try writer.writeAll("const version_to_construct = @min(T.generated_version, _version);");
            }
            try writer.writeAll("const _proxy: *client.wl.Proxy = @ptrCast(_");
        }
        try writer.print("{f});", .{fmtId(trimPrefix(interface.name))});
        if (message.args.len > 0) {
            try writer.writeAll("var _args = [_]common.Argument{");
            for (message.args) |arg| {
                switch (arg.kind) {
                    .int, .uint, .fixed, .string, .array, .fd => {
                        try writer.writeAll(switch (arg.kind) {
                            .int => ".{ .i = ",
                            .uint => ".{ .u = ",
                            .fixed => ".{ .f = ",
                            .string => ".{ .s = ",
                            .array => ".{ .a = ",
                            .fd => ".{ .h = ",
                            else => unreachable,
                        });
                        if (arg.enum_name != null) {
                            try writer.writeAll("switch (@typeInfo(");
                            try arg.emitType(side, writer);

                            // TODO We know the type of the enum at scanning time, but it's
                            //      currently a bit difficult to access it.
                            const c_type = if (arg.kind == .uint) "u32" else "i32";
                            try writer.print(
                                \\ )) {{
                                \\    .@"enum" => @as({[ct]s}, @intCast(@intFromEnum(_{[an]s}))),
                                \\    .@"struct" => @bitCast(_{[an]s}),
                                \\    else => unreachable,
                                \\ }}
                            , .{ .ct = c_type, .an = arg.name });
                        } else {
                            try writer.print("_{s}", .{arg.name});
                        }
                        try writer.writeAll("},\n");
                    },
                    .object, .new_id => |new_iface| {
                        if (arg.kind == .object or side == .server) {
                            try writer.print(".{{ .o = @ptrCast(_{s}) }},\n", .{arg.name});
                        } else {
                            if (new_iface == null) {
                                try writer.writeAll(".{ .s = T.interface.name },\n");
                                try writer.writeAll(".{ .u = version_to_construct },\n");
                            }
                            try writer.writeAll(".{ .o = null },\n");
                        }
                    },
                }
            }
            try writer.writeAll("};\n");
        }
        const args = if (message.args.len > 0) "&_args" else "null";
        if (side == .server) {
            try writer.print("_resource.postEvent({}, {s});", .{ opcode, args });
            if (message.kind == .destructor) try writer.writeAll("_resource.destroy();");
        } else switch (message.kind) {
            .normal, .destructor => {
                try writer.print("_proxy.marshal({}, {s});", .{ opcode, args });
                if (message.kind == .destructor) try writer.writeAll("_proxy.destroy();");
            },
            .constructor => |new_iface| {
                if (new_iface) |i| {
                    try writer.print("return @ptrCast(try _proxy.marshalConstructor({}, &_args, ", .{opcode});
                    try printAbsolute(side, writer, i);
                    try writer.writeAll(".interface));");
                } else {
                    try writer.print(
                        \\return @ptrCast(try _proxy.marshalConstructorVersioned({[opcode]}, &_args, T.interface, version_to_construct));
                    , .{
                        .opcode = opcode,
                    });
                }
            },
        }
        try writer.writeAll("}\n");
    }

    fn emitCommon(message: Message, writer: *Io.Writer) !void {
        try writer.print(
            \\.{{ .name = "{s}", .signature = "
        , .{message.name});
        if (message.since > 1) {
            try writer.print("{d}", .{message.since});
        }
        for (message.args) |arg| {
            try arg.emitSignature(writer);
        }
        try writer.writeAll("\",");
        if (message.args.len == 0) {
            try writer.writeAll(".types = null,");
        } else {
            try writer.writeAll(".types = &.{");
            for (message.args) |arg| {
                switch (arg.kind) {
                    .new_id, .object => |interface| {
                        if (interface) |name| {
                            try writer.print("&common.{s}.{s}.interface,", .{
                                prefix(name).?,
                                trimPrefix(name),
                            });
                        } else if (arg.kind == .new_id) {
                            try writer.writeAll("null,null,null,");
                        } else {
                            try writer.writeAll("null,");
                        }
                    },
                    .int,
                    .uint,
                    .fixed,
                    .string,
                    .array,
                    .fd,
                    => try writer.writeAll("null,"),
                }
            }
            try writer.writeAll("},");
        }
        try writer.writeAll("},");
    }
};

/// All data in this struct is immutable after creation in parse().
const Arg = struct {
    const Type = union(enum) {
        int,
        uint,
        fixed,
        string,
        new_id: ?[]const u8,
        object: ?[]const u8,
        array,
        fd,
    };
    name: []const u8,
    kind: Type,
    allow_null: bool,
    enum_name: ?[]const u8,

    fn parse(arena: mem.Allocator, parser: *xml.Parser) !Arg {
        var name: ?[]const u8 = null;
        var kind: ?std.meta.Tag(Type) = null;
        var interface: ?[]const u8 = null;
        var allow_null: ?bool = null;
        var enum_name: ?[]const u8 = null;

        while (parser.next()) |ev| switch (ev) {
            .attribute => |attr| {
                if (mem.eql(u8, attr.name, "name")) {
                    if (name != null) return error.DuplicateName;
                    name = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "type")) {
                    if (kind != null) return error.DuplicateType;
                    kind = std.meta.stringToEnum(std.meta.Tag(Type), try attr.dupeValue(arena)) orelse
                        return error.InvalidType;
                } else if (mem.eql(u8, attr.name, "interface")) {
                    if (interface != null) return error.DuplicateInterface;
                    interface = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "allow-null")) {
                    if (allow_null != null) return error.DuplicateAllowNull;
                    if (!attr.valueEql("true") and !attr.valueEql("false")) return error.InvalidBoolValue;
                    allow_null = attr.valueEql("true");
                } else if (mem.eql(u8, attr.name, "enum")) {
                    if (enum_name != null) return error.DuplicateEnum;
                    enum_name = try attr.dupeValue(arena);
                }
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "arg")) {
                return Arg{
                    .name = name orelse return error.MissingName,
                    .kind = switch (kind orelse return error.MissingType) {
                        .object => .{ .object = interface },
                        .new_id => .{ .new_id = interface },
                        .int => .int,
                        .uint => .uint,
                        .fixed => .fixed,
                        .string => .string,
                        .array => .array,
                        .fd => .fd,
                    },
                    .allow_null = allow_null orelse false,
                    .enum_name = enum_name,
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn emitSignature(arg: Arg, writer: *Io.Writer) !void {
        switch (arg.kind) {
            .int => try writer.writeByte('i'),
            .uint => try writer.writeByte('u'),
            .fixed => try writer.writeByte('f'),
            .string => {
                if (arg.allow_null) try writer.writeByte('?');
                try writer.writeByte('s');
            },
            .new_id => |interface| if (interface == null)
                try writer.writeAll("sun")
            else
                try writer.writeByte('n'),
            .object => {
                if (arg.allow_null) try writer.writeByte('?');
                try writer.writeByte('o');
            },
            .array => try writer.writeByte('a'),
            .fd => try writer.writeByte('h'),
        }
    }

    fn emitType(arg: Arg, side: Side, writer: *Io.Writer) !void {
        switch (arg.kind) {
            .int, .uint => {
                if (arg.enum_name) |name| {
                    if (mem.indexOfScalar(u8, name, '.')) |dot_index| {
                        // Turn a reference like wl_shm.format into common.wl.shm.Format
                        const us_index = mem.indexOfScalar(u8, name, '_') orelse 0;
                        try writer.print("common.{s}.{s}{f}", .{
                            name[0..us_index],
                            name[us_index + 1 .. dot_index + 1],
                            titleCase(name[dot_index + 1 ..]),
                        });
                    } else {
                        try writer.print("{f}", .{titleCase(name)});
                    }
                } else if (arg.kind == .int) {
                    try writer.writeAll("i32");
                } else {
                    try writer.writeAll("u32");
                }
            },
            .new_id => try writer.writeAll("u32"),
            .fixed => try writer.writeAll("common.Fixed"),
            .string => {
                if (arg.allow_null) try writer.writeByte('?');
                try writer.writeAll("[*:0]const u8");
            },
            .object => |interface| if (interface) |i| {
                if (arg.allow_null) try writer.writeAll("?*") else try writer.writeByte('*');
                try printAbsolute(side, writer, i);
            } else {
                if (arg.allow_null) try writer.writeByte('?');
                try writer.writeAll("*common.Object");
            },
            .array => {
                try writer.writeAll("*common.Array");
            },
            .fd => try writer.writeAll("i32"),
        }
    }
};

/// All data in this struct is immutable after creation in parse().
const Enum = struct {
    name: []const u8,
    description: ?[]const u8,
    since: u32,
    entries: []const Entry,
    bitfield: bool,

    fn parse(gpa: mem.Allocator, arena: mem.Allocator, parser: *xml.Parser) !Enum {
        var name: ?[]const u8 = null;
        var description: ?[]const u8 = null;
        var since: ?u32 = null;
        var entries: std.ArrayList(Entry) = .empty;
        defer entries.deinit(gpa);
        var bitfield: ?bool = null;

        while (parser.next()) |ev| switch (ev) {
            .open_tag => |tag| {
                if (mem.eql(u8, tag, "description")) {
                    if (description != null) return error.DuplicateDescription;
                    description = try parseDescription(arena, parser);
                } else if (mem.eql(u8, tag, "entry"))
                    try entries.append(gpa, try Entry.parse(arena, parser));
            },
            .attribute => |attr| {
                if (mem.eql(u8, attr.name, "name")) {
                    if (name != null) return error.DuplicateName;
                    name = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "since")) {
                    if (since != null) return error.DuplicateSince;
                    since = try fmt.parseInt(u32, try attr.dupeValue(arena), 10);
                } else if (mem.eql(u8, attr.name, "bitfield")) {
                    if (bitfield != null) return error.DuplicateBitfield;
                    if (!attr.valueEql("true") and !attr.valueEql("false")) return error.InvalidBoolValue;
                    bitfield = attr.valueEql("true");
                }
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "enum")) {
                return Enum{
                    .name = name orelse return error.MissingName,
                    .description = description,
                    .since = since orelse 1,
                    .entries = try arena.dupe(Entry, entries.items),
                    .bitfield = bitfield orelse false,
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn emit(e: Enum, target_version: u32, writer: *Io.Writer) !void {
        if (e.description) |desc| {
            try writer.writeByte('\n');
            var iter = mem.splitScalar(u8, mem.trimEnd(u8, desc, &std.ascii.whitespace), '\n');
            while (iter.next()) |line| {
                try writer.print("/// {s}\n", .{mem.trim(u8, line, &std.ascii.whitespace)});
            }
        }

        try writer.print("const {f}", .{titleCase(e.name)});

        if (e.bitfield) {
            try writer.writeAll(" = packed struct(u32) {");

            for (e.entries) |entry| {
                if (entry.since <= target_version) {
                    try emitVersion(entry.name, entry.since, writer);
                }
            }

            try writer.writeByte('\n');

            for (0..32) |i| {
                for (e.entries) |entry| {
                    if (entry.since > target_version) continue;

                    const value = entry.intValue();
                    if (value == 0) continue;

                    if (value == (@as(u32, 1) << @intCast(i))) {
                        try writer.print("{s}: bool = false,", .{entry.name});
                        break;
                    }
                } else {
                    try writer.print("_padding{}: bool = false,", .{i});
                }
            }

            // Emit the normal C abi enum as well as it may be needed to interface
            // with C code.
            try writer.writeAll("pub const Enum ");
        }

        try writer.writeAll(" = enum(c_int) {");
        for (e.entries) |entry| {
            if (entry.since <= target_version) {
                try emitVersion(entry.name, entry.since, writer);
            }
        }
        try writer.writeByte('\n');

        for (e.entries) |entry| {
            if (entry.since <= target_version) {
                try writer.print("{f}= {s},", .{ fmtId(entry.name), entry.value });
            }
        }
        // Always generate non-exhaustive enums to ensure forward compatability.
        // Entries have been added to wl_shm.format without bumping the version.
        try writer.writeAll("_,};\n");

        if (e.bitfield) try writer.writeAll("};\n");
    }
};

/// All data in this struct is immutable after creation in parse().
const Entry = struct {
    name: []const u8,
    since: u32,
    value: []const u8,

    fn parse(arena: mem.Allocator, parser: *xml.Parser) !Entry {
        var name: ?[]const u8 = null;
        var since: ?u32 = null;
        var value: ?[]const u8 = null;

        while (parser.next()) |ev| switch (ev) {
            .attribute => |attr| {
                if (mem.eql(u8, attr.name, "name")) {
                    if (name != null) return error.DuplicateName;
                    name = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "since")) {
                    if (since != null) return error.DuplicateSince;
                    since = try fmt.parseInt(u32, try attr.dupeValue(arena), 10);
                } else if (mem.eql(u8, attr.name, "value")) {
                    if (value != null) return error.DuplicateName;
                    value = try attr.dupeValue(arena);
                }
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "entry")) {
                return Entry{
                    .name = name orelse return error.MissingName,
                    .since = since orelse 1,
                    .value = value orelse return error.MissingValue,
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    // Return numeric value of enum entry. Can be base 10 and hexadecimal notation.
    fn intValue(e: Entry) u32 {
        return fmt.parseInt(u32, e.value, 10) catch blk: {
            const index = mem.indexOfScalar(u8, e.value, 'x').?;
            break :blk fmt.parseInt(u32, e.value[index + 1 ..], 16) catch @panic("Can't parse enum entry.");
        };
    }
};

fn prefix(s: []const u8) ?[]const u8 {
    return s[0 .. mem.indexOfScalar(u8, s, '_') orelse return null];
}

fn trimPrefix(s: []const u8) []const u8 {
    return s[mem.indexOfScalar(u8, s, '_').? + 1 ..];
}

const Case = enum { title, camel };

fn formatCaseImpl(comptime case: Case, comptime trim: bool) type {
    return struct {
        pub fn f(bytes: []const u8, writer: *Io.Writer) Io.Writer.Error!void {
            if (case == .camel and std.zig.Token.getKeyword(bytes) != null) {
                try writer.print("@\"{s}\"", .{bytes});
                return;
            }
            var upper = case == .title;
            const str = if (trim) trimPrefix(bytes) else bytes;
            for (str) |c| {
                if (c == '_') {
                    upper = true;
                    continue;
                }
                try writer.writeByte(if (upper) std.ascii.toUpper(c) else c);
                upper = false;
            }
        }
    };
}

fn titleCase(bytes: []const u8) fmt.Alt([]const u8, formatCaseImpl(.title, false).f) {
    return .{ .data = bytes };
}

fn titleCaseTrim(bytes: []const u8) fmt.Alt([]const u8, formatCaseImpl(.title, true).f) {
    return .{ .data = bytes };
}

fn camelCase(bytes: []const u8) fmt.Alt([]const u8, formatCaseImpl(.camel, false).f) {
    return .{ .data = bytes };
}

fn camelCaseTrim(bytes: []const u8) fmt.Alt([]const u8, formatCaseImpl(.camel, true).f) {
    return .{ .data = bytes };
}

fn printAbsolute(side: Side, writer: *Io.Writer, interface: []const u8) !void {
    try writer.print("{s}.{s}.{f}", .{
        @tagName(side),
        prefix(interface) orelse return error.MissingPrefix,
        titleCaseTrim(interface),
    });
}
