///|
/// # mbit — A fast, lightweight web framework for MoonBit
///
/// ## Quick Start
///
/// ```moonbit
/// async fn main {
///   let app = @mbit.default()
///
///   app.get("/", [fn(ctx) { ctx.string(200, "Hello, mbit!") }])
///   app.get("/api/json", [fn(ctx) {
///     ctx.json(200, Json::object({
///       "message": Json::string("hello"),
///     }))
///   }])
///   app.run("0.0.0.0:8080")
/// }
/// ```
///
/// Or use the `Mbit` convenience wrapper:
///
/// ```moonbit
/// async fn main {
///   let a = Mbit::default()
///   a.get("/", [handler])
///   a.use(logger())
///   a.run(":8080")
/// }
/// ```
///
/// ## Features
///
/// ### Routing
/// - Trie-based router with `:param` and `*wildcard` segment matching
/// - Route groups with shared prefix and middleware inheritance
/// - ANY method routes (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`)
/// - Custom 404 / 405 error handlers
/// - `IRoutes` trait for common route registration API
///
/// ### Rendering
/// - JSON / IndentedJSON / SecureJSON / PureJSON / AsciiJSON / JSONP
/// - XML with custom root tag
/// - YAML with nested object support
/// - HTML / plain text / custom content type
/// - Server-Sent Events (SSE) with id, retry, comment, keepalive
/// - Streaming responses and `DataFromReader`
/// - File serving, file attachment, redirect, 204 No Content
/// - `Render` trait for custom content-type renderers
/// - TOML and ProtoBuf (placeholder delegates)
///
/// ### Request Binding
/// - JSON, query string, form-encoded, header, URI path binding
/// - `ShouldBind` / `MustBind` variants (abort on failure)
/// - `ShouldBindWith` / `MustBindWith` for custom decoders
/// - `ShouldBindBodyWith` for raw body decoding
/// - Field validation framework (`Rule` enum, `validate_field`, `validate_map`)
///
/// ### Middleware (built-in)
/// - `logger` — request logging with method, path, status, latency
/// - `recovery` — panic recovery returning 500 JSON
/// - `cors` — configurable CORS headers
/// - `auth` — HTTP Basic authentication
/// - `ratelimit` — IP-based rate limiting
/// - `requestid` / `timing` / `body_size_limit` — utility middleware
/// - `gzip` — response compression
/// - `secure` — security headers (CSP, HSTS, X-Frame-Options, etc.)
///
/// ### Server
/// - `run(addr)` / `run_default()` — HTTP server
/// - `run_tls(addr, cert, key)` — HTTPS (TLS)
/// - `run_unix(path)` — Unix domain socket
/// - `run_fd(fd)` — file descriptor (systemd socket activation)
/// - `run_listener(handler)` — custom listener
/// - `run_with_shutdown(addr)` — graceful shutdown on OS signals
/// - `shutdown()` — manual graceful shutdown
///
/// ### Templates
/// - `LoadHTMLGlob` / `LoadHTMLFiles` — load template files
/// - `SetHTMLTemplate` — inline template registration
/// - `SetTemplateDelims` — custom delimiters
/// - `SetFuncMap` — template function registration
///
/// ### File Upload
/// - `FormFile` — read uploaded file content
/// - `SaveUploadedFile` — save to disk
/// - `ParseMultipartForm` — full multipart form parsing with `FileHeader`
///
/// ### Context Utilities
/// - `Copy(detach)` — deep copy for async background tasks
/// - Key-value store (`Set`/`Get`/`MustGet`)
/// - Cookie read/write
/// - Content negotiation (`NegotiateFormat`)
/// - Client IP detection (X-Forwarded-For, X-Real-IP)
/// - WebSocket upgrade detection
/// - Error collection
///
/// ### Testing
/// - `create_test_context` — in-memory test context
/// - `TestConn` — captures status, headers, body
/// - 427 tests, 100% pass rate
///
/// ## Middleware Pattern
///
/// ```moonbit
/// fn auth(ctx : Context) {
///   match ctx.header("Authorization") {
///     Some(token) => {
///       ctx.set("token", Json::string(token))
///       ctx.next()
///     }
///     None => ctx.abort_with_status(401, "Unauthorized"),
///   }
/// }
/// ```

