///|
/// The continuation function passed to middleware, calling the next handler in the chain.
pub(all) struct MiddlewareNext(async () -> &Responder noraise)

///|
/// A middleware function that receives a request event and a next continuation, and returns a response.
pub(all) struct Middleware(async (Event, MiddlewareNext) -> &Responder noraise)

///|
fn normalize_middleware_base_path(base_path : String) -> String {
  if base_path == "" {
    ""
  } else if base_path.length() > 1 && base_path[base_path.length() - 1:] == "/" {
    base_path[:base_path.length() - 1].to_owned()
  } else {
    base_path
  }
}

///|
test "normalize_middleware_base_path with empty string" {
  debug_inspect(
    normalize_middleware_base_path(""),
    content=(
      #|""
    ),
  )
}

///|
test "normalize_middleware_base_path with root slash" {
  debug_inspect(
    normalize_middleware_base_path("/"),
    content=(
      #|"/"
    ),
  )
}

///|
test "normalize_middleware_base_path strips trailing slash" {
  debug_inspect(
    normalize_middleware_base_path("/api/"),
    content=(
      #|"/api"
    ),
  )
}

///|
test "normalize_middleware_base_path keeps path without trailing slash" {
  debug_inspect(
    normalize_middleware_base_path("/api"),
    content=(
      #|"/api"
    ),
  )
}

///|
/// Adds a middleware to the app, optionally scoped to a base path prefix.
pub fn App::use_middleware(
  self : App,
  middleware : Middleware,
  base_path? : String,
) -> Unit {
  let base_path = normalize_middleware_base_path(
    base_path.unwrap_or(self.base_path),
  )
  // Store middleware together with path scope
  self.middlewares.push((base_path, middleware))
}

///|
// Execute middleware chain with path matching (onion model)
async fn execute_middlewares(
  middlewares : Array[(String, Middleware)],
  event : Event,
  final_handler : HttpHandler,
) -> &Responder noraise {
  // Fast path: no middleware registered
  if middlewares.is_empty() {
    return final_handler(event)
  }
  // Filter middlewares matching the request path
  let matched_middlewares = []
  let request_path = event.req.path()
  for mw in middlewares {
    let (base_path, middleware) = mw
    // Empty base_path means global middleware;
    // otherwise check if the request path matches the scope
    if base_path == "" || @httputil.path_scope_matches(base_path, request_path) {
      matched_middlewares.push(middleware)
    }
  }
  // Fast path: no middleware matched this path
  if matched_middlewares.is_empty() {
    return final_handler(event)
  }
  // Build middleware chain recursively (onion model)
  execute_middleware_chain(matched_middlewares, 0, event, final_handler)
}

///|
// Recursively execute middleware chain
async fn execute_middleware_chain(
  middlewares : Array[Middleware],
  index : Int,
  event : Event,
  final_handler : HttpHandler,
) -> &Responder noraise {
  if index >= middlewares.length() {
    // All middlewares executed: call the final handler
    final_handler(event)
  } else {
    // Execute current middleware
    let current_middleware = middlewares[index]
    let next = MiddlewareNext(() => {
      execute_middleware_chain(middlewares, index + 1, event, final_handler)
    })
    current_middleware(event, next)
  }
}

///|
async test "empty middleware list calls final handler directly" {
  let event = Event::{
    req: HttpRequest(Get, "/test", {}, raw_body=b""),
    res: HttpResponse(status_code=OK),
    params: {},
  }
  let handler : HttpHandler = _ => "handler-result"
  let result = execute_middlewares([], event, handler)
  let buf = Buffer()
  result.output(buf)
  debug_inspect(
    @utf8.decode(buf.contents()),
    content=(
      #|"handler-result"
    ),
  )
}

///|
async test "single middleware wraps final handler" {
  let order : Array[String] = []
  let mw : Middleware = (_event, next) => {
    order.push("mw-before")
    let result = next()
    order.push("mw-after")
    result
  }
  let event = Event::{
    req: HttpRequest(Get, "/test", {}, raw_body=b""),
    res: HttpResponse(status_code=OK),
    params: {},
  }
  let handler : HttpHandler = _ => {
    order.push("handler")
    "done"
  }
  let middlewares : Array[(String, Middleware)] = [("", mw)]
  ignore(execute_middlewares(middlewares, event, handler))
  debug_inspect(
    order,
    content=(
      #|["mw-before", "handler", "mw-after"]
    ),
  )
}

///|
async test "multiple middlewares execute in onion model order" {
  let order : Array[String] = []
  let mw1 : Middleware = (_event, next) => {
    order.push("mw1-before")
    let result = next()
    order.push("mw1-after")
    result
  }
  let mw2 : Middleware = (_event, next) => {
    order.push("mw2-before")
    let result = next()
    order.push("mw2-after")
    result
  }
  let event = Event::{
    req: HttpRequest(Get, "/test", {}, raw_body=b""),
    res: HttpResponse(status_code=OK),
    params: {},
  }
  let handler : HttpHandler = _ => {
    order.push("handler")
    "done"
  }
  let middlewares : Array[(String, Middleware)] = [("", mw1), ("", mw2)]
  ignore(execute_middlewares(middlewares, event, handler))
  debug_inspect(
    order,
    content=(
      #|["mw1-before", "mw2-before", "handler", "mw2-after", "mw1-after"]
    ),
  )
}

///|
async test "path-scoped middleware skips non-matching request" {
  let order : Array[String] = []
  let mw : Middleware = (_event, next) => {
    order.push("api-mw")
    next()
  }
  let event = Event::{
    req: HttpRequest(Get, "/test", {}, raw_body=b""),
    res: HttpResponse(status_code=OK),
    params: {},
  }
  let handler : HttpHandler = _ => {
    order.push("handler")
    "done"
  }
  let middlewares : Array[(String, Middleware)] = [("/api", mw)]
  ignore(execute_middlewares(middlewares, event, handler))
  debug_inspect(
    order,
    content=(
      #|["handler"]
    ),
  )
}

///|
async test "path-scoped middleware executes for matching request" {
  let order : Array[String] = []
  let mw : Middleware = (_event, next) => {
    order.push("api-mw")
    next()
  }
  let event = Event::{
    req: HttpRequest(Get, "/api/users", {}, raw_body=b""),
    res: HttpResponse(status_code=OK),
    params: {},
  }
  let handler : HttpHandler = _ => {
    order.push("handler")
    "done"
  }
  let middlewares : Array[(String, Middleware)] = [("/api", mw)]
  ignore(execute_middlewares(middlewares, event, handler))
  debug_inspect(
    order,
    content=(
      #|["api-mw", "handler"]
    ),
  )
}