// Copyright 2018-2026 the Deno authors. MIT license.
use std::borrow::Cow;
use std::cell::RefCell;
use std::ffi::c_void;
use std::future::Future;
use std::future::poll_fn;
use std::io;
use std::pin::Pin;
use std::rc::Rc;

use bytes::Bytes;
use bytes::BytesMut;
use deno_core::AsyncMut;
use deno_core::AsyncRefCell;
use deno_core::AsyncResult;
use deno_core::BufView;
use deno_core::CancelFuture;
use deno_core::CancelHandle;
use deno_core::CancelTryFuture;
use deno_core::ExternalPointer;
use deno_core::FromV8;
use deno_core::JsBuffer;
use deno_core::OpState;
use deno_core::RcRef;
use deno_core::Resource;
use deno_core::ResourceId;
use deno_core::convert::ByteString;
use deno_core::external;
use deno_core::op2;
use deno_core::unsync::JoinHandle;
use deno_core::unsync::spawn;
use deno_core::v8;
use deno_net::ops_tls::TlsStream;
use deno_net::raw::NetworkStream;
use deno_net::raw::NetworkStreamReadHalf;
use deno_net::raw::NetworkStreamWriteHalf;
use deno_websocket::ws_create_server_stream;
use fly_accept_encoding::Encoding;
use hyper::StatusCode;
use hyper::body::Incoming;
use hyper::header::ACCEPT_ENCODING;
use hyper::header::CACHE_CONTROL;
use hyper::header::CONTENT_ENCODING;
use hyper::header::CONTENT_LENGTH;
use hyper::header::CONTENT_RANGE;
use hyper::header::CONTENT_TYPE;
use hyper::header::COOKIE;
use hyper::header::HeaderMap;
use hyper::http::HeaderName;
use hyper::http::HeaderValue;
use hyper::server::conn::http1;
use hyper::server::conn::http2;
use hyper::service::HttpService;
use hyper::service::service_fn;
use hyper::upgrade::OnUpgrade;
use hyper_util::rt::TokioIo;
use once_cell::sync::Lazy;
use smallvec::SmallVec;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;

use super::fly_accept_encoding;
use crate::LocalExecutor;
use crate::Options;
use crate::compressible::is_content_compressible;
use crate::extract_network_stream;
use crate::network_buffered_stream::NetworkStreamPrefixCheck;
use crate::request_body::HttpRequestBody;
use crate::request_properties::HttpConnectionProperties;
use crate::request_properties::HttpListenProperties;
use crate::request_properties::HttpPropertyExtractor;
use crate::response_body::Compression;
use crate::response_body::ResponseBytesInner;
use crate::service::HttpRecord;
use crate::service::HttpRecordResponse;
use crate::service::HttpRequestBodyAutocloser;
use crate::service::HttpServerState;
use crate::service::ServerCallback;
use crate::service::SignallingRc;
use crate::service::handle_request;
use crate::service::http_general_trace;
use crate::service::http_trace;

type Request = hyper::Request<Incoming>;

static USE_WRITEV: Lazy<bool> = Lazy::new(|| {
  let enable = std::env::var("DENO_USE_WRITEV").ok();

  if let Some(val) = enable {
    return !val.is_empty();
  }

  false
});

/// All HTTP/2 connections start with this byte string.
///
/// In HTTP/2, each endpoint is required to send a connection preface as a final confirmation
/// of the protocol in use and to establish the initial settings for the HTTP/2 connection. The
/// client and server each send a different connection preface.
///
/// The client connection preface starts with a sequence of 24 octets, which in hex notation is:
///
/// 0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
///
/// That is, the connection preface starts with the string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n). This sequence
/// MUST be followed by a SETTINGS frame (Section 6.5), which MAY be empty.
const HTTP2_PREFIX: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";

/// ALPN negotiation for "h2"
const TLS_ALPN_HTTP_2: &[u8] = b"h2";

/// ALPN negotiation for "http/1.1"
const TLS_ALPN_HTTP_11: &[u8] = b"http/1.1";

/// Name a trait for streams we can serve HTTP over.
trait HttpServeStream:
  tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static
{
}
impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static>
  HttpServeStream for S
{
}

#[repr(transparent)]
struct RcHttpRecord(Rc<HttpRecord>);

// Register the [`HttpRecord`] as an external.
external!(RcHttpRecord, "http record");

/// Construct Rc<HttpRecord> from raw external pointer, consuming
/// refcount. You must make sure the external is deleted on the JS side.
macro_rules! take_external {
  ($external:expr, $args:tt) => {{
    let ptr = ExternalPointer::<RcHttpRecord>::from_raw($external);
    let record = ptr.unsafely_take().0;
    http_trace!(record, $args);
    record
  }};
}

/// Clone Rc<HttpRecord> from raw external pointer.
macro_rules! clone_external {
  ($external:expr, $args:tt) => {{
    let ptr = ExternalPointer::<RcHttpRecord>::from_raw($external);
    ptr.unsafely_deref().0.clone()
  }};
}