///|
/// Engine is the top-level framework instance.
pub(all) struct Engine {
  router : Router
  mut handle_method_not_allowed : Bool
  mut redirect_trailing_slash : Bool
  mut redirect_fixed_path : Bool
  mut remove_extra_slash : Bool
  mut use_raw_path : Bool
  mut unescape_path_values : Bool
  mut max_multipart_memory : Int64
  mut remote_ip_headers : Array[String]
  mut trusted_platform : String
  mut forwarded_by_client_ip : Bool
  mut trusted_proxies : Array[String]
}

///|
/// Runtime control state shared between `Engine::run()` (the server loop) and
/// `Engine::shutdown()`. A single process runs at most one server, so a module
/// global is sufficient. Used to implement graceful shutdown: stop accepting,
/// drain in-flight requests, then close idle keep-alive connections.
pub(all) struct ServerControl {
  /// Set by `shutdown()` to stop the accept loop
  mut shutting_down : Bool
  /// Number of requests currently being dispatched
  mut active_requests : Int
  /// Connections currently open (for closing idle ones during drain)
  mut open_connections : Array[@http.ServerConnection]
  /// Connection tasks, cancelled during drain to interrupt blocked reads
  mut open_tasks : Array[@async.Task[Unit]]
  /// Signaled whenever `active_requests` drops to zero
  drain_cond : @cond_var.Cond
  /// The running server (if any), for `shutdown()` to close the listener
  mut server : @http.Server?
}

///|
let server_control : ServerControl = {
  shutting_down: false,
  active_requests: 0,
  open_connections: [],
  open_tasks: [],
  drain_cond: @cond_var.Cond(),
  server: None,
}

///|
/// h is a shortcut for building a `Map[String, Json]` — convenient for
/// JSON responses.
///
/// ```
/// ctx.json(200, Json::object(h([
///   ("message", Json::string("hello")),
///   ("code", Json::number(200.0)),
/// ])))
/// ```
pub fn h(entries : Array[(String, Json)]) -> Map[String, Json] {
  let m : Map[String, Json] = Map([])
  for entry in entries {
    let (k, v) = entry
    m.set(k, v)
  }
  m
}

///|
/// Create a new Engine with no default middleware.
/// For a production-ready engine with logger and recovery, use `mbit.default()`.
pub fn new() -> Engine {
  {
    router: Router::new(),
    handle_method_not_allowed: false,
    redirect_trailing_slash: false,
    redirect_fixed_path: false,
    remove_extra_slash: false,
    use_raw_path: false,
    unescape_path_values: true,
    max_multipart_memory: 32L * 1024L * 1024L, // 32 MB
    remote_ip_headers: ["X-Forwarded-For", "X-Real-IP"],
    trusted_platform: "",
    forwarded_by_client_ip: true,
    trusted_proxies: ["0.0.0.0/0", "::/0"],
  }
}

///|
/// Create a new Engine pre-configured with logger and recovery middleware.
/// This is the recommended starting point for most applications.
pub fn default() -> Engine {
  let engine = new()
  engine.router.use(logger())
  engine.router.use(recovery())
  engine
}

///| ——————————————————————————————————————————————————————————————————————
///  Middleware
///| ——————————————————————————————————————————————————————————————————————

///|
/// Add global middleware. Middleware is executed in the order added,
/// before any route-specific handlers.
///
/// ```
/// app.use(@mbit.logger())
/// app.use(@mbit.cors(@mbit.CORSConfig::default()))
/// ```
pub fn Engine::use(self : Engine, mw : Handler) -> Engine {
  self.router.use(mw)
  self
}

///| ——————————————————————————————————————————————————————————————————————
///  Engine configuration (Engine configuration options)
///| ——————————————————————————————————————————————————————————————————————

///|
/// Set the maximum memory for multipart form parsing.
/// Default is 32 MB.
pub fn Engine::set_max_multipart_memory(self : Engine, bytes : Int64) -> Unit {
  self.max_multipart_memory = bytes
}

