//! mgf-hrb-usb-watch — the U2b live demo: stream USB add/remove events.
//!
//! Opens a netlink uevent watch via `LinuxUsbBackend.vWatch`, then loops
//! `poll() → pumpUevent`, printing one line per delivered event. Direct-
//! substrate tool — does NOT need the broker.
//!
//! Try it: run this in one terminal, then `plug/unplug a USB device` in
//! another. Each interface add/remove fires one line.
//!
//! Output is the same column style as mgf-hrb-usb-scan, prefixed with the
//! action so two terminals can show a coherent picture:
//!
//!     [+ add  ]  hw:/usb/bus3/port-9/dev/if0
//!     [- remove]  hw:/usb/bus3/port-9/dev/if0
//!
//! Exit codes:
//!   0 — Ctrl-C / SIGINT.
//!   1 — netlink open failed (e.g. no permission).

const std = @import("std");
const linux = std.os.linux;
const kabi = @import("mgf_hrb_kabi");
const model = @import("mgf_hrb_model");
const LinuxUsbBackend = @import("mgf_hrb_linux_usb").LinuxUsbBackend;

const PrintEventSink = struct {
    count: usize = 0,

    fn push(ctx: *anyopaque, ev: model.Event) void {
        const self: *PrintEventSink = @ptrCast(@alignCast(ctx));
        self.count += 1;
        switch (ev) {
            .added => |a| std.debug.print(
                "[+ add   ]  {s}\n",
                .{a.urn.slice()},
            ),
            .removed => |r| std.debug.print(
                "[- remove]  {s}\n",
                .{r.urn.slice()},
            ),
            .gap => |g| std.debug.print(
                "[~ gap   ]  source={s}  at_ms={d}  (events lost — re-enumerate, Part 5.3.3)\n",
                .{ @tagName(g.source), g.since_ms },
            ),
            // future variants surface their kind so nothing is silently dropped
            else => std.debug.print("[? other ]  (event kind not yet rendered)\n", .{}),
        }
    }

    fn sink(self: *PrintEventSink) kabi.EventSink {
        return .{ .ctx = self, .push = push };
    }
};

pub fn main() !u8 {
    const alloc = std.heap.page_allocator;
    var lb = LinuxUsbBackend.init(alloc);
    defer lb.closeWatch();

    var sink_holder = PrintEventSink{};
    const sink = sink_holder.sink();

    const handle = lb.backend().watch("hw:/usb/**", &sink, kabi.Deadline.none) catch |err| {
        std.debug.print("error: netlink uevent open failed: {s}\n", .{@errorName(err)});
        std.debug.print(
            "  (CAP_NET_ADMIN is NOT required; check that NETLINK_KOBJECT_UEVENT\n" ++
                "   is enabled in your kernel and that you have a /sys mounted.)\n",
            .{},
        );
        return 1;
    };

    std.debug.print(
        \\mgf-hrb-usb-watch — netlink uevent stream
        \\
        \\Listening on NETLINK_KOBJECT_UEVENT group 1 (kernel multicast).
        \\Each line is one USB-interface add/remove event. Plug or unplug a
        \\device — a composite shows one row per interface (§2.4).
        \\Press Ctrl-C to exit.
        \\
        \\
    , .{});

    const fd: i32 = @intCast(handle.id);
    var pfd = [_]linux.pollfd{
        .{ .fd = fd, .events = linux.POLL.IN, .revents = 0 },
    };

    while (true) {
        // 1-second timeout so Ctrl-C is responsive even when the bus
        // is idle.
        const rc = linux.poll(&pfd, 1, 1_000);
        const err = linux.errno(rc);
        if (err == .INTR) continue; // signal caught — loop, exits via Ctrl-C
        if (err != .SUCCESS) {
            std.debug.print("error: poll failed: {s}\n", .{@tagName(err)});
            return 1;
        }
        if (rc == 0) continue; // timeout — try again

        if ((pfd[0].revents & linux.POLL.IN) != 0) {
            lb.pumpUevent(&sink, "hw:/usb/**") catch |perr| {
                std.debug.print("error: pumpUevent failed: {s}\n", .{@errorName(perr)});
                return 1;
            };
        }
    }
}