/// Try to clone Rc<HttpRecord> from raw external pointer.
/// Returns None if the pointer has already been consumed by take_external.
macro_rules! try_clone_external {
  ($external:expr) => {{
    let ptr = $external;
    if ptr.is_null()
      || ptr.align_offset(std::mem::align_of::<usize>()) != 0
      || std::ptr::read::<usize>(ptr as _)
        != <RcHttpRecord as deno_core::Externalizable>::external_marker()
    {
      None
    } else {
      let ptr = ExternalPointer::<RcHttpRecord>::from_raw(ptr);
      Some(ptr.unsafely_deref().0.clone())
    }
  }};
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum HttpNextError {
  #[class(inherit)]
  #[error(transparent)]
  Resource(#[from] deno_core::error::ResourceError),
  #[class(inherit)]
  #[error("{0}")]
  Io(#[from] io::Error),
  #[class("Http")]
  #[error("{0}")]
  Hyper(#[from] hyper::Error),
  #[class(inherit)]
  #[error(transparent)]
  JoinError(
    #[from]
    #[inherit]
    tokio::task::JoinError,
  ),
  #[class(inherit)]
  #[error(transparent)]
  Canceled(
    #[from]
    #[inherit]
    deno_core::Canceled,
  ),
  #[class(generic)]
  #[error(transparent)]
  UpgradeUnavailable(#[from] crate::service::UpgradeUnavailableError),
  #[class(inherit)]
  #[error("{0}")]
  Other(
    #[from]
    #[inherit]
    deno_error::JsErrorBox,
  ),
  #[class("Http")]
  #[error("invalid HTTP status line")]
  InvalidHttpStatusLine,
  #[class("Http")]
  #[error("raw upgrade failed")]
  RawUpgradeFailed,
  #[class(inherit)]
  #[error(transparent)]
  TakeNetworkStream(
    #[from]
    #[inherit]
    deno_net::raw::TakeNetworkStreamError,
  ),
}

#[op2(fast)]
#[smi]
pub fn op_http_upgrade_raw(
  state: &mut OpState,
  external: *const c_void,
) -> Result<ResourceId, HttpNextError> {
  // SAFETY: external is deleted before calling this op.
  let http = unsafe { take_external!(external, "op_http_upgrade_raw") };

  let upgrade = http.upgrade()?;

  let read = Rc::new(AsyncRefCell::new(None));
  let read_cell = AsyncRefCell::borrow_sync(read.clone()).unwrap();

  let write = UpgradeStreamWriteState::Parsing(
    BytesMut::with_capacity(b"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n".len()),
    http,
    upgrade,
    read_cell,
  );

  Ok(state.resource_table.add(UpgradeStream::new(read, write)))
}

/// Upgrade a CONNECT request by sending a 200 response, awaiting the
/// upgrade, and returning the stream resource with head bytes captured.
#[op2]
#[smi]
pub async fn op_http_upgrade_raw_connect(
  state: Rc<RefCell<OpState>>,
  external: *const c_void,
) -> Result<ResourceId, HttpNextError> {
  let (http, upgrade) = {
    // SAFETY: external is deleted before calling this op.
    let http =
      unsafe { take_external!(external, "op_http_upgrade_raw_connect") };
    let upgrade = http.upgrade()?;
    (http, upgrade)
  };

  // Send a 200 response to complete the CONNECT handshake.
  http.response_parts().status = StatusCode::OK;
  http.complete();

  let upgraded = upgrade.await?;
  let (stream, head_bytes) = extract_network_stream(upgraded);
  let (read_half, write_half) = stream.into_split();

  let resource =
    UpgradeStream::new_connected(read_half, write_half, head_bytes);
  Ok(state.borrow_mut().resource_table.add(resource))
}

/// Return the head bytes captured during a CONNECT upgrade.
/// The bytes are returned exactly once; subsequent calls return empty.
#[op2]
#[buffer]
pub fn op_http_upgrade_raw_get_head(
  state: &mut OpState,
  #[smi] rid: ResourceId,
) -> Result<Vec<u8>, HttpNextError> {
  let resource = state.resource_table.get::<UpgradeStream>(rid)?;
  let bytes = resource.head_bytes.borrow_mut().take();
  Ok(bytes.map(|b| b.to_vec()).unwrap_or_default())
}

#[op2]
#[smi]
pub async fn op_http_upgrade_websocket_next(
  state: Rc<RefCell<OpState>>,
  external: *const c_void,
) -> Result<ResourceId, HttpNextError> {
  let upgrade = {
    // SAFETY: op is called with external.
    let http =
      unsafe { clone_external!(external, "op_http_upgrade_websocket_next") };
    http.upgrade()?
  };

  let upgraded = upgrade.await?;
  let (stream, bytes) = extract_network_stream(upgraded);

  Ok(ws_create_server_stream(
    &mut state.borrow_mut(),
    stream,
    bytes,
  ))
}

/// Create a server WebSocket from a TCP stream resource (e.g. taken from a
/// node:http TCPWrap handle). This lets ext/node hand off the raw stream
/// without depending on deno_websocket directly.
#[op2(fast)]
#[smi]
pub fn op_http_ws_create_from_stream_resource(
  state: &mut OpState,
  #[smi] stream_rid: ResourceId,
  #[buffer] extra_bytes: &[u8],
) -> Result<ResourceId, HttpNextError> {
  let stream = deno_net::raw::take_network_stream_resource(
    &mut state.resource_table,
    stream_rid,
  )?;
  let read_buf = Bytes::copy_from_slice(extra_bytes);
  Ok(ws_create_server_stream(state, stream, read_buf))
}

#[op2(fast)]
pub fn op_http_set_promise_complete(external: *const c_void, status: u16) {
  let http =
    // SAFETY: external is deleted before calling this op.
    unsafe { take_external!(external, "op_http_set_promise_complete") };
  set_promise_complete(http, status);
}

fn set_promise_complete(http: Rc<HttpRecord>, status: u16) {
  // The Javascript code should never provide a status that is invalid here (see 23_response.js), so we
  // will quietly ignore invalid values.
  if let Ok(code) = StatusCode::from_u16(status) {
    http.response_parts().status = code;
    http.otel_info_set_status(status);
  }
  http.complete();
}

#[op2]
pub fn op_http_get_request_method_and_url<'scope, HTTP>(
  scope: &mut v8::PinScope<'scope, '_>,
  external: *const c_void,
) -> v8::Local<'scope, v8::Array>
where
  HTTP: HttpPropertyExtractor,
{
  let http =
    // SAFETY: op is called with external.
    unsafe { clone_external!(external, "op_http_get_request_method_and_url") };
  let request_info = http.request_info();
  let request_parts = http.request_parts();
  let request_properties = HTTP::request_properties(
    &request_info,
    &request_parts.uri,
    &request_parts.headers,
  );

  let method: v8::Local<v8::Value> = v8::String::new_from_utf8(
    scope,
    request_parts.method.as_str().as_bytes(),
    v8::NewStringType::Normal,
  )
  .unwrap()
  .into();

  let scheme: v8::Local<v8::Value> = match request_parts.uri.scheme_str() {
    Some(scheme) => v8::String::new_from_utf8(
      scope,
      scheme.as_bytes(),
      v8::NewStringType::Normal,
    )
    .unwrap()
    .into(),
    None => v8::undefined(scope).into(),
  };

  let authority: v8::Local<v8::Value> =
    if let Some(authority) = request_parts.uri.authority() {
      v8::String::new_from_utf8(
        scope,
        authority.as_str().as_ref(),
        v8::NewStringType::Normal,
      )
      .unwrap()
      .into()
    } else if let Some(authority) = request_properties.authority {
      v8::String::new_from_utf8(
        scope,
        authority.as_bytes(),
        v8::NewStringType::Normal,
      )
      .unwrap()
      .into()
    } else {
      v8::undefined(scope).into()
    };

  // Only extract the path part - we handle authority elsewhere
  let path = match request_parts.uri.path_and_query() {
    Some(path_and_query) => {
      let path = path_and_query.as_str();
      if matches!(path.as_bytes().first(), Some(b'/' | b'*')) {
        Cow::Borrowed(path)
      } else {
        Cow::Owned(format!("/{}", path))
      }
    }
    None => Cow::Borrowed(""),
  };

  let path: v8::Local<v8::Value> = v8::String::new_from_utf8(
    scope,
    path.as_bytes(),
    v8::NewStringType::Normal,
  )
  .unwrap()
  .into();

  let (peer_ip, peer_port) = match &*http.client_addr() {
    Some(client_addr) => {
      let addr: std::net::SocketAddr =
        client_addr.to_str().unwrap().parse().unwrap();
      (Rc::from(format!("{}", addr.ip())), Some(addr.port() as u32))
    }
    _ => (request_info.peer_address.clone(), request_info.peer_port),
  };

  let peer_ip: v8::Local<v8::Value> = v8::String::new_from_utf8(
    scope,
    peer_ip.as_bytes(),
    v8::NewStringType::Normal,
  )
  .unwrap()
  .into();

  let peer_port: v8::Local<v8::Value> = match peer_port {
    Some(port) => v8::Number::new(scope, port.into()).into(),
    None => v8::undefined(scope).into(),
  };

  let vec = [method, authority, path, peer_ip, peer_port, scheme];
  v8::Array::new_with_elements(scope, vec.as_slice())
}

#[op2]
pub fn op_http_get_request_header(
  external: *const c_void,
  #[string] name: String,
) -> Option<ByteString> {
  let http =
    // SAFETY: op is called with external.
    unsafe { clone_external!(external, "op_http_get_request_header") };
  let request_parts = http.request_parts();
  let value = request_parts.headers.get(name);
  value.map(|value| value.as_bytes().into())
}

#[op2]
pub fn op_http_get_request_headers<'scope>(
  scope: &mut v8::PinScope<'scope, '_>,
  external: *const c_void,
) -> v8::Local<'scope, v8::Array> {
  let http =
    // SAFETY: op is called with external.
    unsafe { clone_external!(external, "op_http_get_request_headers") };
  let headers = &http.request_parts().headers;
  // Two slots for each header key/value pair
  let mut vec: SmallVec<[v8::Local<v8::Value>; 32]> =
    SmallVec::with_capacity(headers.len() * 2);

  let mut cookies: Option<Vec<&[u8]>> = None;
  for (name, value) in headers {
    if name == COOKIE {
      if let Some(ref mut cookies) = cookies {
        cookies.push(value.as_bytes());
      } else {
        cookies = Some(vec![value.as_bytes()]);
      }
    } else {
      vec.push(
        v8::String::new_from_one_byte(
          scope,
          name.as_ref(),
          v8::NewStringType::Normal,
        )
        .unwrap()
        .into(),
      );
      vec.push(
        v8::String::new_from_one_byte(
          scope,
          value.as_bytes(),
          v8::NewStringType::Normal,
        )
        .unwrap()
        .into(),
      );
    }
  }

  // We treat cookies specially, because we don't want them to get them
  // mangled by the `Headers` object in JS. What we do is take all cookie
  // headers and concat them into a single cookie header, separated by
  // semicolons.
  // TODO(mmastrac): This should probably happen on the JS side on-demand
  if let Some(cookies) = cookies {
    let cookie_sep = "; ".as_bytes();

    vec.push(
      v8::String::new_external_onebyte_static(scope, COOKIE.as_ref())
        .unwrap()
        .into(),
    );
    vec.push(
      v8::String::new_from_one_byte(
        scope,
        cookies.join(cookie_sep).as_ref(),
        v8::NewStringType::Normal,
      )
      .unwrap()
      .into(),
    );
  }

  v8::Array::new_with_elements(scope, vec.as_slice())
}

/// Try to drain the entire request body without blocking.
/// Returns the bytes as a Uint8Array iff the whole body is
/// already buffered in hyper; returns `null` otherwise, in
/// which case the JS caller falls through to the streaming
/// `op_http_read_request_body` path. The body is left intact
/// on the `null` branch.
#[op2]
#[buffer]
pub fn op_http_try_take_full_request_body(
  external: *const c_void,
) -> Option<Vec<u8>> {
  let http =
    // SAFETY: op is called with external.
    unsafe { clone_external!(external, "op_http_try_take_full_request_body") };
  http.try_take_full_request_body()
}

#[op2(fast)]
#[smi]
pub fn op_http_read_request_body(
  state: Rc<RefCell<OpState>>,
  external: *const c_void,
) -> ResourceId {
  let http =
    // SAFETY: op is called with external.
    unsafe { clone_external!(external, "op_http_read_request_body") };
  let rid = match http.take_request_body() {
    Some(incoming) => {
      let body_resource = Rc::new(HttpRequestBody::new(incoming));
      state.borrow_mut().resource_table.add_rc(body_resource)
    }
    _ => {
      // This should not be possible, but rather than panicking we'll return an invalid
      // resource value to JavaScript.
      ResourceId::MAX
    }
  };
  http.put_resource(HttpRequestBodyAutocloser::new(rid, state.clone()));
  rid
}

#[op2(fast)]
pub fn op_http_set_response_header(
  external: *const c_void,
  #[string(onebyte)] name: Cow<'_, [u8]>,
  #[string(onebyte)] value: Cow<'_, [u8]>,
) {
  let http =
    // SAFETY: op is called with external.
    unsafe { clone_external!(external, "op_http_set_response_header") };
  let mut response_parts = http.response_parts();
  // These are valid latin-1 strings
  let name = HeaderName::from_bytes(&name).unwrap();
  let value = match value {
    Cow::Borrowed(bytes) => HeaderValue::from_bytes(bytes).unwrap(),
    // SAFETY: These are valid latin-1 strings
    Cow::Owned(bytes_vec) => unsafe {
      HeaderValue::from_maybe_shared_unchecked(bytes::Bytes::from(bytes_vec))
    },
  };
  response_parts.headers.append(name, value);
}

#[op2(fast)]
pub fn op_http_set_response_headers(
  scope: &mut v8::PinScope<'_, '_>,
  external: *const c_void,
  headers: v8::Local<v8::Array>,
) {
  let http =
    // SAFETY: op is called with external.
    unsafe { clone_external!(external, "op_http_set_response_headers") };
  // TODO(mmastrac): Invalid headers should be handled?
  let mut response_parts = http.response_parts();

  let len = headers.length();
  let header_len = len * 2;
  response_parts
    .headers
    .reserve(header_len.try_into().unwrap());

  for i in 0..len {
    let item = headers.get_index(scope, i).unwrap();
    let pair = v8::Local::<v8::Array>::try_from(item).unwrap();
    let name = pair.get_index(scope, 0).unwrap();
    let value = pair.get_index(scope, 1).unwrap();

    let v8_name = ByteString::from_v8(scope, name).unwrap();
    let v8_value = ByteString::from_v8(scope, value).unwrap();
    let header_name = HeaderName::from_bytes(&v8_name).unwrap();
    let header_value =
      // SAFETY: These are valid latin-1 strings
      unsafe { HeaderValue::from_maybe_shared_unchecked(v8_value) };
    response_parts.headers.append(header_name, header_value);
  }
}

#[op2]
pub fn op_http_set_response_trailers(
  external: *const c_void,
  #[scoped] trailers: Vec<(ByteString, ByteString)>,
) {
  let http =
    // SAFETY: op is called with external.
    unsafe { clone_external!(external, "op_http_set_response_trailers") };
  let mut trailer_map: HeaderMap = HeaderMap::with_capacity(trailers.len());
  for (name, value) in trailers {
    // These are valid latin-1 strings
    let name = HeaderName::from_bytes(&name).unwrap();
    // SAFETY: These are valid latin-1 strings
    let value = unsafe { HeaderValue::from_maybe_shared_unchecked(value) };
    trailer_map.append(name, value);
  }
  *http.trailers() = Some(trailer_map);
}

fn is_request_compressible(
  length: Option<usize>,
  headers: &HeaderMap,
) -> Compression {
  if let Some(length) = length {
    // By the time we add compression headers and Accept-Encoding, it probably doesn't make sense
    // to compress stuff that's smaller than this.
    if length < 64 {
      return Compression::None;
    }
  }

  let Some(accept_encoding) = headers.get(ACCEPT_ENCODING) else {
    return Compression::None;
  };

  match accept_encoding.to_str() {
    // Firefox and Chrome send this -- no need to parse
    Ok("gzip, deflate, br") => return Compression::Brotli,
    Ok("gzip, deflate, br, zstd") => return Compression::Brotli,
    Ok("gzip") => return Compression::GZip,
    Ok("br") => return Compression::Brotli,
    _ => (),
  }

  // Fall back to the expensive parser
  let accepted =
    fly_accept_encoding::encodings_iter_http_1(headers).filter(|r| {
      matches!(
        r,
        Ok((
          Some(Encoding::Identity | Encoding::Gzip | Encoding::Brotli),
          _
        ))
      )
    });
  match fly_accept_encoding::preferred(accepted) {
    Ok(Some(fly_accept_encoding::Encoding::Gzip)) => Compression::GZip,
    Ok(Some(fly_accept_encoding::Encoding::Brotli)) => Compression::Brotli,
    _ => Compression::None,
  }
}

fn is_response_compressible(headers: &HeaderMap) -> bool {
  if let Some(content_type) = headers.get(CONTENT_TYPE) {
    if !is_content_compressible(content_type) {
      return false;
    }
  } else {
    return false;
  }
  if headers.contains_key(CONTENT_ENCODING) {
    return false;
  }
  if headers.contains_key(CONTENT_RANGE) {
    return false;
  }
  if let Some(cache_control) = headers.get(CACHE_CONTROL)
    && let Ok(s) = std::str::from_utf8(cache_control.as_bytes())
    && let Some(no_transform) = crate::cache_control_has_no_transform(s)
    && no_transform
  {
    return false;
  }
  true
}

fn modify_compressibility_from_response(
  compression: Compression,
  headers: &mut HeaderMap,
) -> Compression {
  if compression == Compression::None {
    return Compression::None;
  }
  if !is_response_compressible(headers) {
    return Compression::None;
  }
  ensure_vary_accept_encoding(headers);
  let encoding = match compression {
    Compression::Brotli => "br",
    Compression::GZip => "gzip",
    _ => unreachable!(),
  };
  weaken_etag(headers);
  headers.remove(CONTENT_LENGTH);
  headers.insert(CONTENT_ENCODING, HeaderValue::from_static(encoding));
  compression
}

/// If the user provided a ETag header for uncompressed data, we need to ensure it is a
/// weak Etag header ("W/").
fn weaken_etag(hmap: &mut HeaderMap) {
  if let Some(etag) = hmap.get_mut(hyper::header::ETAG)
    && !etag.as_bytes().starts_with(b"W/")
  {
    let mut v = Vec::with_capacity(etag.as_bytes().len() + 2);
    v.extend(b"W/");
    v.extend(etag.as_bytes());
    *etag = v.try_into().unwrap();
  }
}

fn ensure_vary_accept_encoding(hmap: &mut HeaderMap) {
  if let Some(v) = hmap.get_mut(hyper::header::VARY)
    && let Ok(s) = v.to_str()
  {
    if !s.to_lowercase().contains("accept-encoding") {
      *v = format!("Accept-Encoding, {s}").try_into().unwrap()
    }
    return;
  }
  hmap.insert(
    hyper::header::VARY,
    HeaderValue::from_static("Accept-Encoding"),
  );
}

/// Sets the appropriate response body. Use `force_instantiate_body` if you need
/// to ensure that the response is cleaned up correctly (eg: for resources).
fn set_response(
  http: Rc<HttpRecord>,
  length: Option<usize>,
  status: u16,
  force_instantiate_body: bool,
  response_fn: impl FnOnce(Compression) -> ResponseBytesInner,
) {
  // The request may have been cancelled by this point and if so, there's no need for us to
  // do all of this work to send the response.
  if !http.cancelled() {
    let compression =
      is_request_compressible(length, &http.request_parts().headers);
    let mut response_headers =
      std::cell::RefMut::map(http.response_parts(), |this| &mut this.headers);
    let compression =
      modify_compressibility_from_response(compression, &mut response_headers);
    drop(response_headers);
    http.set_response_body(response_fn(compression));

    // The Javascript code should never provide a status that is invalid here (see 23_response.js), so we
    // will quietly ignore invalid values.
    if let Ok(code) = StatusCode::from_u16(status) {
      http.response_parts().status = code;
      http.otel_info_set_status(status);
    }
  } else if force_instantiate_body {
    response_fn(Compression::None).abort();
  }

  http.complete();
}

#[op2(fast)]
pub fn op_http_get_request_cancelled(external: *const c_void) -> bool {
  let http =
    // SAFETY: op is called with external.
    unsafe { clone_external!(external, "op_http_get_request_cancelled") };
  http.cancelled()
}

#[op2]
pub async fn op_http_request_on_cancel(external: *const c_void) -> bool {
  let http =
    // SAFETY: op is called with external.
    unsafe { clone_external!(external, "op_http_request_on_cancel") };
  let (tx, rx) = tokio::sync::oneshot::channel();

  http.on_cancel(tx);
  drop(http);

  rx.await.is_ok()
}

/// Returned promise resolves when body streaming finishes.
/// Call [`op_http_close_after_finish`] when done with the external.
#[op2]
pub async fn op_http_set_response_body_resource(
  state: Rc<RefCell<OpState>>,
  external: *const c_void,
  #[smi] stream_rid: ResourceId,
  auto_close: bool,
  status: u16,
) -> Result<bool, HttpNextError> {
  let http =
    // SAFETY: op is called with external.
    unsafe { clone_external!(external, "op_http_set_response_body_resource") };

  // IMPORTANT: We might end up requiring the OpState lock in set_response if we need to drop the request
  // body resource so we _cannot_ hold the OpState lock longer than necessary.

  // If the stream is auto_close, we will hold the last ref to it until the response is complete.
  // TODO(mmastrac): We should be using the same auto-close functionality rather than removing autoclose resources.
  // It's possible things could fail elsewhere if code expects the rid to continue existing after the response has been
  // returned.
  let resource = {
    let mut state = state.borrow_mut();
    if auto_close {
      state.resource_table.take_any(stream_rid)?
    } else {
      state.resource_table.get_any(stream_rid)?
    }
  };

  *http.needs_close_after_finish() = true;

  set_response(
    http.clone(),
    resource.size_hint().1.map(|s| s as usize),
    status,
    true,
    move |compression| {
      ResponseBytesInner::from_resource(compression, resource, auto_close)
    },
  );

  Ok(http.response_body_finished().await)
}

#[op2(fast)]
pub fn op_http_close_after_finish(external: *const c_void) {
  let http =
    // SAFETY: external is deleted before calling this op.
    unsafe { take_external!(external, "op_http_close_after_finish") };
  http.close_after_finish();
}

#[op2(fast)]
pub fn op_http_set_response_body_text(
  external: *const c_void,
  #[string] text: String,
  status: u16,
) {
  let http =
    // SAFETY: external is deleted before calling this op.
    unsafe { take_external!(external, "op_http_set_response_body_text") };
  if !text.is_empty() {
    set_response(http, Some(text.len()), status, false, |compression| {
      ResponseBytesInner::from_vec(compression, text.into_bytes())
    });
  } else {
    set_promise_complete(http, status);
  }
}

#[op2]
pub fn op_http_set_response_body_bytes(
  external: *const c_void,
  #[buffer] buffer: JsBuffer,
  status: u16,
) {
  let http =
    // SAFETY: external is deleted before calling this op.
    unsafe { take_external!(external, "op_http_set_response_body_bytes") };
  if !buffer.is_empty() {
    set_response(http, Some(buffer.len()), status, false, |compression| {
      ResponseBytesInner::from_bufview(compression, BufView::from(buffer))
    });
  } else {
    set_promise_complete(http, status);
  }
}

fn serve_http11_unconditional(
  io: impl HttpServeStream,
  svc: impl HttpService<Incoming, ResBody = HttpRecordResponse> + 'static,
  cancel: Rc<CancelHandle>,
  http1_builder_hook: Option<fn(http1::Builder) -> http1::Builder>,
) -> impl Future<Output = Result<(), hyper::Error>> + 'static {
  let mut builder = http1::Builder::new();
  builder.keep_alive(true).writev(*USE_WRITEV);

  if let Some(http1_builder_hook) = http1_builder_hook {
    builder = http1_builder_hook(builder);
  }

  let conn = builder
    .serve_connection(TokioIo::new(io), svc)
    .with_upgrades();

  async {
    match conn.or_abort(cancel).await {
      Err(mut conn) => {
        Pin::new(&mut conn).graceful_shutdown();
        conn.await
      }
      Ok(res) => res,
    }
  }
}

fn serve_http2_unconditional(
  io: impl HttpServeStream,
  svc: impl HttpService<Incoming, ResBody = HttpRecordResponse> + 'static,
  cancel: Rc<CancelHandle>,
  http2_builder_hook: Option<
    fn(http2::Builder<LocalExecutor>) -> http2::Builder<LocalExecutor>,
  >,
) -> impl Future<Output = Result<(), hyper::Error>> + 'static {
  let mut builder = http2::Builder::new(LocalExecutor);

  if let Some(http2_builder_hook) = http2_builder_hook {
    builder = http2_builder_hook(builder);
  }

  let conn = builder.serve_connection(TokioIo::new(io), svc);
  async {
    match conn.or_abort(cancel).await {
      Err(mut conn) => {
        Pin::new(&mut conn).graceful_shutdown();
        conn.await
      }
      Ok(res) => res,
    }
  }
}

