///|
#external
pub type HttpRequestInternal

///|
#external
pub type HttpResponseInternal

///|
#borrow(self)
pub extern "js" fn HttpResponseInternal::url(
  self : HttpResponseInternal,
) -> String = "(s) => s.url"

///|
#borrow(self)
pub extern "js" fn HttpRequestInternal::url(
  self : HttpRequestInternal,
) -> String = "(s) => s.url"

///|
#borrow(self)
pub extern "js" fn HttpRequestInternal::req_method(
  self : HttpRequestInternal,
) -> String = "(s) => s.method"

///|
#borrow(self, event)
#owned(handler)
extern "js" fn HttpRequestInternal::on(
  self : HttpRequestInternal,
  event : String,
  handler : (@js.Value) -> Unit,
) -> Unit = "(s, event, handler) => s.on(event, handler)"

///|
#borrow(data)
extern "js" fn node_body_chunk_to_bytes(data : @js.Value) -> Bytes =
  #| (data) => {
  #|   if (data instanceof Uint8Array) {
  #|     return new Uint8Array(data);
  #|   }
  #|   return new TextEncoder().encode(String(data));
  #| }

///|
#borrow(self)
extern "js" fn HttpRequestInternal::headers(
  self : HttpRequestInternal,
) -> @js.Object = "(s) => s.headers"

///|
// extern "js" fn HttpResponseInternal::status_code(
//   self : HttpResponseInternal,
// ) -> Int = "(s) => s.statusCode"

///|
#borrow(obj, key, value)
extern "js" fn set_js_property(
  obj : @js.Value,
  key : String,
  value : @js.Value,
) -> Unit = "(obj, key, value) => obj[key] = value"

///|
#borrow(arr)
extern "js" fn array_to_js(arr : Array[String]) -> @js.Value = "(arr) => arr"

///|
#borrow(self, data)
pub extern "js" fn HttpResponseInternal::end(
  self : HttpResponseInternal,
  data : @js.Value,
) -> Unit = "(s, data) => s.end(data)"

///|
#borrow(self, headers)
extern "js" fn HttpResponseInternal::write_head(
  self : HttpResponseInternal,
  statusCode : Int,
  headers : @js.Value,
) -> Unit = "(s, statusCode, headers) => s.writeHead(statusCode, headers)"

///|
type NodeServerInternal

///|
#owned(handler)
#module("node:http")
extern "js" fn create_server(
  handler : (HttpRequestInternal, HttpResponseInternal, () -> Unit) -> Unit,
) -> NodeServerInternal = "createServer"

