// src/git/diff-worker.ts
import { parentPort } from "worker_threads";

// src/git/diff.ts
import git from "isomorphic-git";
import fsNode from "fs";
import path from "path";

// node_modules/diff/libesm/diff/base.js
var Diff = class {
  diff(oldStr, newStr, options = {}) {
    let callback;
    if (typeof options === "function") {
      callback = options;
      options = {};
    } else if ("callback" in options) {
      callback = options.callback;
    }
    const oldString = this.castInput(oldStr, options);
    const newString = this.castInput(newStr, options);
    const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
    const newTokens = this.removeEmpty(this.tokenize(newString, options));
    return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
  }
  diffWithOptionsObj(oldTokens, newTokens, options, callback) {
    var _a;
    const done = (value) => {
      value = this.postProcess(value, options);
      if (callback) {
        setTimeout(function() {
          callback(value);
        }, 0);
        return void 0;
      } else {
        return value;
      }
    };
    const newLen = newTokens.length, oldLen = oldTokens.length;
    let editLength = 1;
    let maxEditLength = newLen + oldLen;
    if (options.maxEditLength != null) {
      maxEditLength = Math.min(maxEditLength, options.maxEditLength);
    }
    const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
    const abortAfterTimestamp = Date.now() + maxExecutionTime;
    const bestPath = [{ oldPos: -1, lastComponent: void 0 }];
    let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
    if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
      return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
    }
    let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
    const execEditLength = () => {
      for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
        let basePath;
        const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
        if (removePath) {
          bestPath[diagonalPath - 1] = void 0;
        }
        let canAdd = false;
        if (addPath) {
          const addPathNewPos = addPath.oldPos - diagonalPath;
          canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
        }
        const canRemove = removePath && removePath.oldPos + 1 < oldLen;
        if (!canAdd && !canRemove) {
          bestPath[diagonalPath] = void 0;
          continue;
        }
        if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) {
          basePath = this.addToPath(addPath, true, false, 0, options);
        } else {
          basePath = this.addToPath(removePath, false, true, 1, options);
        }
        newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
        if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
          return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
        } else {
          bestPath[diagonalPath] = basePath;
          if (basePath.oldPos + 1 >= oldLen) {
            maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
          }
          if (newPos + 1 >= newLen) {
            minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
          }
        }
      }
      editLength++;
    };
    if (callback) {
      (function exec() {
        setTimeout(function() {
          if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
            return callback(void 0);
          }
          if (!execEditLength()) {
            exec();
          }
        }, 0);
      })();
    } else {
      while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
        const ret = execEditLength();
        if (ret) {
          return ret;
        }
      }
    }
  }
  addToPath(path2, added, removed, oldPosInc, options) {
    const last = path2.lastComponent;
    if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
      return {
        oldPos: path2.oldPos + oldPosInc,
        lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
      };
    } else {
      return {
        oldPos: path2.oldPos + oldPosInc,
        lastComponent: { count: 1, added, removed, previousComponent: last }
      };
    }
  }
  extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
    const newLen = newTokens.length, oldLen = oldTokens.length;
    let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
    while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
      newPos++;
      oldPos++;
      commonCount++;
      if (options.oneChangePerToken) {
        basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
      }
    }
    if (commonCount && !options.oneChangePerToken) {
      basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
    }
    basePath.oldPos = oldPos;
    return newPos;
  }
  equals(left, right, options) {
    if (options.comparator) {
      return options.comparator(left, right);
    } else {
      return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
    }
  }
  removeEmpty(array) {
    const ret = [];
    for (let i = 0; i < array.length; i++) {
      if (array[i]) {
        ret.push(array[i]);
      }
    }
    return ret;
  }
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  castInput(value, options) {
    return value;
  }
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  tokenize(value, options) {
    return Array.from(value);
  }
  join(chars) {
    return chars.join("");
  }
  postProcess(changeObjects, options) {
    return changeObjects;
  }
  get useLongestToken() {
    return false;
  }
  buildValues(lastComponent, newTokens, oldTokens) {
    const components = [];
    let nextComponent;
    while (lastComponent) {
      components.push(lastComponent);
      nextComponent = lastComponent.previousComponent;
      delete lastComponent.previousComponent;
      lastComponent = nextComponent;
    }
    components.reverse();
    const componentLen = components.length;
    let componentPos = 0, newPos = 0, oldPos = 0;
    for (; componentPos < componentLen; componentPos++) {
      const component = components[componentPos];
      if (!component.removed) {
        if (!component.added && this.useLongestToken) {
          let value = newTokens.slice(newPos, newPos + component.count);
          value = value.map(function(value2, i) {
            const oldValue = oldTokens[oldPos + i];
            return oldValue.length > value2.length ? oldValue : value2;
          });
          component.value = this.join(value);
        } else {
          component.value = this.join(newTokens.slice(newPos, newPos + component.count));
        }
        newPos += component.count;
        if (!component.added) {
          oldPos += component.count;
        }
      } else {
        component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
        oldPos += component.count;
      }
    }
    return components;
  }
};