async fn serve_http2_autodetect(
  io: impl HttpServeStream,
  svc: impl HttpService<Incoming, ResBody = HttpRecordResponse> + 'static,
  cancel: Rc<CancelHandle>,
  options: Options,
) -> Result<(), HttpNextError> {
  let prefix = NetworkStreamPrefixCheck::new(io, HTTP2_PREFIX);
  let (matches, io) =
    prefix.match_prefix().try_or_cancel(cancel.clone()).await?;
  if matches {
    serve_http2_unconditional(io, svc, cancel, options.http2_builder_hook)
      .await
      .map_err(HttpNextError::Hyper)
  } else {
    serve_http11_unconditional(io, svc, cancel, options.http1_builder_hook)
      .await
      .map_err(HttpNextError::Hyper)
  }
}

/// Dispatch a record to the JS callback registered for the server.
/// Wraps the record as an `ExternalPointer<RcHttpRecord>` and hands
/// the raw pointer to the JS callback. The JS side eventually calls
/// `take_external!` (via `op_http_set_response_*`) which consumes the
/// refcount.
fn dispatch_to_js(callback: &ServerCallback, record: Rc<HttpRecord>) {
  let ptr = ExternalPointer::new(RcHttpRecord(record)).into_raw();
  // SAFETY: callback's isolate ptr remains valid while the
  // HttpJoinHandle is alive (the HttpJoinHandle owns the
  // ServerCallback and is stored in the resource table; on close
  // the resource is dropped synchronously while the isolate is still
  // live).
  unsafe { callback.dispatch(ptr as *mut std::ffi::c_void) };
}

