const DEFAULT_CAPTURED_OUTPUT_BYTES = 256 * 1024;
const DEFAULT_JSON_RESPONSE_BYTES = 64 * 1024;

export type CapturedOutput = {
  text: string;
  truncated: boolean;
  totalBytes: number;
};

function normalizeByteLimit(maxBytes: number) {
  return Math.max(1, Math.trunc(maxBytes));
}

export function createCapturedOutputBuffer(maxBytes = DEFAULT_CAPTURED_OUTPUT_BYTES) {
  const limit = normalizeByteLimit(maxBytes);
  const chunks: Buffer[] = [];
  let bufferedBytes = 0;
  let totalBytes = 0;
  let truncated = false;

  return {
    append(chunk: Buffer | string | null | undefined) {
      if (chunk === null || chunk === undefined) return;
      const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
      if (buffer.length === 0) return;

      chunks.push(buffer);
      bufferedBytes += buffer.length;
      totalBytes += buffer.length;

      while (bufferedBytes > limit && chunks.length > 0) {
        const overflow = bufferedBytes - limit;
        const head = chunks[0]!;
        if (head.length <= overflow) {
          chunks.shift();
          bufferedBytes -= head.length;
          truncated = true;
          continue;
        }

        chunks[0] = head.subarray(overflow);
        bufferedBytes -= overflow;
        truncated = true;
      }
    },

    finish(): CapturedOutput {
      const body = Buffer.concat(chunks).toString("utf8");
      if (!truncated) {
        return {
          text: body,
          truncated,
          totalBytes,
        };
      }

      return {
        text: `[output truncated to last ${limit} bytes; total ${totalBytes} bytes]\n${body}`,
        truncated,
        totalBytes,
      };
    },
  };
}

export async function parseJsonResponseWithLimit<T>(
  response: Response,
  maxBytes = DEFAULT_JSON_RESPONSE_BYTES,
): Promise<T> {
  const limit = normalizeByteLimit(maxBytes);
  const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
  if (Number.isFinite(contentLength) && contentLength > limit) {
    throw new Error(`Response exceeds ${limit} bytes`);
  }

  if (!response.body) {
    throw new Error("Response has no body");
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let text = "";
  let totalBytes = 0;

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      totalBytes += value.byteLength;
      if (totalBytes > limit) {
        await reader.cancel("response too large");
        throw new Error(`Response exceeds ${limit} bytes`);
      }
      text += decoder.decode(value, { stream: true });
    }
    text += decoder.decode();
  } finally {
    reader.releaseLock();
  }

  return JSON.parse(text) as T;
}