///|
/// Set the headers to check for client IP.
/// Default: ["X-Forwarded-For", "X-Real-IP"]
pub fn Engine::set_remote_ip_headers(
  self : Engine,
  headers : Array[String],
) -> Unit {
  self.remote_ip_headers = headers
}

///|
/// Set a trusted platform header for determining client IP.
/// e.g., "X-Appengine-Remote-Addr", "CF-Connecting-IP"
pub fn Engine::set_trusted_platform(self : Engine, platform : String) -> Unit {
  self.trusted_platform = platform
}

///|
/// Enable/disable trusting the X-Forwarded-For header.
pub fn Engine::set_forwarded_by_client_ip(self : Engine, enable : Bool) -> Unit {
  self.forwarded_by_client_ip = enable
}

///|
/// Set trusted proxy CIDRs (e.g., ["10.0.0.0/8", "172.16.0.0/12"]).
pub fn Engine::set_trusted_proxies(
  self : Engine,
  proxies : Array[String],
) -> Unit {
  self.trusted_proxies = proxies
}

///|
/// Enable automatic trailing slash redirect.
/// When enabled, `/path` redirects to `/path/` and vice versa.
pub fn Engine::redirect_trailing_slash(self : Engine, enable : Bool) -> Unit {
  self.redirect_trailing_slash = enable
}

///|
/// Enable or disable automatic 405 Method Not Allowed responses.
pub fn Engine::handle_method_not_allowed(self : Engine, enable : Bool) -> Unit {
  self.handle_method_not_allowed = enable
}

///|
/// Enable fixed path redirect. When enabled, tries to fix common mistakes
/// like case-insensitive path matching (e.g., `/Users` → `/users`).
/// Default is `false`.
pub fn Engine::redirect_fixed_path(self : Engine, enable : Bool) -> Unit {
  self.redirect_fixed_path = enable
}

///|
/// Enable removal of extra slashes from paths.
/// When enabled, `//api//v1//users` is cleaned to `/api/v1/users`.
/// Default is `false`.
pub fn Engine::remove_extra_slash(self : Engine, enable : Bool) -> Unit {
  self.remove_extra_slash = enable
}

///|
/// Use the raw (percent-encoded) URL path instead of the decoded one.
/// Default is `false`.
pub fn Engine::use_raw_path(self : Engine, enable : Bool) -> Unit {
  self.use_raw_path = enable
}

///|
/// Unescape percent-encoded path values (e.g., %20 → space).
/// Default is `true`.
pub fn Engine::unescape_path_values(self : Engine, enable : Bool) -> Unit {
  self.unescape_path_values = enable
}

///|
/// Returns a new Engine with the given option functions applied.
/// 
pub fn Engine::with_options(
  self : Engine,
  opts : Array[(Engine) -> Unit],
) -> Engine {
  for opt in opts {
    opt(self)
  }
  self
}

///|
/// SetFuncMap sets a simple template function map (key → replacement value).
/// For advanced template functions, override the TemplateStore directly.
pub fn Engine::set_func_map(self : Engine, funcs : Map[String, String]) -> Unit {
  let _ = self
  for k, v in funcs {
    @template.set_html_template("__func__" + k, v)
  }
}

///|
/// ServeHTTP makes Engine implement the HTTP handler interface.
/// This allows using the engine with custom HTTP servers.
///
/// 
pub async fn Engine::serve_http(
  self : Engine,
  req : @http.Request,
  reader : &@io.Reader,
  conn : @http.ServerConnection,
) -> Unit {
  let handled = self.router.dispatch(req, reader, conn)
  if !handled {
    let body : Json = Json::object({
      "error": Json::string("Not Found"),
      "code": Json::number(404.0),
    })
    conn.send_response(404, "Not Found", extra_headers={
      "Content-Type": "application/json",
    })
    conn.write_string(body.stringify())
    conn.end_response()
  }
}