fn serve_https(
  mut io: TlsStream<TcpStream>,
  request_info: HttpConnectionProperties,
  lifetime: HttpLifetime,
  callback: Rc<ServerCallback>,
  options: Options,
) -> JoinHandle<Result<(), HttpNextError>> {
  let HttpLifetime {
    server_state,
    connection_cancel_handle,
    listen_cancel_handle,
  } = lifetime;

  let legacy_abort = !options.no_legacy_abort;
  let svc = service_fn(move |req: Request| {
    let callback = callback.clone();
    handle_request(
      req,
      request_info.clone(),
      server_state.clone(),
      move |record| dispatch_to_js(&callback, record),
      legacy_abort,
    )
  });
  spawn(
    async move {
      let handshake = io.handshake().await?;
      // If the client specifically negotiates a protocol, we will use it. If not, we'll auto-detect
      // based on the prefix bytes
      let handshake = handshake.alpn;
      if Some(TLS_ALPN_HTTP_2) == handshake.as_deref() {
        serve_http2_unconditional(
          io,
          svc,
          listen_cancel_handle,
          options.http2_builder_hook,
        )
        .await
        .map_err(HttpNextError::Hyper)
      } else if Some(TLS_ALPN_HTTP_11) == handshake.as_deref() {
        serve_http11_unconditional(
          io,
          svc,
          listen_cancel_handle,
          options.http1_builder_hook,
        )
        .await
        .map_err(HttpNextError::Hyper)
      } else {
        serve_http2_autodetect(io, svc, listen_cancel_handle, options).await
      }
    }
    .try_or_cancel(connection_cancel_handle),
  )
}

