export const MAX_PUBLIC_DIFF_BYTES = 512 * 1024;

const utf8Decoder = new TextDecoder("utf-8", { fatal: true });

export function projectPublicDiff(value) {
  if (typeof value !== "string" && !Buffer.isBuffer(value)) return {};
  const bytes = Buffer.isBuffer(value) ? value : Buffer.from(value, "utf8");
  if (bytes.length > MAX_PUBLIC_DIFF_BYTES) return { unavailableReason: "diff_too_large" };

  let text;
  try {
    text = utf8Decoder.decode(bytes);
  } catch {
    return { unavailableReason: "binary_file" };
  }
  if (looksBinary(bytes, text)) return { unavailableReason: "binary_file" };
  return { diff: text };
}

export function projectPublicDiffData(value) {
  if (Array.isArray(value)) return value.map(projectPublicDiffData);
  if (!value || typeof value !== "object" || Buffer.isBuffer(value)) return value;

  const projected = Object.fromEntries(Object.entries(value)
    .filter(([key]) => key !== "diff")
    .map(([key, item]) => [key, projectPublicDiffData(item)]));
  if (typeof value.diff !== "string" && !Buffer.isBuffer(value.diff)) return projected;

  const diff = projectPublicDiff(value.diff);
  if (typeof diff.diff === "string") projected.diff = diff.diff;
  else projected.unavailableReason ??= diff.unavailableReason;
  return projected;
}

function looksBinary(bytes, text) {
  if (text.includes("\0") || /^Binary files .+ differ\s*$/m.test(text)) return true;
  const sampleLength = Math.min(bytes.length, 8192);
  if (sampleLength === 0) return false;
  let controls = 0;
  for (let index = 0; index < sampleLength; index += 1) {
    const byte = bytes[index];
    if (byte === 0) return true;
    if ((byte < 7 || (byte > 13 && byte < 32)) && byte !== 9 && byte !== 10 && byte !== 13) controls += 1;
  }
  return controls / sampleLength > 0.05;
}
