// src/utils/binary.ts
import { open } from "node:fs/promises";
var BINARY_EXTENSIONS = new Set([
  ".zip",
  ".tar",
  ".gz",
  ".exe",
  ".dll",
  ".so",
  ".class",
  ".jar",
  ".war",
  ".7z",
  ".doc",
  ".docx",
  ".xls",
  ".xlsx",
  ".ppt",
  ".pptx",
  ".odt",
  ".ods",
  ".odp",
  ".bin",
  ".dat",
  ".obj",
  ".o",
  ".a",
  ".lib",
  ".wasm",
  ".pyc",
  ".pyo"
]);
async function isBinaryFile(filepath, fileSize) {
  const lastDot = filepath.lastIndexOf(".");
  if (lastDot !== -1) {
    const ext = filepath.slice(lastDot).toLowerCase();
    if (BINARY_EXTENSIONS.has(ext)) {
      return true;
    }
  }
  if (fileSize === 0) {
    return false;
  }
  const sampleSize = Math.min(4096, fileSize);
  const fileHandle = await open(filepath, "r");
  try {
    const buffer = new Uint8Array(sampleSize);
    const { bytesRead } = await fileHandle.read(buffer, 0, sampleSize, 0);
    const sample = buffer.subarray(0, bytesRead);
    if (sample.length === 0) {
      return false;
    }
    let nonPrintable = 0;
    for (const byte of sample) {
      if (byte === 0) {
        return true;
      }
      if ((byte < 9 || byte > 13 && byte < 32) && byte !== 27) {
        nonPrintable++;
      }
    }
    return nonPrintable / sample.length > 0.3;
  } finally {
    await fileHandle.close();
  }
}
// src/utils/file-type.ts
import { open as open2 } from "node:fs/promises";
var IMAGE_EXTENSIONS = new Map([
  [".png", { type: "image", mediaType: "image/png" }],
  [".jpg", { type: "image", mediaType: "image/jpeg" }],
  [".jpeg", { type: "image", mediaType: "image/jpeg" }],
  [".gif", { type: "image", mediaType: "image/gif" }],
  [".webp", { type: "image", mediaType: "image/webp" }]
]);
var PDF_TYPE = { type: "pdf", mediaType: "application/pdf" };
function detectFromExtension(filepath) {
  const lastDot = filepath.lastIndexOf(".");
  if (lastDot === -1)
    return;
  const ext = filepath.slice(lastDot).toLowerCase();
  if (ext === ".pdf")
    return PDF_TYPE;
  return IMAGE_EXTENSIONS.get(ext);
}
function detectFromMagicBytes(sample) {
  if (sample.length >= 8 && sample[0] === 137 && sample[1] === 80 && sample[2] === 78 && sample[3] === 71 && sample[4] === 13 && sample[5] === 10 && sample[6] === 26 && sample[7] === 10) {
    return { type: "image", mediaType: "image/png" };
  }
  if (sample.length >= 3 && sample[0] === 255 && sample[1] === 216 && sample[2] === 255) {
    return { type: "image", mediaType: "image/jpeg" };
  }
  if (sample.length >= 6 && sample[0] === 71 && sample[1] === 73 && sample[2] === 70 && sample[3] === 56 && (sample[4] === 55 || sample[4] === 57) && sample[5] === 97) {
    return { type: "image", mediaType: "image/gif" };
  }
  if (sample.length >= 12 && sample[0] === 82 && sample[1] === 73 && sample[2] === 70 && sample[3] === 70 && sample[8] === 87 && sample[9] === 69 && sample[10] === 66 && sample[11] === 80) {
    return { type: "image", mediaType: "image/webp" };
  }
  if (sample.length >= 5 && sample[0] === 37 && sample[1] === 80 && sample[2] === 68 && sample[3] === 70 && sample[4] === 45) {
    return PDF_TYPE;
  }
}
async function readSample(filepath, fileSize) {
  if (fileSize === 0)
    return new Uint8Array;
  const sampleSize = Math.min(16, fileSize);
  const fileHandle = await open2(filepath, "r");
  try {
    const buffer = new Uint8Array(sampleSize);
    const { bytesRead } = await fileHandle.read(buffer, 0, sampleSize, 0);
    return buffer.subarray(0, bytesRead);
  } finally {
    await fileHandle.close();
  }
}
async function detectFileType(filepath, fileSize) {
  const sample = await readSample(filepath, fileSize);
  const magicType = detectFromMagicBytes(sample);
  if (magicType)
    return magicType;
  const extensionType = detectFromExtension(filepath);
  if (extensionType)
    return extensionType;
  if (await isBinaryFile(filepath, fileSize)) {
    return { type: "unsupported-binary" };
  }
  return { type: "text" };
}
// src/utils/patch-parser.ts
function normalizeUnicode(s) {
  return s.replace(/[\u2018\u2019\u201A\u201B]/g, "'").replace(/[\u201C\u201D\u201E\u201F]/g, '"').replace(/[\u2010\u2011\u2012\u2013\u2014\u2015]/g, "-").replace(/\u2026/g, "...").replace(/\u00A0/g, " ");
}
function tryMatch(lines, pattern, startIndex, compare, eof) {
  if (eof) {
    const fromEnd = lines.length - pattern.length;
    if (fromEnd >= startIndex) {
      let matches = true;
      for (let j = 0;j < pattern.length; j++) {
        if (!compare(lines[fromEnd + j], pattern[j])) {
          matches = false;
          break;
        }
      }
      if (matches)
        return fromEnd;
    }
  }
  for (let i = startIndex;i <= lines.length - pattern.length; i++) {
    let matches = true;
    for (let j = 0;j < pattern.length; j++) {
      if (!compare(lines[i + j], pattern[j])) {
        matches = false;
        break;
      }
    }
    if (matches)
      return i;
  }
  return -1;
}
function seekSequence(haystack, needle, startIndex = 0, eof = false) {
  if (needle.length === 0)
    return -1;
  const exact = tryMatch(haystack, needle, startIndex, (a, b) => a === b, eof);
  if (exact !== -1)
    return exact;
  const rstrip = tryMatch(haystack, needle, startIndex, (a, b) => a.trimEnd() === b.trimEnd(), eof);
  if (rstrip !== -1)
    return rstrip;
  const trim = tryMatch(haystack, needle, startIndex, (a, b) => a.trim() === b.trim(), eof);
  if (trim !== -1)
    return trim;
  const normalized = tryMatch(haystack, needle, startIndex, (a, b) => normalizeUnicode(a.trim()) === normalizeUnicode(b.trim()), eof);
  return normalized;
}
function stripHeredoc(patchText) {
  const heredocRe = /^(?:cat\s+)?<<['"]?(\w+)['"]?\s*\n([\s\S]*?)\n\1\s*$/;
  const m = patchText.trim().match(heredocRe);
  return m ? m[2] ?? patchText : patchText;
}
function parsePatch(rawPatchText) {
  const patchText = stripHeredoc(rawPatchText);
  const lines = patchText.split(`
`);
  const beginIdx = lines.findIndex((l) => l.trim() === "*** Begin Patch");
  const endIdx = lines.findIndex((l) => l.trim() === "*** End Patch");
  if (beginIdx === -1 || endIdx === -1 || endIdx <= beginIdx) {
    throw new Error("Invalid patch format: patch must begin with '*** Begin Patch' and end with '*** End Patch'");
  }
  const body = lines.slice(beginIdx + 1, endIdx);
  const operations = [];
  let i = 0;
  while (i < body.length) {
    const line = body[i];
    if (line.startsWith("*** Add File: ")) {
      const filePath = line.slice("*** Add File: ".length).trim();
      i++;
      let content = "";
      while (i < body.length && !body[i].startsWith("*** ")) {
        const cl = body[i];
        if (cl.startsWith("+")) {
          content += `${cl.substring(1)}
`;
        }
        i++;
      }
      if (content.endsWith(`
`)) {
        content = content.slice(0, -1);
      }
      operations.push({
        type: "add",
        filePath,
        chunks: [],
        addContent: content
      });
    } else if (line.startsWith("*** Delete File: ")) {
      const filePath = line.slice("*** Delete File: ".length).trim();
      i++;
      while (i < body.length && !body[i].startsWith("*** ")) {
        i++;
      }
      operations.push({ type: "delete", filePath, chunks: [] });
    } else if (line.startsWith("*** Update File: ")) {
      const filePath = line.slice("*** Update File: ".length).trim();
      i++;
      let targetPath;
      if (i < body.length && body[i].startsWith("*** Move to: ")) {
        targetPath = body[i].slice("*** Move to: ".length).trim();
        i++;
      }
      const chunks = [];
      while (i < body.length && (body[i].startsWith("@@") || body[i] === "*** End of File" || !body[i].startsWith("*** "))) {
        if (body[i].startsWith("@@")) {
          const contextLine = body[i].substring(2).trim();
          i++;
          const oldLines = [];
          const newLines = [];
          let isEndOfFile = false;
          while (i < body.length && !body[i].startsWith("@@") && (body[i] === "*** End of File" || !body[i].startsWith("*** "))) {
            const cl = body[i];
            if (cl === "*** End of File") {
              isEndOfFile = true;
              i++;
              break;
            }
            const prefix = cl[0];
            const content = cl.slice(1);
            if (prefix === " ") {
              oldLines.push(content);
              newLines.push(content);
            } else if (prefix === "-") {
              oldLines.push(content);
            } else if (prefix === "+") {
              newLines.push(content);
            }
            i++;
          }
          chunks.push({
            oldLines,
            newLines,
            changeContext: contextLine || undefined,
            endOfFile: isEndOfFile
          });
        } else {
          i++;
        }
      }
      const op = {
        type: targetPath ? "move" : "update",
        filePath,
        chunks
      };
      if (targetPath)
        op.targetPath = targetPath;
      operations.push(op);
    } else {
      i++;
    }
  }
  return operations;
}
function computeReplacements(originalLines, chunks) {
  const replacements = [];
  let lineIndex = 0;
  for (const chunk of chunks) {
    if (chunk.changeContext) {
      const contextIdx = seekSequence(originalLines, [chunk.changeContext], lineIndex);
      if (contextIdx === -1) {
        throw new Error(`Failed to find context '${chunk.changeContext}' in file`);
      }
      lineIndex = contextIdx + 1;
    }
    if (chunk.oldLines.length === 0) {
      const insertionIdx = originalLines.length > 0 && originalLines[originalLines.length - 1] === "" ? originalLines.length - 1 : originalLines.length;
      replacements.push([insertionIdx, 0, chunk.newLines]);
      continue;
    }
    let pattern = chunk.oldLines;
    let newSlice = chunk.newLines;
    let found = seekSequence(originalLines, pattern, lineIndex, chunk.endOfFile);
    if (found === -1 && pattern.length > 0 && pattern[pattern.length - 1] === "") {
      pattern = pattern.slice(0, -1);
      if (newSlice.length > 0 && newSlice[newSlice.length - 1] === "") {
        newSlice = newSlice.slice(0, -1);
      }
      found = seekSequence(originalLines, pattern, lineIndex, chunk.endOfFile);
    }
    if (found !== -1) {
      replacements.push([found, pattern.length, newSlice]);
      lineIndex = found + pattern.length;
    } else {
      throw new Error(`Failed to find expected lines in file:
${chunk.oldLines.slice(0, 3).join(`
`)}`);
    }
  }
  replacements.sort((a, b) => a[0] - b[0]);
  return replacements;
}
function applyReplacements(lines, replacements) {
  const result = [...lines];
  for (let i = replacements.length - 1;i >= 0; i--) {
    const [startIdx, oldLen, newSegment] = replacements[i];
    result.splice(startIdx, oldLen, ...newSegment);
  }
  return result;
}
function applyUpdateChunks(content, chunks) {
  const hasCRLF = content.includes(`\r
`);
  const normalised = content.replace(/\r\n/g, `
`);
  const lines = normalised.split(`
`);
  if (lines.length > 0 && lines[lines.length - 1] === "") {
    lines.pop();
  }
  const replacements = computeReplacements(lines, chunks);
  const newLines = applyReplacements(lines, replacements);
  if (newLines.length === 0 || newLines[newLines.length - 1] !== "") {
    newLines.push("");
  }
  const result = newLines.join(`
`);
  return hasCRLF ? result.replace(/\n/g, `\r
`) : result;
}
async function validateHunks(ops, readFile) {
  for (const op of ops) {
    if (op.type === "update" || op.type === "move") {
      let content;
      try {
        content = await readFile(op.filePath);
      } catch {
        throw new Error(`apply_patch verification failed: Failed to read file to update: ${op.filePath}`);
      }
      try {
        applyUpdateChunks(content, op.chunks);
      } catch (err) {
        throw new Error(`apply_patch verification failed: ${err}`);
      }
    } else if (op.type === "delete") {
      try {
        await readFile(op.filePath);
      } catch {
        throw new Error(`apply_patch verification failed: Failed to read file to delete: ${op.filePath}`);
      }
    }
  }
}
// src/utils/truncate.ts
var MAX_LINES = 2000;
var MAX_BYTES = 50 * 1024;
function truncateOutput(output, opts) {
  const maxLines = opts?.maxLines ?? MAX_LINES;
  const maxBytes = opts?.maxBytes ?? MAX_BYTES;
  const byteLength = Buffer.byteLength(output, "utf-8");
  const lines = output.split(`
`);
  if (lines.length <= maxLines && byteLength <= maxBytes) {
    return output;
  }
  let result = "";
  let lineCount = 0;
  for (const line of lines) {
    if (lineCount >= maxLines)
      break;
    const next = lineCount === 0 ? line : `
${line}`;
    if (Buffer.byteLength(result + next, "utf-8") > maxBytes)
      break;
    result += next;
    lineCount++;
  }
  return `${result}

[output truncated — ${lines.length} lines / ${byteLength} bytes exceeded limit of ${maxLines} lines / ${maxBytes} bytes]`;
}
export {
  validateHunks,
  truncateOutput,
  stripHeredoc,
  parsePatch,
  isBinaryFile,
  detectFileType,
  applyUpdateChunks
};

//# debugId=7A1068C08232CFF664756E2164756E21