// node_modules/diff/libesm/diff/line.js
var LineDiff = class extends Diff {
  constructor() {
    super(...arguments);
    this.tokenize = tokenize;
  }
  equals(left, right, options) {
    if (options.ignoreWhitespace) {
      if (!options.newlineIsToken || !left.includes("\n")) {
        left = left.trim();
      }
      if (!options.newlineIsToken || !right.includes("\n")) {
        right = right.trim();
      }
    } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
      if (left.endsWith("\n")) {
        left = left.slice(0, -1);
      }
      if (right.endsWith("\n")) {
        right = right.slice(0, -1);
      }
    }
    return super.equals(left, right, options);
  }
};
var lineDiff = new LineDiff();
function diffLines(oldStr, newStr, options) {
  return lineDiff.diff(oldStr, newStr, options);
}
function tokenize(value, options) {
  if (options.stripTrailingCr) {
    value = value.replace(/\r\n/g, "\n");
  }
  const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
  if (!linesAndNewlines[linesAndNewlines.length - 1]) {
    linesAndNewlines.pop();
  }
  for (let i = 0; i < linesAndNewlines.length; i++) {
    const line = linesAndNewlines[i];
    if (i % 2 && !options.newlineIsToken) {
      retLines[retLines.length - 1] += line;
    } else {
      retLines.push(line);
    }
  }
  return retLines;
}

