function cmd::group::run() {
  local subcmd="${1:-help}"
  shift || true

  if command::json; then
    cmd::group::_output_json
    return 0
  fi

  case "$subcmd" in
    list|ls)              cmd::group::list "$@"        ;;
    show)                 cmd::group::show "$@"        ;;
    add|new|create)       cmd::group::add "$@"         ;;
    remove|rm|del|delete) cmd::group::remove "$@"      ;;
    rename)               cmd::group::rename "$@"      ;;
    peer)                 cmd::group::peer "$@"        ;;
    rm-peers)             cmd::group::rm_peers "$@"    ;;
    set-main)             cmd::group::set_main "$@"    ;;
    block)                cmd::group::block "$@"       ;;
    unblock)              cmd::group::unblock "$@"     ;;
    rule)                 cmd::group::rule "$@"        ;;
    purge-stale)          cmd::group::purge_stale "$@" ;;
    audit)                cmd::group::audit "$@"       ;;
    logs)                 cmd::group::logs "$@"        ;;
    watch)                cmd::group::watch "$@"       ;;
    help)                 cmd::group::help             ;;
    *)
      log::error "Unknown subcommand: '${subcmd}'"
      cmd::group::help
      return 1
      ;;
  esac
}

# ============================================
# List
# ============================================

function cmd::group::list() {
  local groups_dir
  groups_dir="$(ctx::groups)"
 
  local groups=("${groups_dir}"/*.group)
  if [[ ! -f "${groups[0]}" ]]; then
    log::wg "No groups configured"
    return 0
  fi
 
  local data
  data=$(json::group_list_data "$groups_dir" "$(ctx::blocks)" "$(ctx::clients)")
  [[ -z "$data" ]] && log::wg "No groups configured" && return 0
 
  # Measure column widths
  local w_name=12 w_desc=16
  while IFS="|" read -r name desc total blocked; do
    [[ -z "$name" ]] && continue
    (( ${#name} > w_name )) && w_name=${#name}
    local desc_len=${#desc}
    [[ -z "$desc" ]] && desc_len=1
    (( desc_len > w_desc )) && w_desc=$desc_len
  done <<< "$data"
  (( w_name += 2 ))
  (( w_desc += 2 ))
 
  log::section "Groups"
  echo ""
 
  if display::is_table "group_list"; then
    cmd::group::_render_table "$data" "$w_name" "$w_desc"
    return 0
  fi

  while IFS="|" read -r name desc total blocked; do
    [[ -z "$name" ]] && continue
    ui::group::list_row "$name" "$desc" "$total" "$blocked" "$w_name" "$w_desc"
  done <<< "$data"
 
  echo ""
}

# ============================================
# Show
# ============================================

function cmd::group::show() {
  local name=""

  while [[ $# -gt 0 ]]; do
function cmd::group::add() {
  local name="" desc=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --desc) util::require_flag "--desc" "${2:-}" || return 1; desc="$2"; shift 2 ;;
      --help) cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1

  if group::exists "$name"; then
    log::error "Group already exists: ${name}"
    return 1
  fi

  json::create_group "$(group::path "$name")" "$name" "$desc"

  log::wg_success "Group created: ${name}"
}

# ============================================
# Remove
# ============================================

function cmd::group::remove() {
  local name="" force=false

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)  util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --force) force=true; shift ;;