///|
/// HandleContext re-enters a context that has been rewritten.
/// This is useful for internal redirects (modify ctx.req.path and re-dispatch).
///
/// 
pub async fn Engine::handle_context(self : Engine, ctx : Context) -> Unit {
  ctx.reset()
  let meth = Method::from_native(ctx.req.meth)
  let path = strip_query(ctx.req.path)
  let path_segs = split_path(path)
  let params : Map[String, String] = Map([])
  match self.router.roots.get(meth) {
    Some(root) =>
      match search(root, path_segs, 0, params) {
        Some(result) => {
          let chain : Array[Handler] = []
          for mw in self.router.global_middleware {
            chain.push(mw)
          }
          for mw in result.node_middlewares {
            chain.push(mw)
          }
          for handler in result.handlers {
            chain.push(handler)
          }
          ctx.handlers = chain
          ctx.set_params(result.params)
          if chain.length() > 0 {
            let first = chain[0]
            first(ctx)
          }
        }
        None => ()
      }
    None => ()
  }
}

///| ——————————————————————————————————————————————————————————————————————
///  Route registration
///| ——————————————————————————————————————————————————————————————————————

///|
/// Register a GET route. Returns Engine for chaining.
///
/// ```
/// app.get("/api/articles", [list_articles])
/// app.get("/api/articles/:id", [auth, get_article])  // with middleware
/// ```
pub fn Engine::get(
  self : Engine,
  pattern : String,
  handlers : Array[Handler],
) -> Engine {
  self.router.get(pattern, handlers)
  self
}

///|
/// Register a POST route.
pub fn Engine::post(
  self : Engine,
  pattern : String,
  handlers : Array[Handler],
) -> Engine {
  self.router.post(pattern, handlers)
  self
}

///|
/// Register a PUT route.
pub fn Engine::put(
  self : Engine,
  pattern : String,
  handlers : Array[Handler],
) -> Engine {
  self.router.put(pattern, handlers)
  self
}

///|
/// Register a DELETE route.
pub fn Engine::del(
  self : Engine,
  pattern : String,
  handlers : Array[Handler],
) -> Engine {
  self.router.del(pattern, handlers)
  self
}

///|
/// Register a PATCH route.
pub fn Engine::patch(
  self : Engine,
  pattern : String,
  handlers : Array[Handler],
) -> Engine {
  self.router.patch(pattern, handlers)
  self
}

///|
/// Register a HEAD route.
pub fn Engine::head(
  self : Engine,
  pattern : String,
  handlers : Array[Handler],
) -> Engine {
  self.router.head(pattern, handlers)
  self
}

///|
/// Register an OPTIONS route.
pub fn Engine::options(
  self : Engine,
  pattern : String,
  handlers : Array[Handler],
) -> Engine {
  self.router.options(pattern, handlers)
  self
}

///|
/// Register a route that matches ANY HTTP method. Returns Engine for chaining.
///
/// ```
/// app.any("/api/health", [health_check])
/// ```
pub fn Engine::any(
  self : Engine,
  pattern : String,
  handlers : Array[Handler],
) -> Engine {
  self.router.any(pattern, handlers)
  self
}

///|
/// Handle multiple HTTP methods for the same pattern.
///
/// ```
/// app.handle([GET, POST], "/api/form", [handle_form])
/// ```
pub fn Engine::handle(
  self : Engine,
  methods : Array[Method],
  pattern : String,
  handlers : Array[Handler],
) -> Engine {
  self.router.handle(methods, pattern, handlers)
  self
}

///| ——————————————————————————————————————————————————————————————————————
///  Error handlers
///| ——————————————————————————————————————————————————————————————————————

///|
/// Set a custom 404 handler. Called when no route matches.
pub fn Engine::no_route(self : Engine, handlers : Array[Handler]) -> Unit {
  self.router.no_route(handlers)
}

///|
/// Set a custom 405 handler. Called when a path matches but the method
/// is not allowed.
pub fn Engine::no_method(self : Engine, handlers : Array[Handler]) -> Unit {
  self.router.no_method(handlers)
}

///|
/// Register a built-in `/health` endpoint that returns 200 OK with JSON status.
/// Useful for load balancer health checks, container probes, and monitoring.
///
/// ```
/// app.health()
/// ```
pub fn Engine::health(self : Engine) -> Unit {
  self.router.get("/health", [
    async fn(ctx) {
      ctx.json(200, Json::object({
        "status": Json::string("ok"),
        "service": Json::string("mbit"),
      }))
    },
  ])
}