// src/git/diff.ts
async function getFileDiff(cwd, filepath) {
  try {
    const fullPath = path.join(cwd, filepath);
    const fs = fsNode.promises;
    try {
      await fs.access(fullPath);
    } catch {
      return null;
    }
    let headBuffer = Buffer.alloc(0);
    let headContent = "";
    try {
      const headCommitOid = await git.resolveRef({
        fs: fsNode,
        dir: cwd,
        ref: "HEAD"
      });
      const { blob } = await git.readBlob({
        fs: fsNode,
        dir: cwd,
        oid: headCommitOid,
        filepath
      });
      headBuffer = Buffer.from(blob);
      headContent = headBuffer.toString("utf8");
    } catch {
      headContent = "";
    }
    const workingBuffer = await fs.readFile(fullPath);
    const workingContent = workingBuffer.toString("utf8");
    if (isBinaryContent(headBuffer) || isBinaryContent(workingBuffer)) {
      return "[Binary file - no diff available]";
    }
    if (headContent === workingContent) {
      return null;
    }
    return generateUnifiedDiff(filepath, headContent, workingContent);
  } catch (error) {
    throw new Error(
      `Failed to get diff for ${filepath}: ${error instanceof Error ? error.message : String(error)}`
    );
  }
}
async function getCheckpointFileDiff(cwd, filepath, checkpointRef) {
  try {
    let checkpointBuffer = Buffer.alloc(0);
    let checkpointContent = "";
    try {
      const checkpointOid = await git.resolveRef({
        fs: fsNode,
        dir: cwd,
        ref: checkpointRef
      });
      const { blob } = await git.readBlob({
        fs: fsNode,
        dir: cwd,
        oid: checkpointOid,
        filepath
      });
      checkpointBuffer = Buffer.from(blob);
      checkpointContent = checkpointBuffer.toString("utf8");
    } catch {
      return `Will be deleted on restore: ${filepath}

This file exists in HEAD but not in the checkpoint.
Restoring the checkpoint will remove this file from the working directory.`;
    }
    let headBuffer = Buffer.alloc(0);
    let headContent = "";
    try {
      const headCommitOid = await git.resolveRef({
        fs: fsNode,
        dir: cwd,
        ref: "HEAD"
      });
      const { blob } = await git.readBlob({
        fs: fsNode,
        dir: cwd,
        oid: headCommitOid,
        filepath
      });
      headBuffer = Buffer.from(blob);
      headContent = headBuffer.toString("utf8");
    } catch {
      headContent = "";
    }
    if (isBinaryContent(headBuffer) || isBinaryContent(checkpointBuffer)) {
      return "[Binary file - no diff available]";
    }
    if (checkpointContent === headContent) {
      return "No changes between checkpoint and HEAD";
    }
    return generateUnifiedDiff(filepath, headContent, checkpointContent);
  } catch (error) {
    throw new Error(
      `Failed to get checkpoint diff for ${filepath}: ${error instanceof Error ? error.message : String(error)}`
    );
  }
}
function generateUnifiedDiff(filepath, oldContent, newContent) {
  const oldLines = oldContent.split("\n");
  const newLines = newContent.split("\n");
  const changes = diffLines(oldContent, newContent, {
    newlineIsToken: false
  });
  let addedCount = 0;
  let removedCount = 0;
  const chunks = [];
  for (const change of changes) {
    const lines = change.value.split("\n").filter((line) => line.length > 0);
    if (!change.added && !change.removed) {
      for (const line of lines) {
        chunks.push(` ${line}`);
      }
    } else if (change.removed) {
      removedCount += lines.length;
      for (const line of lines) {
        chunks.push(`-${line}`);
      }
    } else if (change.added) {
      addedCount += lines.length;
      for (const line of lines) {
        chunks.push(`+${line}`);
      }
    }
  }
  const diff = [];
  diff.push(`--- Lines that will be REMOVED on restore: ${removedCount} lines`);
  diff.push(`+++ Lines that will be ADDED on restore: ${addedCount} lines`);
  const MAX_LINES = 2e4;
  const totalLines = Math.max(oldLines.length, newLines.length);
  if (chunks.length > MAX_LINES) {
    const truncatedChunks = chunks.slice(0, MAX_LINES);
    diff.push(...truncatedChunks);
    diff.push("");
    diff.push(
      `[File truncated - showing first ${MAX_LINES.toLocaleString()} of ${totalLines.toLocaleString()} lines]`
    );
  } else {
    diff.push(...chunks);
  }
  return diff.join("\n");
}
function isBinaryContent(buffer) {
  if (buffer.length === 0) {
    return false;
  }
  const checkLength = Math.min(buffer.length, 8e3);
  for (let i = 0; i < checkLength; i++) {
    if (buffer[i] === 0) {
      return true;
    }
  }
  return false;
}

// src/git/diff-worker.ts
if (!parentPort) {
  throw new Error("This module must be run as a worker thread");
}
parentPort.on("message", async (request) => {
  try {
    let diff;
    if (request.checkpointRef) {
      diff = await getCheckpointFileDiff(
        request.cwd,
        request.filepath,
        request.checkpointRef
      );
    } else {
      diff = await getFileDiff(request.cwd, request.filepath);
    }
    const response = {
      id: request.id,
      diff: diff || "No changes to display"
    };
    parentPort.postMessage(response);
  } catch (error) {
    const response = {
      id: request.id,
      error: error instanceof Error ? error.message : "Error loading diff"
    };
    parentPort.postMessage(response);
  }
});
