fn build_panel(title: &str) -> (gtk::Box, gtk::Box) {
    build_panel_with_action(title, None)
}

/// Build a vertical panel with an optional header action.
fn build_panel_with_action(title: &str, action: Option<&gtk::Widget>) -> (gtk::Box, gtk::Box) {
    let panel = gtk::Box::new(gtk::Orientation::Vertical, 8);
    panel.add_css_class("panel");

    let header = gtk::Box::new(gtk::Orientation::Horizontal, 8);
    header.set_hexpand(true);
    header.set_halign(gtk::Align::Fill);
    let heading = gtk::Label::new(Some(title));
    heading.add_css_class("panel-title");
    heading.set_halign(gtk::Align::Start);
    heading.set_hexpand(true);
    header.append(&heading);
    if let Some(action) = action {
        header.append(action);
    }
    panel.append(&header);

    let body = gtk::Box::new(gtk::Orientation::Vertical, 8);
    panel.append(&body);
    (panel, body)
}

fn build_subgroup(title: &str) -> gtk::Box {
    build_subgroup_with_action(title, None)
}

/// Build a titled subgroup row matching the relay-control section style.
fn build_subgroup_with_action(title: &str, action: Option<&gtk::Widget>) -> gtk::Box {
    let group = gtk::Box::new(gtk::Orientation::Vertical, 8);
    group.add_css_class("subgroup");
    let heading_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
    heading_row.set_hexpand(true);
    let heading = gtk::Label::new(Some(title));
    heading.add_css_class("subgroup-title");
    heading.set_halign(gtk::Align::Start);
    heading.set_hexpand(true);
    heading_row.append(&heading);
    if let Some(action) = action {
        heading_row.append(action);
    }
    group.append(&heading_row);
    group
}

const STATUS_CHIP_WIDTH: i32 = 84;
const STATUS_CHIP_VALUE_CHARS: i32 = 8;

/// Build a fixed-width status chip so changing labels do not move the header row.
fn build_status_chip(label: &str, value: &str) -> (gtk::Box, gtk::Label) {
    let chip = gtk::Box::new(gtk::Orientation::Vertical, 4);
    chip.add_css_class("status-chip");
    chip.set_hexpand(false);
    chip.set_size_request(STATUS_CHIP_WIDTH, -1);

    let label_widget = gtk::Label::new(Some(label));
    label_widget.add_css_class("status-chip-label");
    label_widget.set_halign(gtk::Align::Center);
    label_widget.set_xalign(0.5);
    let value_widget = gtk::Label::new(Some(value));
    value_widget.add_css_class("status-chip-value");
    value_widget.set_halign(gtk::Align::Center);
    value_widget.set_xalign(0.5);
    value_widget.set_ellipsize(pango::EllipsizeMode::End);
    value_widget.set_single_line_mode(true);
    value_widget.set_width_chars(STATUS_CHIP_VALUE_CHARS);
    chip.append(&label_widget);
    chip.append(&value_widget);
    (chip, value_widget)
}

/// Build a fixed-width status chip with a colored health light.
fn build_status_chip_with_light(label: &str, value: &str) -> (gtk::Box, gtk::Box, gtk::Label) {
    let chip = gtk::Box::new(gtk::Orientation::Vertical, 4);
    chip.add_css_class("status-chip");
    chip.set_hexpand(false);
    chip.set_size_request(STATUS_CHIP_WIDTH, -1);

    let meta = gtk::Box::new(gtk::Orientation::Horizontal, 6);
    meta.add_css_class("status-chip-meta");
    meta.set_halign(gtk::Align::Center);
    let light = gtk::Box::new(gtk::Orientation::Horizontal, 0);
    light.add_css_class("status-light");
    light.add_css_class("status-light-idle");
    let label_widget = gtk::Label::new(Some(label));
    label_widget.add_css_class("status-chip-label");
    label_widget.set_halign(gtk::Align::Center);
    label_widget.set_xalign(0.5);
    meta.append(&light);
    meta.append(&label_widget);
    let value_widget = gtk::Label::new(Some(value));
    value_widget.add_css_class("status-chip-value");
    value_widget.set_halign(gtk::Align::Center);
    value_widget.set_xalign(0.5);
    value_widget.set_ellipsize(pango::EllipsizeMode::End);
    value_widget.set_single_line_mode(true);
    value_widget.set_width_chars(STATUS_CHIP_VALUE_CHARS);
    chip.append(&meta);
    chip.append(&value_widget);
    (chip, light, value_widget)
}