///|
#borrow(server, address)
#owned(accept_key)
pub extern "js" fn start_server(
  server : NodeServerInternal,
  address : String,
  accept_key : (String) -> String,
) -> Unit =
  #|(server, address, acceptKey) => { 
  #|    function parseAddress(address) {
  #|      const text = String(address || '');
  #|      if (text.startsWith('[')) {
  #|        const end = text.lastIndexOf(']:');
  #|        if (end >= 0) {
  #|          return { host: text.slice(1, end), port: Number(text.slice(end + 2)) };
  #|        }
  #|      }
  #|      const idx = text.lastIndexOf(':');
  #|      if (idx < 0) return { host: '0.0.0.0', port: Number(text) };
  #|      const host = text.slice(0, idx) || '0.0.0.0';
  #|      return { host, port: Number(text.slice(idx + 1)) };
  #|    }
  #|    const listenAddr = parseAddress(address);
  #|    const port = listenAddr.port;
  #|    if (!Number.isInteger(port) || port < 0 || port > 65535) {
  #|      throw new Error(`Invalid listen address: ${address}`);
  #|    }
  #|    function start(server, port) {
  #|      const clientsById = new Map();
  #|    
  #|      // 供 FFI 调用的发送函数
  #|      function sendText(socket, str) {
  #|        const payload = Buffer.from(String(str), 'utf8');
  #|        sendFrame(socket, payload, 0x1);
  #|      }
  #|
  #|      function sendBinary(socket, bytes) {
  #|        const payload = Buffer.from(bytes);
  #|        sendFrame(socket, payload, 0x2);
  #|      }
  #|
  #|      function sendPong(socket) {
  #|        const payload = Buffer.alloc(0);
  #|        sendFrame(socket, payload, 0xA);
  #|      }
  #|
  #|      function sendFrame(socket, payload, opcode) {
  #|        const len = payload.length;
  #|        let header;
  #|        if (len < 126) {
  #|          header = Buffer.alloc(2);
  #|          header[0] = 0x80 | opcode; // FIN + opcode
  #|          header[1] = len;
  #|        } else if (len < 65536) {
  #|          header = Buffer.alloc(4);
  #|          header[0] = 0x80 | opcode;
  #|          header[1] = 126;
  #|          header.writeUInt16BE(len, 2);
  #|        } else {
  #|          header = Buffer.alloc(10);
  #|          header[0] = 0x80 | opcode;
  #|          header[1] = 127;
  #|          header.writeBigUInt64BE(BigInt(len), 2);
  #|        }
  #|        try {
  #|          socket.write(Buffer.concat([header, payload]));
  #|        } catch (_) {}
  #|      }
  #|    
  #|      function parseFrame(buf) {
  #|        if (!Buffer.isBuffer(buf) || buf.length < 2) return null;
  #|        const fin = (buf[0] & 0x80) !== 0;
  #|        const opcode = buf[0] & 0x0f;
  #|        const masked = (buf[1] & 0x80) !== 0;
  #|        let len = buf[1] & 0x7f;
  #|        let offset = 2;
  #|        if (len === 126) {
  #|          if (buf.length < 4) return null;
  #|          len = buf.readUInt16BE(2);
  #|          offset = 4;
  #|        } else if (len === 127) {
  #|          if (buf.length < 10) return null;
  #|          const big = buf.readBigUInt64BE(2);
  #|          len = Number(big);
  #|          offset = 10;
  #|        }
  #|        if (masked) {
  #|          if (buf.length < offset + 4 + len) return null;
  #|          const mask = buf.slice(offset, offset + 4);
  #|          const data = buf.slice(offset + 4, offset + 4 + len);
  #|          const out = Buffer.alloc(len);
  #|          for (let i = 0; i < len; i++) out[i] = data[i] ^ mask[i % 4];
  #|          return { fin, opcode, data: out };
  #|        } else {
  #|          if (buf.length < offset + len) return null;
  #|          const data = buf.slice(offset, offset + len);
  #|          return { fin, opcode, data };
  #|        }
  #|      }
  #|    
  #|      // 导出四个 FFI 函数,供 MoonBit 调用
  #|      if (!globalThis.ws_port_bindings) globalThis.ws_port_bindings = new Map();
  #|      globalThis.ws_port_bindings.set(port, {
  #|        send: (id, msg) => {
  #|          const s = clientsById.get(id);
  #|          if (s) sendText(s, msg);
  #|        },
  #|        sendBytes: (id, bytes) => {
  #|          const s = clientsById.get(id);
  #|          if (s) sendBinary(s, bytes);
  #|        },
  #|        pong: (id) => {
  #|          const s = clientsById.get(id);
  #|          if (s) sendPong(s);
  #|        }
  #|      });
  #|    
  #|      server.on('upgrade', (req, socket, head) => {
  #|        const upgrade = (req.headers['upgrade'] || req.headers['Upgrade'] || '').toString();
  #|        if (upgrade !== 'websocket' && upgrade !== 'WebSocket') {
  #|          try { socket.destroy(); } catch (_) {}
  #|          return;
  #|        }
  #|        const key = req.headers['sec-websocket-key'] || req.headers['Sec-WebSocket-Key'];
  #|        if (!key) { try { socket.destroy(); } catch (_) {} return; }
  #|        const accept = acceptKey(key.toString());
  #|        const headers = [
  #|          'HTTP/1.1 101 Switching Protocols',
  #|          'Upgrade: websocket',
  #|          'Connection: Upgrade',
  #|          'Sec-WebSocket-Accept: ' + accept,
  #|          '\r\n'
  #|        ];
  #|        try { socket.write(headers.join('\r\n')); } catch (_) {
  #|          try { socket.destroy(); } catch (_) {} return;
  #|        }
  #|    
  #|        // 生成唯一连接 ID,注册到全局映射
  #|        const connectionId = crypto.randomUUID();
  #|        clientsById.set(connectionId, socket);
  #|    
  #|        // 派发 open 事件到 MoonBit
  #|        // console.log('[ws-bridge] emit open', port, connectionId);
  #|        if (typeof globalThis.__ws_emit_port === 'function') {
  #|          globalThis.__ws_emit_port('open', port, connectionId, '');
  #|        } else {
  #|          // console.log('[ws-bridge] __ws_emit not found');
  #|        }
  #|    
  #|        socket.on('data', (buf) => {
  #|          const frame = parseFrame(buf);
  #|          if (!frame) return;
  #|          // Close frame
  #|          if (frame.opcode === 0x8) {
  #|            try { socket.end(); } catch (_) {}
  #|            if (clientsById.delete(connectionId)) {
  #|              if (typeof globalThis.__ws_emit_port === 'function') {
  #|                globalThis.__ws_emit_port('close', port, connectionId, Buffer.alloc(0));
  #|              }
  #|          }
  #|         return;
  #|       }
  #|          // Ping -> Pong
  #|          if (frame.opcode === 0x9) {
  #|            // Emit ping event
  #|            if (typeof globalThis.__ws_emit_port === 'function') {
  #|              globalThis.__ws_emit_port('ping', port, connectionId, Buffer.alloc(0));
  #|            }
  #|            return;
  #|          }
  #|          // Text
  #|          if (frame.opcode === 0x1) {
  #|            const msg = frame.data || Buffer.alloc(0);
  #|            if (typeof globalThis.__ws_emit_port === 'function') {
  #|              globalThis.__ws_emit_port('message', port, connectionId, msg);
  #|            }
  #|          }
  #|          // Binary
  #|          if (frame.opcode === 0x2) {
  #|            const msg = frame.data || Buffer.alloc(0);
  #|            if (typeof globalThis.__ws_emit_port === 'function') {
  #|              globalThis.__ws_emit_port('binary', port, connectionId, msg);
  #|            }
  #|          }
  #|        });
  #|    
  #|        socket.on('close', () => {
  #|          if (clientsById.delete(connectionId)) {
  #|            if (typeof globalThis.__ws_emit_port === 'function') {
  #|              globalThis.__ws_emit_port('close', port, connectionId, Buffer.alloc(0));
  #|            }
  #|          }
  #|        });
  #|        socket.on('error', () => {
  #|          clientsById.delete(connectionId);
  #|          try { socket.destroy(); } catch (_) {}
  #|        });
  #|      });
  #|    
  #|      // console.log('[ws-bridge] WebSocket upgrade handler attached for port', port);
  #|    };
  #|    start(server, port); server.listen(port, listenAddr.host, () => {});
  #|}

