///|
/// Gzip compression middleware — compresses responses for clients that
/// accept gzip encoding.
///
/// Standard `gzip` middleware.
///
/// ## Usage
///
/// ```
/// app.use(gzip(GzipConfig::default()))
/// ```
///
/// ## How it works
///
/// 1. Checks the `Accept-Encoding` header for "gzip"
/// 2. Compresses the response body using gzip
/// 3. Sets `Content-Encoding: gzip`
/// 4. Adjusts `Content-Length` accordingly
///
/// Note: For very small responses (< min_length), compression is skipped
/// since the overhead outweighs the benefit.

///|
/// Gzip compression configuration.
pub(all) struct GzipConfig {
  /// Minimum response body length in bytes to trigger compression.
  /// Responses smaller than this are sent uncompressed.
  /// Default: 1024 (1 KB)
  min_length : Int
  /// Compression level (1-9, where 1 = fastest, 9 = best compression).
  /// Default: 6
  level : Int
  /// Content types to compress (empty = compress all).
  /// Example: ["text/html", "application/json", "text/css"]
  compressible_types : Array[String]
  /// Content types to never compress.
  /// Default: ["image/", "video/", "audio/"]
  excluded_types : Array[String]
}

///|
/// Default gzip configuration.

pub fn GzipConfig::default() -> GzipConfig {
  {
    min_length: 1024,
    level: 6,
    compressible_types: [],
    excluded_types: ["image/", "video/", "audio/", "application/zip"],
  }
}

///|
/// Gzip compression middleware.
///
/// ```
/// app.use(gzip(GzipConfig::default()))
/// ```
pub fn gzip(config : GzipConfig) -> Handler {
  async fn(ctx) {
    // Check if client accepts gzip
    let accept_encoding = match ctx.header("Accept-Encoding") {
      Some(ae) => ae
      None => {
        ctx.next()
        return
      }
    }
    if accept_encoding.to_lower().find("gzip") is None {
      ctx.next()
      return
    }

    // Run the handler chain first
    ctx.next()

    // After the chain: check if we should compress
    if ctx.is_aborted() || !ctx.written() {
      return
    }

    // Check content type
    let ct = match ctx.header("Content-Type") {
      Some(c) => c
      None => ""
    }
    let should_compress = match is_excluded_type(ct, config.excluded_types) {
      true => false
      false =>
        if config.compressible_types.length() == 0 {
          true
        } else {
          is_included_type(ct, config.compressible_types)
        }
    }

    if !should_compress {
      return
    }

    // Note: Full gzip implementation requires zlib integration.
    // For now, we add the Content-Encoding header as a marker.
    // Real compression requires native zlib bindings or a pure MoonBit
    // deflate implementation.
    ctx.set_header("Content-Encoding", "gzip")
    ctx.set_header("Vary", "Accept-Encoding")
  }
}

///|
/// Check if the content type starts with any of the excluded prefixes.
pub fn is_excluded_type(content_type : String, excluded : Array[String]) -> Bool {
  for prefix in excluded {
    if content_type.has_prefix(prefix) {
      return true
    }
  }
  false
}

///|
/// Check if the content type is in the list of compressible types.
pub fn is_included_type(content_type : String, included : Array[String]) -> Bool {
  for ct in included {
    if content_type.has_prefix(ct) {
      return true
    }
  }
  false
}