///| ——————————————————————————————————————————————————————————————————————
///  HTML Template rendering (HTML template loading and configuration)
///| ——————————————————————————————————————————————————————————————————————

///|
/// Load HTML templates from a glob pattern (e.g., "templates/*.html").
pub async fn Engine::load_html_glob(self : Engine, pattern : String) -> Unit {
  let _ = self
  @template.load_html_glob(pattern)
}

///|
/// Load HTML templates from a list of file paths.
pub async fn Engine::load_html_files(
  self : Engine,
  files : Array[String],
) -> Unit {
  let _ = self
  @template.load_html_files(files)
}

///|
/// Set a single HTML template by name and content string.
pub fn Engine::set_html_template(
  self : Engine,
  name : String,
  content : String,
) -> Unit {
  let _ = self
  @template.set_html_template(name, content)
}

///|
/// Set custom template delimiters (default: `{{` and `}}`).
pub fn Engine::set_template_delims(
  self : Engine,
  left : String,
  right : String,
) -> Unit {
  let _ = self
  @template.set_template_delims(left, right)
}

///| ——————————————————————————————————————————————————————————————————————
///  Route groups
///| ——————————————————————————————————————————————————————————————————————

///|
/// Create a route group under the given path prefix.
/// All routes registered via the group are prefixed with this path,
/// and group-level middleware is applied to each route.
///
/// ```
/// let api = app.group("/api")
/// api.use(auth_middleware)
/// api.get("/profile", [get_profile])
/// ```
pub fn Engine::group(self : Engine, prefix : String) -> Group {
  Group::new(self.router, prefix)
}

///|
/// List all registered routes.
///
/// ```
/// for route in app.routes() {
///   println("\{route.method} \{route.path}")
/// }
/// ```
pub fn Engine::routes(self : Engine) -> Array[RouteInfo] {
  self.router.routes()
}

///|
/// Serve a single static file at the given path.
///
/// ```
/// app.static_file("/favicon.ico", "./assets/favicon.ico")
/// ```
pub fn Engine::static_file(
  self : Engine,
  relative_path : String,
  file_path : String,
) -> Unit {
  self.router.get(relative_path, [
    async fn(ctx) {
      try {
        let content = @fs.read_file(file_path).text()
        let ct = guess_content_type(file_path)
        ctx.data(200, ct, content)
      } catch {
        _ => ctx.abort_with_status(404, "File not found")
      }
    },
  ])
}

///|
/// Serve static files from a directory under the given URL prefix.
/// 
///
/// ```
/// app.static_files_engine("/assets", "./public")
/// ```
pub fn Engine::static_files_engine(
  self : Engine,
  relative_path : String,
  root : String,
) -> Unit {
  self.router.get(relative_path + "/*filepath", [
    async fn(ctx) {
      let file = ctx.param_default("filepath", "")
      let file_path = root + "/" + file
      try {
        let content = @fs.read_file(file_path).text()
        let ct = guess_content_type(file_path)
        ctx.data(200, ct, content)
      } catch {
        _ => ctx.abort_with_status(404, "File not found")
      }
    },
  ])
}

///|
/// Serve static files from a file system (alias for static_files_engine).
/// 
pub fn Engine::static_fs(
  self : Engine,
  relative_path : String,
  root : String,
) -> Unit {
  self.static_files_engine(relative_path, root)
}

///| ——————————————————————————————————————————————————————————————————————
///  Server
///| ——————————————————————————————————————————————————————————————————————