///|
fn listen_port(address : String) -> Int {
  match address.rev_split_once(":") {
    Some((_, port_text)) => @string.parse_int(port_text) catch { _ => 0 }
    None => @string.parse_int(address) catch { _ => 0 }
  }
}

///|
pub fn listen_ffi(mocket : Mocket, address : String) -> Unit {
  let port = listen_port(address)
  let server = create_server(fn(req, res, _) {
    // 优化:简化 headers 转换逻辑
    let string_headers : Map[StringView, StringView] = {}
    guard (try? req.headers().to_value().to_json()) is Ok(Object(headers)) else {
      res.write_head(400, @js.Object::new().to_value())
      res.end(@js.Value::cast_from("Invalid headers"))
      return
    }

    // 批量转换 headers,减少单个处理的开销
    headers.each(fn(key, value) {
      if value is String(v) {
        string_headers.set(key, v.to_string_view())
      }
    })
    let (params, handler) = match
      mocket.find_route(req.req_method(), req.url()) {
      Some((h, p)) => (p, h)
      _ => ({}, handle_not_found())
    }
    let event = {
      req: {
        http_method: req.req_method(),
        url: req.url(),
        headers: string_headers,
        raw_body: b"",
      },
      res: HttpResponse::new(OK),
      params,
    }
    async_run(() => {
      // 如果是 post,先等待 data 事件
      if event.req.http_method == "POST" {
        let buffer = Buffer()
        (try? suspend(fn(res, _) {
          req.on("data", data => {
            buffer.write_bytes(node_body_chunk_to_bytes(data))
          })
          req.on("end", _ => res(()))
        }))
        |> ignore
        event.req.raw_body = buffer.to_bytes()
      }

      // 执行中间件链和处理器
      let responder = mocket.execute_middlewares(event, handler)
      // let boundary = "----------------moonbit-" + port.to_string()
      responder.options(event.res)
      res.write_head(
        event.res.status_code.to_int(),
        {
          let headers_obj = (try? @js.Value::from_json(
            event.res.headers.to_json(),
          )).or(@js.Object::new().to_value())
          if !event.res.cookies.is_empty() {
            let cookies = event.res.cookies
              .values()
              .map(fn(c) { c.to_string() })
              .to_array()
            set_js_property(headers_obj, "Set-Cookie", array_to_js(cookies))
          }
          headers_obj
        },
      )
      let buf = Buffer()
      responder.output(buf)
      event.res.raw_body = buf.to_bytes()
      res.end(@js.Value::cast_from(event.res.raw_body))
    })
  })
  start_server(server, address, websocket_accept_key)
  register_ws_handler(mocket, port)
  __ws_emit_js_export(__ws_emit_js_port)
  __ws_state_js_export(
    __ws_subscribe_js, __ws_unsubscribe_js, __ws_get_members_js,
  )
  __get_port_by_connection_js_export(__get_port_by_connection_js)
}