fn serve_http(
  io: impl HttpServeStream,
  request_info: HttpConnectionProperties,
  lifetime: HttpLifetime,
  callback: Rc<ServerCallback>,
  options: Options,
  wait_for_connection: bool,
) -> JoinHandle<Result<(), HttpNextError>> {
  let HttpLifetime {
    server_state,
    connection_cancel_handle,
    listen_cancel_handle,
  } = lifetime;

  let legacy_abort = !options.no_legacy_abort;
  let svc = service_fn(move |req: Request| {
    let callback = callback.clone();
    handle_request(
      req,
      request_info.clone(),
      server_state.clone(),
      move |record| dispatch_to_js(&callback, record),
      legacy_abort,
    )
  });
  let connection_cancel_handle_for_outer = connection_cancel_handle.clone();
  spawn(
    async move {
      let prefix = NetworkStreamPrefixCheck::new(io, HTTP2_PREFIX);
      let (matches, io) = prefix
        .match_prefix()
        .try_or_cancel(listen_cancel_handle.clone())
        .await?;
      let join_handle = if matches {
        spawn(
          async move {
            serve_http2_unconditional(
              io,
              svc,
              listen_cancel_handle,
              options.http2_builder_hook,
            )
            .await
            .map_err(HttpNextError::Hyper)
          }
          .try_or_cancel(connection_cancel_handle),
        )
      } else {
        spawn(
          async move {
            serve_http11_unconditional(
              io,
              svc,
              listen_cancel_handle,
              options.http1_builder_hook,
            )
            .await
            .map_err(HttpNextError::Hyper)
          }
          .try_or_cancel(connection_cancel_handle),
        )
      };

      if wait_for_connection {
        join_handle.await?
      } else {
        Ok(())
      }
    }
    .try_or_cancel(connection_cancel_handle_for_outer),
  )
}