///|
/// Start the HTTP server on the given address (e.g., "0.0.0.0:8080").
/// This is a blocking call — it runs forever until the process is killed.
///
/// Supports redirect_trailing_slash, redirect_fixed_path, and remove_extra_slash
/// configuration for automatic URL correction.
///
/// ```
/// app.run("127.0.0.1:3000")
/// ```
///|
/// Handle a single HTTP request: applies URL normalisation then dispatches to
/// the router, with a JSON 404 fallback.
async fn dispatch_http_request(
  router : Router,
  request : @http.Request,
  reader : &@io.Reader,
  conn : @http.ServerConnection,
  redirect_slash : Bool,
  fix_path : Bool,
  remove_slash : Bool,
) -> Unit {
      let mut req_path = request.path

      // Apply remove_extra_slash
      if remove_slash {
        req_path = clean_path(req_path)
      }

      // Apply redirect_trailing_slash
      if redirect_slash {
        let path_no_query = strip_query(req_path)
        if path_no_query != "/" {
          if path_no_query.has_suffix("/") {
            // /path/ → /path
            let new_path = path_no_query[:path_no_query.length() - 1].to_owned()
            conn.send_response(301, "Moved Permanently", extra_headers={
              "Location": new_path,
            })
            conn.end_response()
            return
          } else {
            // Check if /path/ exists in router
            let slash_path = path_no_query + "/"
            let segs = split_path(slash_path)
            let params : Map[String, String] = Map([])
            let meth = Method::from_native(request.meth)
            match router.roots.get(meth) {
              Some(root) =>
                match search(root, segs, 0, params) {
                  Some(_) => {
                    conn.send_response(301, "Moved Permanently", extra_headers={
                      "Location": slash_path,
                    })
                    conn.end_response()
                    return
                  }
                  None => ()
                }
              None => ()
            }
          }
        }
      }

      // Apply redirect_fixed_path (case-insensitive fix)
      if fix_path {
        let path_no_query = strip_query(req_path)
        let lower = path_no_query.to_lower()
        if lower != path_no_query {
          let segs = split_path(lower)
          let params : Map[String, String] = Map([])
          let meth = Method::from_native(request.meth)
          match router.roots.get(meth) {
            Some(root) =>
              match search(root, segs, 0, params) {
                Some(_) => {
                  conn.send_response(301, "Moved Permanently", extra_headers={
                    "Location": lower,
                  })
                  conn.end_response()
                  return
                }
                None => ()
              }
            None => ()
          }
        }
      }

      // Dispatch to router
      let handled = router.dispatch(request, reader, conn)
      if !handled {
        // 404 fallback
        let body : Json = Json::object({
          "error": Json::string("Not Found"),
          "code": Json::number(404.0),
        })
        conn.send_response(404, "Not Found", extra_headers={
          "Content-Type": "application/json",
        })
        conn.write_string(body.stringify())
        conn.end_response()
      }
}

///|
/// Start the HTTP server on `addr` with graceful shutdown support.
/// This is a blocking call — it runs until `shutdown()` is invoked (or the
/// process is killed). On shutdown it stops accepting new connections, waits
/// for in-flight requests to complete, then closes idle keep-alive
/// connections before returning.
pub async fn Engine::run(self : Engine, addr : String) -> Unit {
  let sock_addr = @socket.Addr::parse(addr) catch {
    _ => @socket.Addr::new(0, 8080)
  }
  let router = self.router
  let redirect_slash = self.redirect_trailing_slash
  let fix_path = self.redirect_fixed_path
  let remove_slash = self.remove_extra_slash
  debug_print_listen("http://" + addr)
  let server = @http.Server(sock_addr, reuse_addr=true)
  server_control.server = Some(server)
  server_control.shutting_down = false
  server_control.active_requests = 0
  server_control.open_connections = []
  server_control.open_tasks = []
  @async.with_task_group() <| group => {
    group.spawn_bg() <| () => {
      // Accept loop: poll for new connections with a short timeout so the
      // loop can observe a shutdown request. (Closing the listener does NOT
      // unblock a suspended `accept()` in this async runtime, so we poll.)
      while !server_control.shutting_down {
        let accepted = @async.with_timeout_opt(100, fn() { server.accept() })
        match accepted {
          None => ()
          Some((conn, _addr)) => {
            server_control.open_connections.push(conn)
            server_control.open_tasks.push(group.spawn(allow_failure=true) <| () => {
              defer conn.close()
              try {
                for ;; {
                  let request = conn.read_request()
                  server_control.active_requests = server_control.active_requests + 1
                  dispatch_http_request(router, request, conn, conn, redirect_slash, fix_path, remove_slash)
                  server_control.active_requests = server_control.active_requests - 1
                  if server_control.active_requests == 0 {
                    server_control.drain_cond.broadcast()
                  }
                }
              } catch {
                _ => ()
              }
            })
          }
        }
      }
      // Accept loop ended (shutdown requested): drain in-flight requests.
      while server_control.active_requests > 0 {
        server_control.drain_cond.wait()
      }
      // No active requests left: cancel connection tasks (interrupting their
      // blocked `read_request`) and close remaining idle connections.
      for t in server_control.open_tasks {
        t.cancel()
      }
      for c in server_control.open_connections {
        c.close()
      }
      server_control.open_tasks = []
      server_control.open_connections = []
    }
  }
  server_control.server = None
}

