// CORS Middleware
//
// Cross-Origin Resource Sharing middleware with flexible configuration.

// ============================================================================
// CORS Configuration
// ============================================================================

///|
/// Origin configuration
pub enum CorsOrigin {
  /// Allow all origins (*)
  All
  /// Allow single origin
  Single(String)
  /// Allow multiple origins
  List(Array[String])
  /// Dynamic origin validation
  Dynamic((String) -> Bool)
}

///|
/// CORS configuration
pub struct CorsConfig {
  /// Allowed origins
  origin : CorsOrigin
  /// Allowed methods
  methods : Array[String]
  /// Allowed headers
  allowed_headers : Array[String]
  /// Exposed headers
  exposed_headers : Array[String]
  /// Allow credentials
  credentials : Bool
  /// Max age for preflight cache (seconds)
  max_age : Int?
}

///|
/// Default CORS configuration (permissive)
pub fn CorsConfig::default() -> CorsConfig {
  {
    origin: CorsOrigin::All,
    methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
    allowed_headers: ["Content-Type", "Authorization", "X-Requested-With"],
    exposed_headers: [],
    credentials: false,
    max_age: Some(86400), // 24 hours
  }
}

///|
/// Strict CORS configuration (same origin only)
pub fn CorsConfig::strict() -> CorsConfig {
  {
    origin: CorsOrigin::List([]),
    methods: ["GET", "POST"],
    allowed_headers: ["Content-Type"],
    exposed_headers: [],
    credentials: false,
    max_age: None,
  }
}

///|
/// Set allowed origin
pub fn CorsConfig::with_origin(
  self : CorsConfig,
  origin : CorsOrigin,
) -> CorsConfig {
  { ..self, origin, }
}

///|
/// Set single origin
pub fn CorsConfig::with_origin_single(
  self : CorsConfig,
  origin : String,
) -> CorsConfig {
  { ..self, origin: CorsOrigin::Single(origin) }
}

///|
/// Set allowed methods
pub fn CorsConfig::with_methods(
  self : CorsConfig,
  methods : Array[String],
) -> CorsConfig {
  { ..self, methods, }
}

///|
/// Set allowed headers
pub fn CorsConfig::with_allowed_headers(
  self : CorsConfig,
  headers : Array[String],
) -> CorsConfig {
  { ..self, allowed_headers: headers }
}

///|
/// Set exposed headers
pub fn CorsConfig::with_exposed_headers(
  self : CorsConfig,
  headers : Array[String],
) -> CorsConfig {
  { ..self, exposed_headers: headers }
}

///|
/// Enable credentials
pub fn CorsConfig::with_credentials(self : CorsConfig) -> CorsConfig {
  { ..self, credentials: true }
}

///|
/// Set max age
pub fn CorsConfig::with_max_age(self : CorsConfig, max_age : Int) -> CorsConfig {
  { ..self, max_age: Some(max_age) }
}

// ============================================================================
// CORS Middleware
// ============================================================================

///|
/// Create CORS middleware with default config
pub fn cors() -> Middleware {
  cors_with_config(CorsConfig::default())
}

///|
/// Create CORS middleware with custom config
pub fn cors_with_config(config : CorsConfig) -> Middleware {
  match static_mars_cors_handler(config) {
    Some(base_handler) => Middleware(base_handler)
    None =>
      Middleware(
        @mars.Handler(async fn(ctx) {
          let request_origin = match ctx.header("Origin") {
            Some(o) => o
            None => return // Continue
          }
          let response_origin = match
            resolve_response_origin(request_origin, config.origin) {
            Some(origin) => origin
            None => return
          }
          let options = to_mars_cors_options(config, response_origin)
          let @mars.Handler(base_handler) = @mars_mw.cors(options~)
          base_handler(ctx)
        }),
      )
  }
}

///|
fn static_mars_cors_handler(config : CorsConfig) -> @mars.Handler? {
  let response_origin = match config.origin {
    All => Some("*")
    Single(origin) => Some(origin)
    List(_) | Dynamic(_) => None
  }
  match response_origin {
    Some(origin) => {
      let options = to_mars_cors_options(config, origin)
      Some(@mars_mw.cors(options~))
    }
    None => None
  }
}

///|
fn resolve_response_origin(
  request_origin : String,
  allowed : CorsOrigin,
) -> String? {
  match allowed {
    All => Some("*")
    Single(allowed_origin) =>
      if request_origin == allowed_origin {
        Some(allowed_origin)
      } else {
        None
      }
    List(origins) =>
      if origins.contains(request_origin) {
        Some(request_origin)
      } else {
        None
      }
    Dynamic(check) =>
      if check(request_origin) {
        Some(request_origin)
      } else {
        None
      }
  }
}

///|
fn to_mars_cors_options(
  config : CorsConfig,
  response_origin : String,
) -> @mars_mw.CorsOptions {
  let max_age = match config.max_age {
    Some(age) => age
    None => 0
  }
  {
    origin: response_origin,
    methods: config.methods,
    allowed_headers: config.allowed_headers,
    exposed_headers: config.exposed_headers,
    credentials: config.credentials,
    max_age,
  }
}