///|
pub fn serve_ffi(mocket : Mocket, port~ : Int) -> Unit {
  listen_ffi(mocket, "0.0.0.0:\{port}")
}

///|
fn websocket_accept_key(key : String) -> String {
  let guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  (key + guid)
  |> @utf8.encode
  |> @crypto.sha1
  |> Bytes::from_array
  |> @base64.encode
}

///|
/// 全局 handler 映射:port -> WebSocketHandler
let ws_handler_map : Map[Int, WebSocketHandler] = Map([])

///|
let ws_mocket_map : Map[Int, Mocket] = Map([])

///|
extern "js" fn _init_bindings_map() -> @js.Value = "() => { if (!globalThis.ws_port_bindings) globalThis.ws_port_bindings = new Map(); return globalThis.ws_port_bindings; }"

///|
/// 在 serve_ffi 里注册 WS handler(只取第一个静态路由)
pub fn register_ws_handler(mocket : Mocket, port : Int) -> Unit {
  let mut done = false
  mocket.ws_static_routes.each(fn(_, handler) {
    if !done {
      ws_handler_map.set(port, handler)
      ws_mocket_map.set(port, mocket)
      ignore(_init_bindings_map())
      done = true
    }
  })
}

///|
/// 供 JS 调用的 MoonBit 事件入口:捕获异常,避免抛回 JS
pub fn __ws_emit_js_port(
  event_type : String,
  port : Int,
  connection_id : String,
  payload : Bytes,
) -> Unit {
  let handler = match ws_handler_map.get(port) {
    Some(h) => h
    None => fn(_) {  }
  }
  let mocket = match ws_mocket_map.get(port) {
    Some(m) => m
    None => new()
  }
  match event_type {
    "open" => {
      mocket.ws_clients.set(connection_id, ())
      mocket.ws_client_port.set(connection_id, port)
    }
    "close" => {
      ignore(mocket.ws_clients.remove(connection_id))
      ignore(mocket.ws_client_port.remove(connection_id))
      mocket.ws_channels.each((_ch, set) => {
        if set.get(connection_id) is Some(_) {
          ignore(set.remove(connection_id))
        }
      })
    }
    _ => ()
  }
  let peer = WebSocketPeer::{ connection_id, subscribed_channels: [] }
  match event_type {
    "open" => handler(Open(peer))
    "message" => {
      let msg = @utf8.decode(payload) catch { _ => "" }
      handler(Message(peer, Text(msg)))
    }
    "binary" => handler(Message(peer, Binary(payload)))
    "ping" => handler(Message(peer, Ping))
    "close" => handler(Close(peer))
    _ => ()
  }
}

///|
/// 导出 MoonBit 函数到 JS 全局
#owned(cb)
pub extern "js" fn __ws_emit_js_export(
  cb : (String, Int, String, Bytes) -> Unit,
) -> Unit = "(cb) => { globalThis.__ws_emit_port = (type, port, id, payload) => { try { cb(type, port, id, payload); } catch (_) {} }; }"

///|
pub fn __ws_subscribe_js(connection_id : String, channel : String) -> Unit {
  let mut mocket = new()
  ws_mocket_map.each(fn(_, m) {
    if m.ws_clients.get(connection_id) is Some(_) {
      mocket = m
    }
  })
  match mocket.ws_channels.get(channel) {
    Some(set) => set.set(connection_id, ())
    None => {
      let set : Map[String, Unit] = {}
      set.set(connection_id, ())
      mocket.ws_channels.set(channel, set)
    }
  }
}

///|
pub fn __ws_unsubscribe_js(connection_id : String, channel : String) -> Unit {
  let mut mocket = new()
  ws_mocket_map.each(fn(_, m) {
    if m.ws_clients.get(connection_id) is Some(_) {
      mocket = m
    }
  })
  match mocket.ws_channels.get(channel) {
    Some(set) => ignore(set.remove(connection_id))
    None => ()
  }
}

