///|
/// Context supplied to every middleware and terminal handler.
pub(all) struct HookContext {
  provider : HookProvider
  delivery_id : String
  middleware_index : Int
} derive(Eq, Debug)

///|
/// Middleware either forwards an enriched event or stops processing.
pub(all) enum MiddlewareOutcome {
  Continue(HookEvent)
  Halt(HookResult)
} derive(Debug)

///|
pub type MiddlewareFn = (HookEvent, HookContext) -> MiddlewareOutcome

///|
pub type HookHandler = (HookEvent, HookContext) -> HookResult

///|
/// Named middleware entry used by the processing pipeline.
pub struct HookMiddleware {
  name : String
  callback : MiddlewareFn
}

///|
/// Ordered middleware pipeline with a terminal event handler.
pub struct HookPipeline {
  middlewares : Array[HookMiddleware]
  handler : HookHandler
}

///|
/// Execution details exposed to HTTP adapters, logs, and tests.
pub(all) struct PipelineResult {
  event : HookEvent
  result : HookResult
  executed : Array[String]
} derive(Debug)

///|
pub fn HookMiddleware::HookMiddleware(
  name : StringView,
  callback : MiddlewareFn,
) -> HookMiddleware {
  { name: name.to_owned(), callback }
}

///|
pub fn new_hook_pipeline(handler : HookHandler) -> HookPipeline {
  { middlewares: [], handler }
}

///|
/// Append middleware while preserving registration order.
pub fn HookPipeline::use_middleware(
  self : HookPipeline,
  name : StringView,
  callback : MiddlewareFn,
) -> HookPipeline {
  let middlewares : Array[HookMiddleware] = []
  for middleware in self.middlewares {
    middlewares.push(middleware)
  }
  middlewares.push(HookMiddleware(name, callback))
  { middlewares, handler: self.handler }
}

///|
pub fn HookPipeline::middleware_count(self : HookPipeline) -> Int {
  self.middlewares.length()
}

///|
/// Execute middleware in order and invoke the handler only when none halts.
pub fn HookPipeline::run(
  self : HookPipeline,
  initial : HookEvent,
) -> PipelineResult {
  let mut current = initial
  let executed : Array[String] = []
  let mut index = 0
  for middleware in self.middlewares {
    let context = {
      provider: current.provider,
      delivery_id: current.delivery_id,
      middleware_index: index,
    }
    executed.push(middleware.name)
    match (middleware.callback)(current, context) {
      Continue(updated) => current = updated
      Halt(result) => return { event: current, result, executed }
    }
    index = index + 1
  }
  let context = {
    provider: current.provider,
    delivery_id: current.delivery_id,
    middleware_index: index,
  }
  { event: current, result: (self.handler)(current, context), executed }
}

///|
/// Built-in middleware that requires valid JSON before routing.
pub fn require_json_middleware() -> MiddlewareFn {
  (event, _) => {
    if event.attribute("payload.valid_json") == Some("true") {
      Continue(event)
    } else {
      Halt(failed(message="request body is not valid JSON"))
    }
  }
}

///|
/// Built-in middleware that enriches every event with a static attribute.
pub fn attribute_middleware(key : String, value : String) -> MiddlewareFn {
  (event, _) => Continue(event.with_attribute(key, value))
}

///|
/// Built-in middleware that rejects events missing a parsed attribute.
pub fn require_attribute_middleware(key : String) -> MiddlewareFn {
  (event, _) => {
    if event.attribute(key) is Some(_) {
      Continue(event)
    } else {
      Halt(failed(message="required event attribute is missing: " + key))
    }
  }
}