import http from "node:http";
import net from "node:net";

export async function loadingProxy(port: number, destination: number) {
  let online = true;
  let held = true;
  const pending: Array<() => void> = [];
  const sockets = new Set<net.Socket>();
  let observed!: () => void;
  let rejected!: () => void;
  const shellRequested = new Promise<void>(resolve => { observed = resolve; });
  const retryRejected = new Promise<void>(resolve => { rejected = resolve; });
  const proxy = http.createServer(async (req, res) => {
    if (!online) { rejected(); res.writeHead(503); res.end("Temporarily unavailable"); return; }
    if (req.url?.startsWith("/api/orchestration/shell") && held) {
      observed();
      await new Promise<void>(resolve => pending.push(resolve));
    }
    if (res.destroyed) return;
    const upstream = http.request({ hostname: "127.0.0.1", port: destination, path: req.url, method: req.method, headers: req.headers }, response => {
      res.writeHead(response.statusCode ?? 502, response.headers);
      response.pipe(res);
    });
    upstream.on("error", () => res.destroy());
    req.pipe(upstream);
  });
  proxy.on("connection", socket => { sockets.add(socket); socket.on("close", () => sockets.delete(socket)); });
  proxy.on("upgrade", (req, socket, head) => {
    if (!online) { rejected(); socket.end("HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\n\r\n"); return; }
    const upstream = net.connect(destination, "127.0.0.1", () => {
      upstream.write(`${req.method} ${req.url} HTTP/${req.httpVersion}\r\n${Object.entries(req.headers).map(([k,v]) => `${k}: ${v}`).join("\r\n")}\r\n\r\n`);
      upstream.write(head);
      socket.pipe(upstream).pipe(socket);
    });
    upstream.on("error", () => socket.destroy());
    socket.on("close", () => upstream.destroy());
  });
  await new Promise<void>(resolve => proxy.listen(port, "0.0.0.0", resolve));
  return {
    shellRequested, retryRejected,
    disconnect() { online = false; for (const socket of sockets) socket.destroy(); },
    reconnect() { online = true; },
    release() { held = false; for (const resolve of pending.splice(0)) resolve(); },
    close() { held = false; for (const resolve of pending.splice(0)) resolve(); for (const socket of sockets) socket.destroy(); proxy.close(); },
  };
}
