///|
/// Join a group prefix with a route path into a single, normalised path: at most
/// one slash between the two, a guaranteed leading slash, and `"/"` for the empty
/// result.
fn join_path(prefix : String, path : String) -> String {
  let mut end = prefix.length()
  while end > 0 && prefix[end - 1] == '/' {
    end = end - 1
  }
  let base = prefix[0:end].to_owned()
  let tail = if path.length() > 0 && path[0] != '/' { "/" + path } else { path }
  let joined = base + tail
  if joined.length() == 0 {
    "/"
  } else {
    joined
  }
}

///|
/// A route group (← go-zero's `RouteGroup`): registers a set of routes on an
/// underlying `moonapi.App` under a shared path prefix, so related endpoints
/// (e.g. everything under `/api/v1`) are declared without repeating the prefix.
pub struct Group {
  app : @moonapi.App
  prefix : String
}

///|
/// Open a group that prefixes every route it registers with `prefix` on `app`.
pub fn Group::new(app : @moonapi.App, prefix : String) -> Group {
  { app, prefix }
}

///|
/// The prefix this group joins onto each registered route.
pub fn Group::prefix(self : Group) -> String {
  self.prefix
}

///|
/// Register a route for an explicit method under the group's prefix.
pub fn Group::route(
  self : Group,
  verb : @moonapi.Method,
  path : String,
  handler : @moonapi.ApiHandler,
  summary? : String = "",
) -> Unit {
  self.app.route(verb, join_path(self.prefix, path), handler, summary~)
}

///|
/// Register a `GET` route under the group's prefix.
pub fn Group::get(
  self : Group,
  path : String,
  handler : @moonapi.ApiHandler,
  summary? : String = "",
) -> Unit {
  self.route(Get, path, handler, summary~)
}

///|
/// Register a `POST` route under the group's prefix.
pub fn Group::post(
  self : Group,
  path : String,
  handler : @moonapi.ApiHandler,
  summary? : String = "",
) -> Unit {
  self.route(Post, path, handler, summary~)
}

///|
/// Register a `PUT` route under the group's prefix.
pub fn Group::put(
  self : Group,
  path : String,
  handler : @moonapi.ApiHandler,
  summary? : String = "",
) -> Unit {
  self.route(Put, path, handler, summary~)
}

///|
/// Register a `PATCH` route under the group's prefix.
pub fn Group::patch(
  self : Group,
  path : String,
  handler : @moonapi.ApiHandler,
  summary? : String = "",
) -> Unit {
  self.route(Patch, path, handler, summary~)
}

///|
/// Register a `DELETE` route under the group's prefix.
pub fn Group::delete(
  self : Group,
  path : String,
  handler : @moonapi.ApiHandler,
  summary? : String = "",
) -> Unit {
  self.route(Delete, path, handler, summary~)
}