const PUBLIC_ERROR = Symbol("public_verifier_error");
const DEFAULT_MAX_BODY_BYTES = 16 * 1024;

export async function readJson(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
  const contentType = String(req.headers?.["content-type"] ?? "").split(";", 1)[0].trim().toLowerCase();
  if (contentType !== "application/json") throw publicError(415, "unsupported_media_type");

  const chunks = [];
  let bytes = 0;
  for await (const chunk of req) {
    const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
    bytes += buffer.length;
    if (bytes > maxBytes) throw publicError(413, "request_too_large");
    chunks.push(buffer);
  }
  try {
    return JSON.parse(Buffer.concat(chunks).toString("utf8"));
  } catch {
    throw publicError(400, "invalid_json");
  }
}

export function sendJson(res, status, body) {
  res.writeHead(status, {
    "content-type": "application/json; charset=utf-8",
    "cache-control": "no-store",
    "x-content-type-options": "nosniff",
  });
  res.end(JSON.stringify(body));
}

export function publicError(status, code) {
  const error = new Error(code);
  error.status = status;
  error.code = code;
  error[PUBLIC_ERROR] = true;
  return error;
}

export function publicErrorResponse(error) {
  if (
    error?.[PUBLIC_ERROR] === true
    && Number.isInteger(error.status)
    && error.status >= 400
    && error.status <= 599
    && /^[a-z][a-z0-9_]*$/.test(error.code)
  ) {
    return { status: error.status, code: error.code };
  }
  return { status: 500, code: "internal_error" };
}
