///|
/// JavaScript Request FFI type
pub(all) type JsRequest

///|
/// JavaScript Response FFI type
pub(all) type JsResponse

///|
/// JavaScript Headers FFI type
type JsHeaders

///|
/// Get request method from JsRequest
extern "js" fn js_request_method(req : JsRequest) -> String =
  #| (req) => req.method

///|
/// Get request URL from JsRequest
extern "js" fn js_request_url(req : JsRequest) -> String =
  #| (req) => req.url

///|
/// Get request body as text from JsRequest
extern "js" fn js_request_text(req : JsRequest) -> @js_async.Promise[String] =
  #| (req) => req.text()

///|
/// Get request headers from JsRequest
extern "js" fn js_request_headers(req : JsRequest) -> JsHeaders =
  #| (req) => req.headers

///|
/// Get header value from JsHeaders
/// Returns undefined for None (headers.get returns null which MoonBit may not handle correctly)
extern "js" fn js_headers_get(headers : JsHeaders, name : String) -> String? =
  #| (headers, name) => {
  #|   const value = headers.get(name);
  #|   return value === null ? undefined : value;
  #| }

///|
/// Create a new JsResponse with headers object
/// Headers are passed as MoonBit Map and converted to JS object in FFI
extern "js" fn js_response_new(
  body : String,
  status : Int,
  headers : Map[String, String],
) -> JsResponse =
  #| (body, status, headers) => {
  #|   // Convert MoonBit Map to plain JS object for Response headers
  #|   // MoonBit Map structure: { head: node, entries: [...] }
  #|   // where each node has: { key, value, next }
  #|   const headerObj = {};
  #|   let curr = headers.head;
  #|   while (curr !== undefined && curr !== -1) {
  #|     headerObj[curr.key] = curr.value;
  #|     curr = curr.next;
  #|   }
  #|   return new Response(body, { status, headers: headerObj });
  #| }

///|
/// JavaScript platform-specific context data
pub(all) struct PlatformContext {
  /// Original JavaScript request
  js_request : JsRequest
  /// JavaScript headers
  js_headers : JsHeaders
  /// Response resolver callback
  response_resolve : (JsResponse) -> Unit
}

///|
/// Create a new PlatformContext for JS
pub fn PlatformContext::new(
  js_request : JsRequest,
  response_resolve : (JsResponse) -> Unit,
) -> PlatformContext {
  { js_request, js_headers: js_request_headers(js_request), response_resolve }
}

///|
/// Create a new Context (JS-specific constructor)
pub fn Context::new_js(
  request : @http.Request,
  params : @router.Params,
  js_request : JsRequest,
  response_resolve : (JsResponse) -> Unit,
) -> Context {
  {
    request,
    params,
    platform: PlatformContext::new(js_request, response_resolve),
    data: Map::new(),
    vars: Variables::new(),
    execution_ctx: ExecutionContext::new(),
    env_bindings: MapEnv::new(),
    cached_body: None,
    cached_query: None,
    cached_cookies: None,
    response_headers: {},
    status: 200,
    response_sent: false,
  }
}

///|
/// Create a new Context with environment bindings (JS-specific constructor)
pub fn Context::with_env_js(
  request : @http.Request,
  params : @router.Params,
  js_request : JsRequest,
  response_resolve : (JsResponse) -> Unit,
  env : MapEnv,
) -> Context {
  {
    request,
    params,
    platform: PlatformContext::new(js_request, response_resolve),
    data: Map::new(),
    vars: Variables::new(),
    execution_ctx: ExecutionContext::new(),
    env_bindings: env,
    cached_body: None,
    cached_query: None,
    cached_cookies: None,
    response_headers: {},
    status: 200,
    response_sent: false,
  }
}

///|
/// Read request body as string (JS implementation)
pub async fn Context::body(self : Context) -> String {
  match self.cached_body {
    Some(body) => body
    None => {
      let body = js_request_text(self.platform.js_request).wait()
      self.cached_body = Some(body)
      body
    }
  }
}

// Note: build_js_headers removed - headers are now passed directly to FFI
// The js_response_new FFI handles Map[String, String] -> JS object conversion

///|
/// Send response via JS callback
fn send_js_response(
  resolve : (JsResponse) -> Unit,
  body : String,
  status : Int,
  headers : Map[String, String],
) -> Unit {
  let response = js_response_new(body, status, headers)
  resolve(response)
}

///|
/// Send a text response (JS implementation)
pub fn Context::text(self : Context, body : String, status? : Int) -> Unit {
  let status = status.unwrap_or(self.status)
  let headers = merge_headers(self.response_headers, {
    "Content-Type": "text/plain; charset=utf-8",
  })
  send_js_response(self.platform.response_resolve, body, status, headers)
  self.response_sent = true
}

