import "../chunk-PZ5AY32C.js";

// src/adapters/http.ts
function resolveAuth(auth) {
  if (!auth) return Promise.resolve(null);
  if (typeof auth === "string") return Promise.resolve(auth);
  return Promise.resolve(auth());
}
function httpDispatch(opts) {
  if (!opts.url && !opts.resolveUrl) {
    throw new Error("httpDispatch: pass exactly one of `url` or `resolveUrl`.");
  }
  if (opts.url && opts.resolveUrl) {
    throw new Error("httpDispatch: pass exactly one of `url` or `resolveUrl`, not both.");
  }
  const timeoutMs = opts.timeoutMs ?? 5 * 60 * 1e3;
  const maxRetries = opts.retries ?? 2;
  const f = opts.fetchImpl ?? ((...args) => fetch(...args));
  return async (scenario, ctx) => {
    const url = opts.url ?? opts.resolveUrl({ scenario, placement: ctx.placement, cellId: ctx.cellId });
    const authValue = await resolveAuth(opts.auth);
    const body = {
      scenario,
      cellId: ctx.cellId,
      rep: ctx.rep,
      generation: ctx.generation,
      seed: ctx.seed,
      placement: ctx.placement,
      cycleId: ctx.cycleId
    };
    let lastError;
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      const ourTimeout = AbortSignal.timeout(timeoutMs);
      const combinedSignal = AbortSignal.any([ctx.signal, ourTimeout]);
      try {
        const res = await f(url, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            ...authValue ? {
              Authorization: authValue.startsWith("Bearer ") ? authValue : `Bearer ${authValue}`
            } : {},
            ...opts.headers
          },
          body: JSON.stringify(body),
          signal: combinedSignal
        });
        if (!res.ok) {
          const retryable = res.status >= 500 || res.status === 408 || res.status === 429;
          if (!retryable || attempt === maxRetries) {
            const text = await res.text().catch(() => "");
            throw new Error(`httpDispatch ${url} failed (${res.status}): ${text.slice(0, 500)}`);
          }
          await sleep(2 ** attempt * 200 + Math.random() * 200);
          continue;
        }
        const parsed = await res.json();
        return parsed.artifact;
      } catch (err) {
        if (ctx.signal.aborted) throw err;
        lastError = err;
        if (attempt === maxRetries) throw err;
        await sleep(2 ** attempt * 200 + Math.random() * 200);
      }
    }
    throw lastError ?? new Error("httpDispatch exhausted retries");
  };
}
function sleep(ms) {
  return new Promise((resolve) => {
    const t = setTimeout(resolve, ms);
    if (typeof t.unref === "function")
      t.unref();
  });
}
async function runDispatchServer(opts) {
  if (opts.auth === void 0) {
    throw new Error(
      "runDispatchServer: 'auth' is required (pass a bearer-token string, or `auth: false` explicitly for a closed-network test deployment)."
    );
  }
  const path = opts.path ?? "/dispatch";
  const maxBytes = opts.maxBodyBytes ?? 10 * 1024 * 1024;
  const expectedAuth = typeof opts.auth === "string" ? `Bearer ${opts.auth.replace(/^Bearer\s+/, "")}` : null;
  const { createServer } = await import("http");
  const server = createServer(async (req, res) => {
    const start = Date.now();
    let cellId = "unknown";
    let success = false;
    let errCaught;
    try {
      if (req.method !== "POST" || req.url?.split("?")[0] !== path) {
        res.statusCode = 404;
        res.end("not found");
        return;
      }
      if (expectedAuth) {
        const got = req.headers.authorization;
        if (got !== expectedAuth) {
          res.statusCode = 401;
          res.end("unauthorized");
          return;
        }
      }
      const chunks = [];
      let totalBytes = 0;
      const aborter = new AbortController();
      req.on("close", () => {
        if (!res.writableEnded) aborter.abort();
      });
      for await (const chunk of req) {
        const buf = chunk;
        totalBytes += buf.length;
        if (totalBytes > maxBytes) {
          res.statusCode = 413;
          res.end("payload too large");
          return;
        }
        chunks.push(buf);
      }
      const body = JSON.parse(
        Buffer.concat(chunks).toString("utf8")
      );
      cellId = body.cellId;
      const ctx = opts.contextFactory ? await opts.contextFactory(body, aborter.signal) : {
        cellId: body.cellId,
        rep: body.rep,
        generation: body.generation,
        seed: body.seed,
        signal: aborter.signal,
        placement: body.placement,
        cycleId: body.cycleId,
        trace: NOOP_TRACE,
        artifacts: NOOP_ARTIFACTS,
        cost: NOOP_COST
      };
      const artifact = await opts.dispatch(body.scenario, ctx);
      const responseBody = { artifact };
      res.statusCode = 200;
      res.setHeader("content-type", "application/json");
      res.end(JSON.stringify(responseBody));
      success = true;
    } catch (err) {
      errCaught = err;
      if (err?.name === "AbortError") {
        res.statusCode = 499;
        res.end("client aborted");
        return;
      }
      res.statusCode = 500;
      res.setHeader("content-type", "application/json");
      res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
    } finally {
      opts.onRequest?.({
        cellId,
        durationMs: Date.now() - start,
        success,
        error: errCaught
      });
    }
  });
  await new Promise((resolve, reject) => {
    server.once("error", reject);
    server.listen(opts.port, opts.host ?? "0.0.0.0", () => resolve());
  });
  const addr = server.address();
  const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
  return {
    port: boundPort,
    close: () => new Promise((resolve, reject) => {
      server.close((err) => err ? reject(err) : resolve());
    })
  };
}
var NOOP_TRACE = {
  span: () => ({
    end: () => {
    },
    setAttribute: () => {
    },
    setStatus: () => {
    },
    recordException: () => {
    },
    addEvent: () => {
    }
  })
};
var NOOP_ARTIFACTS = {
  write: async () => void 0,
  read: async () => void 0,
  list: async () => []
};
var NOOP_COST = {
  record: () => {
  },
  total: () => 0
};
export {
  httpDispatch,
  runDispatchServer
};
//# sourceMappingURL=http.js.map