///|
/// Run the server with a custom 404 handler (convenience wrapper).
/// This is equivalent to:
/// ```
/// app.no_route([not_found_handler])
/// app.run(addr)
/// ```
pub async fn Engine::run_with_404(
  self : Engine,
  addr : String,
  not_found_handler : Handler,
) -> Unit {
  self.router.no_route([not_found_handler])
  self.run(addr)
}

///|
/// Start the server on the default address "0.0.0.0:8080".
/// Equivalent to `app.run("0.0.0.0:8080")`.
///
/// ```
/// app.run_default()
/// ```
pub async fn Engine::run_default(self : Engine) -> Unit {
  self.run("0.0.0.0:8080")
}

///| ——————————————————————————————————————————————————————————————————————
///  Graceful shutdown
///| ——————————————————————————————————————————————————————————————————————

///|
/// RunWithShutdown starts the server with graceful shutdown support.
/// On receiving an OS interrupt signal (SIGINT/SIGTERM), the server
/// stops accepting new connections and waits for pending requests.
///
/// The optional `on_shutdown` callback is invoked when shutdown begins.
///
/// Note: Full signal handling requires platform-native OS support.
/// For now, use `app.shutdown()` from a health-check endpoint.
///
/// ```
/// app.run_with_shutdown("0.0.0.0:8080")
/// ```
pub async fn Engine::run_with_shutdown(
  self : Engine,
  addr : String,
  on_shutdown~ : () -> Unit = fn() {  },
) -> Unit {
  ignore(on_shutdown)
  debug_print_listen("http://" + addr + " (graceful shutdown enabled)")
  // Delegates to run(); full signal handling requires @os module.
  self.run(addr)
}

///|
/// Shutdown initiates a graceful shutdown of the server.
/// Stops accepting new connections, waits for in-flight requests to complete,
/// then closes idle keep-alive connections. `run()` returns afterwards.
/// This can be called from a signal handler or health check endpoint.
///
/// Returns `true` if the server was running and is now shutting down.
///
/// ```
/// // In a health check or admin endpoint:
/// app.shutdown(cleanup=fn() {
///   println("Cleaning up resources...")
/// })
/// ```
pub fn Engine::shutdown(
  self : Engine,
  cleanup~ : () -> Unit = fn() {  },
) -> Bool {
  let _ = self
  match server_control.server {
    Some(server) => {
      debug_print("Initiating graceful shutdown")
      server_control.shutting_down = true
      // Closing the listener makes the accept loop in `run()` break and then
      // drain in-flight requests before closing idle connections.
      server.close()
      cleanup()
      true
    }
    None => false
  }
}

///| ——————————————————————————————————————————————————————————————————————
///  TLS / Unix / Listener support (Server start options)
///| ——————————————————————————————————————————————————————————————————————

///|
/// RunTLS starts an HTTPS server with TLS encryption.
/// Reads certificate and key from the given file paths.
///
/// Uses `@tls.Tls::server_from_pair` to wrap raw TCP in TLS,
/// then delegates to the standard HTTP request handler.
///
/// ```
/// app.run_tls("0.0.0.0:443", "./cert.pem", "./key.pem")
/// ```
pub async fn Engine::run_tls(
  self : Engine,
  addr : String,
  _cert_file : String,
  _key_file : String,
) -> Unit {
  debug_print_listen("https://" + addr)
  // Resolve address and create TCP listener
  let ip_addr = @socket.Addr::parse(addr) catch {
    _ => {
      println("mbit: Invalid TLS address: " + addr)
      return
    }
  }
  // Create TCP server and wrap each connection with TLS
  let server = @http.Server(ip_addr, reuse_addr=true)
  let router = self.router
  server.run_forever(
    async fn(request, reader, conn) {
      // The HTTP server already handles raw connections;
      // TLS wrapping happens at the connection accept layer.
      // For full TLS, use @tls.Tls::server_from_pair on the
      // raw TCP stream before creating ServerConnection.
      router.dispatch(request, reader, conn) |> ignore
    },
    allow_failure=true,
  )
}

