function cmd::rule::assign() {
  local name="" peer="" type=""
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)  name="$2";  shift 2 ;;
      --peer)  peer="$2";  shift 2 ;;
      --type)  type="$2";  shift 2 ;;
      --help)  cmd::rule::help; return ;;
      *)
        log::error "Unknown flag: $1"
        return 1
        ;;
    esac
  done

  if [[ -z "$name" || -z "$peer" ]]; then
    log::error "Missing required flags: --name and --peer"
    return 1
  fi

  rule::require_exists "$name" || return 1

  # Support --type for peer resolution
  peer=$(peers::resolve_and_require "$peer" "$type") || return 1

  # Unapply existing rule first if any
  local existing_rule
  existing_rule=$(peers::get_meta "$peer" "rule")

  local ip
  ip=$(peers::get_ip "$peer")
  log::debug "rule::assign: peer=$peer ip=$ip"
  [[ -z "$ip" ]] && log::error "Could not resolve IP for: $peer" && return 1

  log::debug "assign: peer=$peer ip=$ip clients=$(ctx::clients)"

  if [[ -n "$existing_rule" && "$existing_rule" != "$name" ]]; then
    rule::unapply "$existing_rule" "$ip"
    log::wg "Removed existing rule '${existing_rule}' from: ${peer}"
  fi

  rule::apply "$name" "$ip"

  log::wg_success "Assigned rule '${name}' to: ${peer}"
}