///|
/// Send a JSON response (JS implementation)
pub fn Context::json(self : Context, data : Json, status? : Int) -> Unit {
  let status = status.unwrap_or(self.status)
  let headers = merge_headers(self.response_headers, {
    "Content-Type": "application/json",
  })
  send_js_response(
    self.platform.response_resolve,
    data.stringify(),
    status,
    headers,
  )
  self.response_sent = true
}

///|
/// Send an HTML response (JS implementation)
pub fn Context::html(self : Context, body : String, status? : Int) -> Unit {
  let status = status.unwrap_or(self.status)
  let headers = merge_headers(self.response_headers, {
    "Content-Type": "text/html; charset=utf-8",
  })
  send_js_response(self.platform.response_resolve, body, status, headers)
  self.response_sent = true
}

///|
/// Send a redirect response (JS implementation)
pub fn Context::redirect(self : Context, url : String) -> Unit {
  let headers = merge_headers(self.response_headers, { "Location": url })
  send_js_response(self.platform.response_resolve, "", 302, headers)
  self.response_sent = true
}

///|
/// Send a 404 Not Found response (JS implementation)
pub fn Context::not_found(self : Context) -> Unit {
  self.text("Not Found", status=404)
}

///|
/// Start an SSE response - not fully supported in JS fetch handler
/// For full SSE support, consider using native or specialized streaming
pub fn Context::sse_start(self : Context) -> Unit {
  let headers = merge_headers(self.response_headers, {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    "Connection": "keep-alive",
  })
  // Note: Full SSE streaming requires ReadableStream in JS
  // This is a simplified implementation
  send_js_response(self.platform.response_resolve, "", 200, headers)
  self.response_sent = true
}

///|
/// Write raw data - limited support in JS fetch handler
pub fn Context::write_raw(self : Context, _data : String) -> Unit {
  // Note: Streaming writes require ReadableStream in JS
  // This is a no-op in the simplified fetch handler
  let _ = self
  ()
}

///|
/// End the response - no-op in JS fetch handler
pub fn Context::end_response(self : Context) -> Unit {
  // Response is already sent via Promise resolution
  let _ = self
  ()
}

///|
/// Convert JS method string to @http.RequestMethod
pub fn js_method_to_request_method(meth : String) -> @http.RequestMethod {
  match meth {
    "GET" => @http.RequestMethod::Get
    "POST" => @http.RequestMethod::Post
    "PUT" => @http.RequestMethod::Put
    "DELETE" => @http.RequestMethod::Delete
    "PATCH" => @http.RequestMethod::Patch
    "HEAD" => @http.RequestMethod::Head
    "OPTIONS" => @http.RequestMethod::Options
    "CONNECT" => @http.RequestMethod::Connect
    "TRACE" => @http.RequestMethod::Trace
    _ => @http.RequestMethod::Get
  }
}

///|
/// Parse path from URL string
fn parse_path_from_url(url : String) -> String {
  // Find protocol end (://)
  let mut start = 0
  let chars : Array[Char] = []
  for c in url {
    chars.push(c)
  }
  // Skip protocol
  for i = 0; i < chars.length() - 2; i = i + 1 {
    if chars[i] == ':' && chars[i + 1] == '/' && chars[i + 2] == '/' {
      start = i + 3
      break
    }
  }
  // Find host end (first / after protocol)
  for i = start; i < chars.length(); i = i + 1 {
    if chars[i] == '/' {
      // Return from this / to end
      let buf = StringBuilder::new()
      for j = i; j < chars.length(); j = j + 1 {
        buf.write_char(chars[j])
      }
      return buf.to_string()
    }
  }
  "/"
}

///|
/// Parse headers from JsHeaders to Map
fn parse_js_headers(js_headers : JsHeaders) -> Map[String, String] {
  let headers : Map[String, String] = {}
  // Common headers to check
  let common_headers = [
    "Content-Type", "Accept", "Authorization", "Cookie", "User-Agent", "Host", "Origin",
    "Referer", "X-Requested-With", "Content-Length",
    // Fetch Metadata headers for CSRF protection
    "Sec-Fetch-Site", "Sec-Fetch-Mode", "Sec-Fetch-Dest",
  ]
  for name in common_headers {
    match js_headers_get(js_headers, name) {
      Some(value) => headers.set(name, value)
      None => ()
    }
  }
  headers
}

///|
/// Create @http.Request from JsRequest
pub fn js_request_to_http_request(js_req : JsRequest) -> @http.Request {
  let meth = js_method_to_request_method(js_request_method(js_req))
  let url = js_request_url(js_req)
  let path = parse_path_from_url(url)
  let headers = parse_js_headers(js_request_headers(js_req))
  { meth, path, headers }
}