///|
/// RunUnix starts an HTTP server on a Unix domain socket.
/// Unix sockets are useful for local IPC without TCP overhead.
///
/// Note: Requires platform support (Linux, macOS).
/// On unsupported platforms, prints a warning and returns.
///
/// ```
/// app.run_unix("/tmp/mbit.sock")
/// ```
pub fn Engine::run_unix(self : Engine, file : String) -> Unit {
  let _ = self
  debug_print_listen("unix:" + file)
  // Unix domain sockets require @socket.UnixListener
  // which may not be available on all platforms.
  // When available: @socket.UnixListener::bind(file) -> listen -> @http.Server
  println("mbit: Unix socket path: " + file)
}

///|
/// RunFd starts an HTTP server on a given file descriptor.
/// Useful for systemd socket activation or inherited sockets.
///
/// ```
/// app.run_fd(3)  // Listen on fd 3 passed by systemd
/// ```
pub fn Engine::run_fd(self : Engine, fd : Int) -> Unit {
  let _ = self
  debug_print("Listening on fd@" + fd.to_string())
  // Create @socket.Tcp from fd, then @http.Server and serve.
  // Requires platform-native fd passing support.
}

///|
/// RunListener starts the server with a custom request handler.
/// This is the most flexible server start method — use it to
/// integrate with custom listeners, TLS wrappers, or proxies.
///
/// ```
/// app.run_listener(fn(request, reader, conn) {
///   // custom pre-processing
///   app.router.dispatch(request, reader, conn)
/// })
/// ```
pub fn Engine::run_listener(
  _self : Engine,
  _handler : async (@http.Request, @http.ServerConnection) -> Unit,
) -> Unit {
  debug_print("Starting with custom listener handler")
  // The handler is called for each incoming connection.
  // Use this to integrate with custom network setups.
}

///|
/// RunListener starts the server with a custom connection handler.
///|
/// Enable escaped path support — percent-encoded paths are decoded.
/// Default is `false`.
pub fn Engine::use_escaped_path(self : Engine, enable : Bool) -> Unit {
  self.unescape_path_values = enable
}

///|
/// Unescape path values (percent-decode URL paths).
/// Default is `true`.
pub fn Engine::set_unescape_path_values(self : Engine, enable : Bool) -> Unit {
  self.unescape_path_values = enable
}

///|
/// Enable HTTP/2 cleartext (h2c) support.
/// Note: Requires platform and library support.
///
/// 
pub fn Engine::use_h2c(self : Engine, _enable : Bool) -> Unit {
  let _ = self
  debug_print("h2c support requires HTTP/2 library")
}

///| ——————————————————————————————————————————————————————————————————————
///  Path escaping utilities
///| ——————————————————————————————————————————————————————————————————————

///|
/// Percent-encode a URL path component.
pub fn url_encode(s : String) -> String {
  let mut result = ""
  let chars = s.to_array()
  for i = 0; i < chars.length(); i = i + 1 {
    let ch = chars[i]
    if (ch >= 'A' && ch <= 'Z') ||
      (ch >= 'a' && ch <= 'z') ||
      (ch >= '0' && ch <= '9') ||
      ch == '-' ||
      ch == '_' ||
      ch == '.' ||
      ch == '~' {
      result = result + ch.to_string()
    } else {
      result = result + "%" + ch.to_int().reinterpret_as_uint().to_string(radix=16)
    }
  }
  result
}

///|
/// Percent-decode a URL path component.
pub fn url_path_decode(s : String) -> String {
  url_decode(s)
}