///|
pub fn __ws_get_members_js(channel : String) -> Array[String] {
  let mut mocket = new()
  ws_mocket_map.each(fn(_, m) {
    match m.ws_channels.get(channel) {
      Some(_) => mocket = m
      None => ()
    }
  })
  match mocket.ws_channels.get(channel) {
    Some(set) => set.keys().collect()
    None => []
  }
}

///|
#owned(sub, unsub, get_members)
pub extern "js" fn __ws_state_js_export(
  sub : (String, String) -> Unit,
  unsub : (String, String) -> Unit,
  get_members : (String) -> Array[String],
) -> Unit = "(sub, unsub, get_members) => { globalThis.__ws_subscribe_js = (id, ch) => { try { sub(id, ch); } catch (_) {} }; globalThis.__ws_unsubscribe_js = (id, ch) => { try { unsub(id, ch); } catch (_) {} }; globalThis.__ws_get_members_js = (ch) => { try { return get_members(ch); } catch (_) { return []; } }; }"

///|
pub fn __get_port_by_connection_js(id : String) -> Int {
  let mut found = 0
  ws_mocket_map.each(fn(p, m) {
    if m.ws_clients.get(id) is Some(_) {
      found = p
    }
  })
  found
}

///|
#owned(cb)
pub extern "js" fn __get_port_by_connection_js_export(
  cb : (String) -> Int,
) -> Unit = "(cb) => { globalThis.__get_port_by_connection = (id) => { try { return cb(id); } catch (_) { return 0 } } }"

///|
/// 四个 FFI 包装,供 WebSocketPeer 调用
#borrow(id, msg)
pub extern "js" fn ws_send(id : String, msg : String) -> Unit = "(id, msg) => { const port = globalThis.__get_port_by_connection(id); const bindings = globalThis.ws_port_bindings && globalThis.ws_port_bindings.get(port); if (bindings && bindings.send) bindings.send(id, msg); }"

///|
#borrow(id, msg)
pub extern "js" fn ws_send_bytes(id : String, msg : Bytes) -> Unit = "(id, msg) => { const port = globalThis.__get_port_by_connection(id); const bindings = globalThis.ws_port_bindings && globalThis.ws_port_bindings.get(port); if (bindings && bindings.sendBytes) bindings.sendBytes(id, msg); }"

///|
#borrow(id)
pub extern "js" fn ws_pong(id : String) -> Unit = "(id) => { const port = globalThis.__get_port_by_connection(id); const bindings = globalThis.ws_port_bindings && globalThis.ws_port_bindings.get(port); if (bindings && bindings.pong) bindings.pong(id); }"

///|
#borrow(id, channel)
pub extern "js" fn ws_subscribe(id : String, channel : String) -> Unit = "(id, ch) => { if (globalThis.__ws_subscribe_js) globalThis.__ws_subscribe_js(id, ch); }"

///|
#borrow(id, channel)
pub extern "js" fn ws_unsubscribe(id : String, channel : String) -> Unit = "(id, ch) => { if (globalThis.__ws_unsubscribe_js) globalThis.__ws_unsubscribe_js(id, ch); }"

///|
#borrow(channel, msg)
pub extern "js" fn ws_publish(channel : String, msg : String) -> Unit = "(ch, msg) => { const ids = globalThis.__ws_get_members_js ? globalThis.__ws_get_members_js(ch) : []; const bindings = globalThis.ws_port_bindings && globalThis.ws_port_bindings.values().next().value; if (bindings && bindings.send) { for (const id of ids) { bindings.send(id, msg); } } }"

///|
pub fn encode_multipart(
  m : Map[String, MultipartFormValue],
  boundary : String,
) -> String {
  let sb = StringBuilder::new()
  m.each(fn(name, v) {
    sb.write_string("--" + boundary + "\r\n")
    sb.write_string("Content-Disposition: form-data; name=\"" + name + "\"")
    match v.filename {
      Some(fnm) => sb.write_string("; filename=\"" + fnm + "\"")
      None => ()
    }
    sb.write_string("\r\n")
    match v.content_type {
      Some(ct) => sb.write_string("Content-Type: " + ct + "\r\n")
      None => ()
    }
    sb.write_string("\r\n")
    sb.write_string(@utf8.decode(v.data) catch { _ => "" })
    sb.write_string("\r\n")
  })
  sb.write_string("--" + boundary + "--\r\n")
  sb.to_string()
}