fn serve_http_on<HTTP>(
  connection: HTTP::Connection,
  listen_properties: &HttpListenProperties,
  lifetime: HttpLifetime,
  callback: Rc<ServerCallback>,
  options: Options,
  wait_for_connection: bool,
) -> JoinHandle<Result<(), HttpNextError>>
where
  HTTP: HttpPropertyExtractor,
{
  let connection_properties: HttpConnectionProperties =
    HTTP::connection_properties(listen_properties, &connection);

  let network_stream = HTTP::to_network_stream_from_connection(connection);

  match network_stream {
    NetworkStream::Tcp(conn) => serve_http(
      conn,
      connection_properties,
      lifetime,
      callback,
      options,
      wait_for_connection,
    ),
    NetworkStream::Tls(conn) => {
      serve_https(conn, connection_properties, lifetime, callback, options)
    }
    #[cfg(unix)]
    NetworkStream::Unix(conn) => serve_http(
      conn,
      connection_properties,
      lifetime,
      callback,
      options,
      wait_for_connection,
    ),
    #[cfg(any(
      target_os = "android",
      target_os = "linux",
      target_os = "macos"
    ))]
    NetworkStream::Vsock(conn) => serve_http(
      conn,
      connection_properties,
      lifetime,
      callback,
      options,
      wait_for_connection,
    ),
    NetworkStream::Tunnel(conn) => serve_http(
      conn,
      connection_properties,
      lifetime,
      callback,
      options,
      wait_for_connection,
    ),
    #[cfg(windows)]
    NetworkStream::WindowsPipe(conn) => serve_http(
      conn,
      connection_properties,
      lifetime,
      callback,
      options,
      wait_for_connection,
    ),
  }
}

#[derive(Clone)]
struct HttpLifetime {
  connection_cancel_handle: Rc<CancelHandle>,
  listen_cancel_handle: Rc<CancelHandle>,
  server_state: SignallingRc<HttpServerState>,
}

struct HttpJoinHandle {
  join_handle: AsyncRefCell<Option<JoinHandle<Result<(), HttpNextError>>>>,
  connection_cancel_handle: Rc<CancelHandle>,
  listen_cancel_handle: Rc<CancelHandle>,
  server_state: SignallingRc<HttpServerState>,
}

impl HttpJoinHandle {
  fn new() -> Self {
    Self {
      join_handle: AsyncRefCell::new(None),
      connection_cancel_handle: CancelHandle::new_rc(),
      listen_cancel_handle: CancelHandle::new_rc(),
      server_state: HttpServerState::new(),
    }
  }

  fn lifetime(self: &Rc<Self>) -> HttpLifetime {
    HttpLifetime {
      connection_cancel_handle: self.connection_cancel_handle.clone(),
      listen_cancel_handle: self.listen_cancel_handle.clone(),
      server_state: self.server_state.clone(),
    }
  }

  fn connection_cancel_handle(self: &Rc<Self>) -> Rc<CancelHandle> {
    self.connection_cancel_handle.clone()
  }

  fn listen_cancel_handle(self: &Rc<Self>) -> Rc<CancelHandle> {
    self.listen_cancel_handle.clone()
  }
}

impl Resource for HttpJoinHandle {
  fn name(&self) -> Cow<'_, str> {
    "http".into()
  }

  fn close(self: Rc<Self>) {
    // During a close operation, we cancel everything
    self.connection_cancel_handle.cancel();
    self.listen_cancel_handle.cancel();
  }
}

impl Drop for HttpJoinHandle {
  fn drop(&mut self) {
    // In some cases we may be dropped without closing, so let's cancel everything on the way out
    self.connection_cancel_handle.cancel();
    self.listen_cancel_handle.cancel();
  }
}

#[op2]
pub fn op_http_serve<'scope, HTTP>(
  scope: &mut v8::PinScope<'scope, '_>,
  isolate: &mut v8::Isolate,
  state: Rc<RefCell<OpState>>,
  #[smi] listener_rid: ResourceId,
  callback: v8::Local<'scope, v8::Function>,
) -> Result<(ResourceId, &'static str, String, bool), HttpNextError>
where
  HTTP: HttpPropertyExtractor,
{
  let listener =
    HTTP::get_listener_for_rid(&mut state.borrow_mut(), listener_rid)?;

  let listen_properties = HTTP::listen_properties_from_listener(&listener)?;

  let resource: Rc<HttpJoinHandle> = Rc::new(HttpJoinHandle::new());
  let listen_cancel_clone = resource.listen_cancel_handle();

  let lifetime = resource.lifetime();
  let callback_global = v8::Global::new(scope, callback);
  let callback = Rc::new(ServerCallback::new(
    scope,
    isolate,
    callback_global,
    state.borrow().waker.clone(),
  ));

  let options = {
    let state = state.borrow();
    *state.borrow::<Options>()
  };

  let listen_properties_clone: HttpListenProperties = listen_properties.clone();
  let handle = spawn(async move {
    loop {
      let conn = HTTP::accept_connection_from_listener(&listener)
        .try_or_cancel(listen_cancel_clone.clone())
        .await?;
      serve_http_on::<HTTP>(
        conn,
        &listen_properties_clone,
        lifetime.clone(),
        callback.clone(),
        options,
        false,
      );
    }
    #[allow(unreachable_code, reason = "to avoid typing closure")]
    Ok::<_, HttpNextError>(())
  });

  // Set the handle after we start the future
  *RcRef::map(&resource, |this| &this.join_handle)
    .try_borrow_mut()
    .unwrap() = Some(handle);

  Ok((
    state.borrow_mut().resource_table.add_rc(resource),
    listen_properties.scheme,
    listen_properties.fallback_host,
    options.no_legacy_abort,
  ))
}

#[op2]
pub fn op_http_serve_on<'scope, HTTP>(
  scope: &mut v8::PinScope<'scope, '_>,
  isolate: &mut v8::Isolate,
  state: Rc<RefCell<OpState>>,
  #[smi] connection_rid: ResourceId,
  callback: v8::Local<'scope, v8::Function>,
) -> Result<(ResourceId, &'static str, String, bool), HttpNextError>
where
  HTTP: HttpPropertyExtractor,
{
  let connection =
    HTTP::get_connection_for_rid(&mut state.borrow_mut(), connection_rid)?;

  let listen_properties = HTTP::listen_properties_from_connection(&connection)?;

  let resource: Rc<HttpJoinHandle> = Rc::new(HttpJoinHandle::new());
  let callback_global = v8::Global::new(scope, callback);
  let callback = Rc::new(ServerCallback::new(
    scope,
    isolate,
    callback_global,
    state.borrow().waker.clone(),
  ));

  let options = {
    let state = state.borrow();
    *state.borrow::<Options>()
  };

  let handle = serve_http_on::<HTTP>(
    connection,
    &listen_properties,
    resource.lifetime(),
    callback,
    options,
    true,
  );

  // Set the handle after we start the future
  *RcRef::map(&resource, |this| &this.join_handle)
    .try_borrow_mut()
    .unwrap() = Some(handle);

  Ok((
    state.borrow_mut().resource_table.add_rc(resource),
    listen_properties.scheme,
    listen_properties.fallback_host,
    options.no_legacy_abort,
  ))
}

/// Wait for the server to finish accepting connections. Resolves
/// when the accept-loop spawned by `op_http_serve` has exited (either
/// from listener error or because the resource was closed).
#[op2]
pub async fn op_http_wait(
  state: Rc<RefCell<OpState>>,
  #[smi] rid: ResourceId,
) -> Result<(), HttpNextError> {
  let join_handle = state
    .borrow_mut()
    .resource_table
    .get::<HttpJoinHandle>(rid)?;

  let res = RcRef::map(join_handle, |this| &this.join_handle)
    .borrow_mut()
    .await
    .take()
    .unwrap()
    .await?;

  // Filter out shutdown (ENOTCONN) errors
  if let Err(err) = res
    && !is_normal_close(&err)
  {
    return Err(err);
  }

  Ok(())
}

fn is_normal_close(err: &(dyn std::error::Error + 'static)) -> bool {
  if let Some(err) = err.downcast_ref::<HttpNextError>() {
    if let HttpNextError::Io(err) = &err {
      return is_normal_close(err);
    }

    if let HttpNextError::Other(err) = &err {
      return is_normal_close(err);
    }

    return false;
  }

  if let Some(err) = err.downcast_ref::<deno_error::JsErrorBox>() {
    if let Some(err) = err.get_inner_ref() {
      return is_normal_close(err);
    }

    return false;
  }

  if let Some(err) = err.downcast_ref::<std::io::Error>() {
    if err.kind() == io::ErrorKind::NotConnected {
      return true;
    }

    if let Some(err) = err.get_ref() {
      return is_normal_close(err);
    }

    return false;
  }

  if let Some(err) = err.downcast_ref::<deno_net::tunnel::Error>() {
    if let deno_net::tunnel::Error::QuinnConnection(err) = err {
      return is_normal_close(err);
    }

    return false;
  }

  if let Some(err) =
    err.downcast_ref::<deno_net::tunnel::quinn::ConnectionError>()
  {
    if matches!(err, deno_net::tunnel::quinn::ConnectionError::LocallyClosed) {
      return true;
    }

    return false;
  }

  false
}

/// Cancels the HTTP handle.
#[op2(fast)]
pub fn op_http_cancel(
  state: &mut OpState,
  #[smi] rid: ResourceId,
  graceful: bool,
) -> Result<(), deno_core::error::ResourceError> {
  let join_handle = state.resource_table.get::<HttpJoinHandle>(rid)?;

  if graceful {
    // In a graceful shutdown, we close the listener and allow all the remaining connections to drain
    join_handle.listen_cancel_handle().cancel();
  } else {
    // In a forceful shutdown, we close everything
    join_handle.listen_cancel_handle().cancel();
    join_handle.connection_cancel_handle().cancel();
  }

  Ok(())
}

#[op2]
pub async fn op_http_close(
  state: Rc<RefCell<OpState>>,
  #[smi] rid: ResourceId,
  graceful: bool,
) -> Result<(), HttpNextError> {
  let join_handle = state
    .borrow_mut()
    .resource_table
    .take::<HttpJoinHandle>(rid)?;

  if graceful {
    http_general_trace!("graceful shutdown");
    // In a graceful shutdown, we close the listener and allow all the remaining connections to drain
    join_handle.listen_cancel_handle().cancel();
    poll_fn(|cx| join_handle.server_state.poll_complete(cx)).await;
  } else {
    http_general_trace!("forceful shutdown");
    // In a forceful shutdown, we close everything
    join_handle.listen_cancel_handle().cancel();
    join_handle.connection_cancel_handle().cancel();
    // Give streaming responses a tick to close
    tokio::task::yield_now().await;
  }

  http_general_trace!("awaiting shutdown");

  let mut join_handle = RcRef::map(&join_handle, |this| &this.join_handle)
    .borrow_mut()
    .await;
  if let Some(join_handle) = join_handle.take() {
    join_handle.await??;
  }

  Ok(())
}

enum UpgradeStreamWriteState {
  Parsing(
    BytesMut,
    Rc<HttpRecord>,
    OnUpgrade,
    AsyncMut<Option<(NetworkStreamReadHalf, Bytes)>>,
  ),
  /// Used after a CONNECT upgrade where the 200 response was already sent
  /// by hyper. Consumes and discards the HTTP response the application
  /// writes (since it's redundant), then switches to Network mode.
  ConsumeResponse(BytesMut, NetworkStreamWriteHalf),
  Network(NetworkStreamWriteHalf),
  /// The upgrade was rejected with a non-101 status code.
  /// The response has been sent and the stream is now closed for writing.
  Rejected,
  Failed,
}

struct UpgradeStream {
  read: Rc<AsyncRefCell<Option<(NetworkStreamReadHalf, Bytes)>>>,
  write: AsyncRefCell<UpgradeStreamWriteState>,
  cancel_handle: CancelHandle,
  /// Head bytes extracted during a CONNECT upgrade, available via
  /// `op_http_upgrade_raw_get_head`.
  head_bytes: RefCell<Option<Bytes>>,
  /// Set to true when the upgrade was rejected with a non-101 status.
  /// When rejected, reads return EOF and writes are silently ignored.
  rejected: std::cell::Cell<bool>,
}

impl UpgradeStream {
  pub fn new(
    read: Rc<AsyncRefCell<Option<(NetworkStreamReadHalf, Bytes)>>>,
    write: UpgradeStreamWriteState,
  ) -> Self {
    Self {
      read,
      write: AsyncRefCell::new(write),
      cancel_handle: CancelHandle::new(),
      head_bytes: RefCell::new(None),
      rejected: std::cell::Cell::new(false),
    }
  }

  pub fn new_connected(
    read_half: NetworkStreamReadHalf,
    write_half: NetworkStreamWriteHalf,
    head_bytes: Bytes,
  ) -> Self {
    let read = Rc::new(AsyncRefCell::new(Some((read_half, Bytes::new()))));
    Self {
      read,
      write: AsyncRefCell::new(UpgradeStreamWriteState::ConsumeResponse(
        BytesMut::with_capacity(128),
        write_half,
      )),
      cancel_handle: CancelHandle::new(),
      head_bytes: RefCell::new(Some(head_bytes)),
      rejected: std::cell::Cell::new(false),
    }
  }

  async fn read(
    self: Rc<Self>,
    buf: &mut [u8],
  ) -> Result<usize, std::io::Error> {
    // If the upgrade was rejected, return EOF
    if self.rejected.get() {
      return Ok(0);
    }

    let cancel_handle = RcRef::map(self.clone(), |this| &this.cancel_handle);
    async {
      let read = RcRef::map(self, |this| &this.read);
      let mut read = read.borrow_mut().await;
      let Some(read) = &mut *read else {
        return Err(std::io::Error::other(HttpNextError::RawUpgradeFailed));
      };
      if !read.1.is_empty() {
        let n = read.1.len().min(buf.len());
        buf[0..n].copy_from_slice(&read.1.split_to(n));
        Ok(n)
      } else {
        Pin::new(&mut read.0).read(buf).await
      }
    }
    .try_or_cancel(cancel_handle)
    .await
  }

  async fn write(self: Rc<Self>, buf: &[u8]) -> Result<usize, std::io::Error> {
    let cancel_handle = RcRef::map(self.clone(), |this| &this.cancel_handle);
    let this = self.clone();
    async {
      let wr = RcRef::map(self, |this| &this.write);
      let mut wr = wr.borrow_mut().await;
      match std::mem::replace(&mut *wr, UpgradeStreamWriteState::Failed) {
        UpgradeStreamWriteState::Failed => {
          Err(std::io::Error::other(HttpNextError::RawUpgradeFailed))
        }
        UpgradeStreamWriteState::Rejected => {
          // The upgrade was rejected and the response was already sent.
          // Silently accept writes but don't do anything with them.
          *wr = UpgradeStreamWriteState::Rejected;
          Ok(buf.len())
        }
        UpgradeStreamWriteState::Parsing(
          mut bytes,
          http,
          on_upgrade,
          mut read_cell,
        ) => {
          let prev_len = bytes.len();
          bytes.extend_from_slice(buf);

          let mut headers = [httparse::EMPTY_HEADER; 16];
          let mut response = httparse::Response::new(&mut headers);
          match response.parse(&bytes) {
            Ok(httparse::Status::Partial) => {
              *wr = UpgradeStreamWriteState::Parsing(
                bytes, http, on_upgrade, read_cell,
              );
              Ok(buf.len())
            }
            Ok(httparse::Status::Complete(n)) => {
              let status_code = response.code.unwrap_or(0);

              // Accept 101 (WebSocket upgrade) and 200 (CONNECT tunnel)
              if status_code == StatusCode::SWITCHING_PROTOCOLS.as_u16()
                || status_code == StatusCode::OK.as_u16()
              {
                let status = StatusCode::from_u16(status_code)
                  .unwrap_or(StatusCode::SWITCHING_PROTOCOLS);
                http.otel_info_set_status(status.as_u16());
                http.response_parts().status = status;

                for header in response.headers {
                  http.response_parts().headers.append(
                    HeaderName::from_bytes(header.name.as_bytes())
                      .map_err(std::io::Error::other)?,
                    HeaderValue::from_bytes(header.value)
                      .map_err(std::io::Error::other)?,
                  );
                }

                http.complete();

                let upgraded =
                  on_upgrade.await.map_err(std::io::Error::other)?;
                let (stream, bytes) = extract_network_stream(upgraded);
                let (read, write) = stream.into_split();

                let _ = read_cell.insert((read, bytes));
                *wr = UpgradeStreamWriteState::Network(write);

                Ok(n - prev_len)
              } else {
                // Upgrade rejected - send the rejection response through hyper
                http.otel_info_set_status(status_code);
                http.response_parts().status =
                  StatusCode::from_u16(status_code)
                    .unwrap_or(StatusCode::BAD_REQUEST);

                for header in response.headers {
                  http.response_parts().headers.append(
                    HeaderName::from_bytes(header.name.as_bytes())
                      .map_err(std::io::Error::other)?,
                    HeaderValue::from_bytes(header.value)
                      .map_err(std::io::Error::other)?,
                  );
                }

                // Any data after the headers is the response body
                let body = bytes.split_off(n);
                if !body.is_empty() {
                  http.set_response_body(ResponseBytesInner::Bytes(
                    BufView::from(body.freeze()),
                  ));
                }

                http.complete();

                // Mark as rejected - no upgrade will happen
                *wr = UpgradeStreamWriteState::Rejected;
                this.rejected.set(true);
                // Drop the on_upgrade future since we're not upgrading
                drop(on_upgrade);

                Ok(buf.len())
              }
            }
            Err(e) => Err(std::io::Error::other(e)),
          }
        }
        UpgradeStreamWriteState::ConsumeResponse(mut bytes, mut stream) => {
          bytes.extend_from_slice(buf);

          let mut headers = [httparse::EMPTY_HEADER; 16];
          let mut response = httparse::Response::new(&mut headers);
          match response.parse(&bytes) {
            Ok(httparse::Status::Partial) => {
              *wr = UpgradeStreamWriteState::ConsumeResponse(bytes, stream);
              Ok(buf.len())
            }
            Ok(httparse::Status::Complete(n)) => {
              // Response consumed. Forward any trailing bytes after
              // the response headers to the network.
              let trailing = &bytes[n..];
              if !trailing.is_empty() {
                let mut written = 0;
                while written < trailing.len() {
                  written +=
                    Pin::new(&mut stream).write(&trailing[written..]).await?;
                }
              }
              let consumed_from_buf = n - (bytes.len() - buf.len());
              *wr = UpgradeStreamWriteState::Network(stream);
              Ok(consumed_from_buf)
            }
            Err(_) => {
              // Not an HTTP response — treat as raw data.
              // Write everything accumulated so far to the network.
              let all = bytes.freeze();
              let mut written = 0;
              while written < all.len() {
                written += Pin::new(&mut stream).write(&all[written..]).await?;
              }
              *wr = UpgradeStreamWriteState::Network(stream);
              Ok(buf.len())
            }
          }
        }
        UpgradeStreamWriteState::Network(mut stream) => {
          let r = Pin::new(&mut stream).write(buf).await;
          *wr = UpgradeStreamWriteState::Network(stream);
          r
        }
      }
    }
    .try_or_cancel(cancel_handle)
    .await
  }

  async fn write_vectored(
    self: Rc<Self>,
    buf1: &[u8],
    buf2: &[u8],
  ) -> Result<usize, std::io::Error> {
    let mut wr = RcRef::map(&self, |r| &r.write).borrow_mut().await;

    match &mut *wr {
      UpgradeStreamWriteState::Failed => {
        Err(std::io::Error::other(HttpNextError::RawUpgradeFailed))
      }
      UpgradeStreamWriteState::Rejected => {
        // The upgrade was rejected; silently accept writes
        Ok(buf1.len() + buf2.len())
      }
      UpgradeStreamWriteState::Parsing(..) => {
        drop(wr);
        self.write(if buf1.is_empty() { buf2 } else { buf1 }).await
      }
      UpgradeStreamWriteState::ConsumeResponse(..) => {
        self.write(if buf1.is_empty() { buf2 } else { buf1 }).await
      }
      UpgradeStreamWriteState::Network(stream) => {
        let bufs = [std::io::IoSlice::new(buf1), std::io::IoSlice::new(buf2)];
        stream.write_vectored(&bufs).await
      }
    }
  }
}

impl Resource for UpgradeStream {
  fn name(&self) -> Cow<'_, str> {
    "httpRawUpgradeStream".into()
  }

  deno_core::impl_readable_byob!();
  deno_core::impl_writable!();

  fn close(self: Rc<Self>) {
    self.cancel_handle.cancel();
  }
}

#[op2(fast)]
pub fn op_can_write_vectored(
  state: &mut OpState,
  #[smi] rid: ResourceId,
) -> bool {
  state.resource_table.get::<UpgradeStream>(rid).is_ok()
}

#[op2]
#[number]
pub async fn op_raw_write_vectored(
  state: Rc<RefCell<OpState>>,
  #[smi] rid: ResourceId,
  #[buffer] buf1: JsBuffer,
  #[buffer] buf2: JsBuffer,
) -> Result<usize, HttpNextError> {
  let resource: Rc<UpgradeStream> =
    state.borrow().resource_table.get::<UpgradeStream>(rid)?;
  let nwritten = resource.write_vectored(&buf1, &buf2).await?;
  Ok(nwritten)
}

#[op2(fast)]
pub fn op_http_metric_handle_otel_error(external: *const c_void) {
  // SAFETY: The external may have already been consumed by a take_external!
  // call. Gracefully skip if the pointer is no longer valid.
  let Some(http) = (unsafe { try_clone_external!(external) }) else {
    return;
  };

  http.otel_info_set_error("user");
}

#[op2(fast)]
pub fn op_http_copy_span_to_otel_info(
  external: *const c_void,
  #[cppgc] span: &deno_telemetry::OtelSpan,
) {
  // SAFETY: The external may have already been consumed by a take_external!
  // call (e.g. op_http_set_promise_complete). On some platforms (Windows),
  // the freed memory may be reused, causing the marker check to fail.
  // We gracefully skip the copy in that case rather than panicking.
  let Some(http) = (unsafe { try_clone_external!(external) }) else {
    return;
  };

  http.copy_span_to_otel_info(span);
}
