"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseHunks = parseHunks;
exports.hunksToPatch = hunksToPatch;
exports.hunkIsEmpty = hunkIsEmpty;
exports.fixPatch = fixPatch;
exports.savePatchError = savePatchError;
exports.patchFile = patchFile;
exports.categorizeHunks = categorizeHunks;
const fs = __importStar(require("fs"));
const diff_1 = require("diff");
const utils_1 = require("../../utils");
const services_1 = require("../../services");
function findAllLineNumbers(fullText, searchText) {
    const lines = (0, utils_1.splitByNewLines)(fullText);
    const search = searchText.startsWith("+") || searchText.startsWith("-")
        ? searchText.slice(1).trim()
        : searchText.trim();
    if (!search)
        return [];
    const lineNumbers = [];
    lines.forEach((line, index) => {
        if (line.trim() === search) {
            lineNumbers.push(index + 1);
        }
    });
    return lineNumbers;
}
function findClosestNumber(numbers, goal) {
    if (!numbers || numbers.length === 0) {
        return undefined;
    }
    return numbers.reduce((prev, curr) => {
        return Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev;
    });
}
function findSequenceIndex(haystack, needle) {
    if (!needle || needle.length === 0) {
        return -1;
    }
    if (!haystack || needle.length > haystack.length) {
        return -1;
    }
    let haystackIndex = 0;
    while (haystackIndex <= haystack.length - needle.length) {
        const potentialStartIndex = haystack.indexOf(needle[0], haystackIndex);
        if (potentialStartIndex === -1) {
            return -1;
        }
        if (potentialStartIndex > haystack.length - needle.length) {
            return -1;
        }
        let sequenceMatches = true;
        for (let needleIndex = 1; needleIndex < needle.length; needleIndex++) {
            if (haystack[potentialStartIndex + needleIndex] !== needle[needleIndex]) {
                sequenceMatches = false;
                break;
            }
        }
        if (sequenceMatches) {
            return potentialStartIndex;
        }
        haystackIndex = potentialStartIndex + 1;
    }
    return -1;
}
function parseHunks(patch) {
    const patchLines = (0, utils_1.splitByNewLines)(patch);
    const hunks = [];
    let currentHunkLines = [];
    let currentHeader = "";
    let originalStart = 0;
    let originalCount = 0;
    let newStart = 0;
    let newCount = 0;
    for (const line of patchLines) {
        if (line.startsWith("@@")) {
            if (currentHeader) {
                const additions = currentHunkLines.filter((l) => l.startsWith("+"));
                const subtractions = currentHunkLines.filter((l) => l.startsWith("-"));
                const contextLines = currentHunkLines.filter((l) => l.startsWith(" "));
                hunks.push({
                    header: currentHeader,
                    originalStartLine: originalStart,
                    originalLineCount: originalCount,
                    newStartLine: newStart,
                    newLineCount: newCount,
                    lines: currentHunkLines,
                    additions,
                    subtractions,
                    contextLines,
                });
            }
            currentHeader = line;
            currentHunkLines = [];
            const match = line.match(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
            if (match) {
                originalStart = parseInt(match[1], 10);
                originalCount = parseInt(match[2] || "1", 10);
                newStart = parseInt(match[3], 10);
                newCount = parseInt(match[4] || "1", 10);
            }
            else {
                originalStart = 0;
                originalCount = 0;
                newStart = 0;
                newCount = 0;
                console.warn("Could not parse hunk header:", line);
            }
        }
        else if (currentHeader &&
            (line.startsWith(" ") ||
                line.startsWith("+") ||
                line.startsWith("-") ||
                line === "\\ No newline at end of file")) {
            currentHunkLines.push(line);
        }
    }
    if (currentHeader) {
        const additions = currentHunkLines.filter((l) => l.startsWith("+"));
        const subtractions = currentHunkLines.filter((l) => l.startsWith("-"));
        const contextLines = currentHunkLines.filter((l) => l.startsWith(" "));
        hunks.push({
            header: currentHeader,
            originalStartLine: originalStart,
            originalLineCount: originalCount,
            newStartLine: newStart,
            newLineCount: newCount,
            lines: currentHunkLines,
            additions,
            subtractions,
            contextLines,
        });
    }
    return hunks;
}
function hunksToPatch(hunks) {
    const noNewLineMessage = "\\ No newline at end of file";
    return hunks
        .map((hunk) => {
        let lines = [...hunk.lines];
        let endsWithNoNewline = false;
        if (lines[lines.length - 1]?.trim() === noNewLineMessage.trim()) {
            endsWithNoNewline = true;
            lines = lines.slice(0, -1);
        }
        let hunkContent = lines.join("\n");
        if (endsWithNoNewline) {
            hunkContent += "\n" + noNewLineMessage;
        }
        return [hunk.header, hunkContent].join("\n");
    })
        .join("\n");
}
function hunkIsEmpty(hunk) {
    const noLines = hunk.lines.length === 0;
    const noChanges = hunk.additions.length === 0 && hunk.subtractions.length === 0;
    const additionsText = hunk.additions.map((l) => l.slice(1)).join("\n");
    const subtractionsText = hunk.subtractions.map((l) => l.slice(1)).join("\n");
    const noEffectiveChange = additionsText === subtractionsText;
    return noLines || noChanges || noEffectiveChange;
}
const CONTEXT_LINES = 3;
function fixSingleHunk(hunk, originalContent) {
    const originalLines = (0, utils_1.splitByNewLines)(originalContent);
    const deletionLinesContent = hunk.subtractions.map((l) => l.slice(1));
    const additionLinesContent = hunk.additions.map((l) => l.slice(1));
    let actualOriginalStartLine = -1;
    if (deletionLinesContent.length > 0) {
        const deletionStartIndex = findSequenceIndex(originalLines, deletionLinesContent);
        if (deletionStartIndex !== -1) {
            actualOriginalStartLine = deletionStartIndex + 1;
            console.log(`Anchor found via deletion sequence at line ${actualOriginalStartLine}`);
        }
    }
    if (actualOriginalStartLine === -1 &&
        deletionLinesContent.length === 0 &&
        additionLinesContent.length > 0) {
        let precedingContextLine = "";
        for (const line of hunk.lines) {
            if (line.startsWith("+"))
                break;
            if (line.startsWith(" ")) {
                precedingContextLine = line.slice(1);
            }
        }
        if (precedingContextLine) {
            const potentialLines = findAllLineNumbers(originalContent, precedingContextLine);
            const closestLine = findClosestNumber(potentialLines, hunk.originalStartLine);
            if (closestLine !== undefined) {
                actualOriginalStartLine = closestLine + 1;
                console.log(`Anchor found via preceding context '${precedingContextLine}' targeting line ${actualOriginalStartLine}`);
            }
        }
    }
    if (actualOriginalStartLine === -1) {
        console.warn("Could not determine anchor point for hunk:", hunk.header);
        let fallbackAnchorFound = false;
        for (let i = 0; i < hunk.lines.length; i++) {
            const line = hunk.lines[i];
            if (line.startsWith("+"))
                continue;
            const lineContent = line.slice(1);
            const potentialLines = findAllLineNumbers(originalContent, lineContent);
            if (potentialLines.length > 0) {
                const closestLine = findClosestNumber(potentialLines, hunk.originalStartLine + i);
                if (closestLine !== undefined) {
                    let offset = 0;
                    for (let j = 0; j < i; j++) {
                        if (!hunk.lines[j].startsWith("+")) {
                            offset++;
                        }
                    }
                    actualOriginalStartLine = closestLine - offset;
                    console.log(`Fallback anchor found via line '${lineContent}' near ${hunk.originalStartLine}, estimated start: ${actualOriginalStartLine}`);
                    fallbackAnchorFound = true;
                    break;
                }
            }
        }
        if (!fallbackAnchorFound) {
            console.error("Failed to find any anchor for hunk:", hunk.header);
            return null;
        }
    }
    actualOriginalStartLine = Math.max(1, actualOriginalStartLine);
    const contextBeforeStartLine = Math.max(0, actualOriginalStartLine - CONTEXT_LINES - 1);
    const contextBeforeEndLine = Math.max(0, actualOriginalStartLine - 1);
    const contextBefore = originalLines
        .slice(contextBeforeStartLine, contextBeforeEndLine)
        .map((l) => ` ${l}`);
    const originalContentEndLine = actualOriginalStartLine + deletionLinesContent.length;
    const contextAfterStartLine = originalContentEndLine - 1;
    const contextAfterEndLine = Math.min(originalLines.length, contextAfterStartLine + CONTEXT_LINES);
    const contextAfter = originalLines
        .slice(contextAfterStartLine, contextAfterEndLine)
        .map((l) => ` ${l}`);
    const newHunkLines = [
        ...contextBefore,
        ...hunk.subtractions,
        ...hunk.additions,
        ...contextAfter,
    ];
    const newOriginalStart = contextBefore.length > 0
        ? actualOriginalStartLine - contextBefore.length
        : actualOriginalStartLine;
    const newOriginalCount = contextBefore.length + hunk.subtractions.length + contextAfter.length;
    const newNewStart = newOriginalStart;
    const newNewCount = contextBefore.length + hunk.additions.length + contextAfter.length;
    const finalOriginalStart = Math.max(1, newOriginalStart);
    const finalOriginalCount = Math.max(1, newOriginalCount);
    const finalNewStart = Math.max(1, newNewStart);
    const finalNewCount = Math.max(1, newNewCount);
    const displayOriginalCount = originalLines.length === 0 && newOriginalCount === 0
        ? 0
        : finalOriginalCount;
    const displayNewCount = originalLines.length === 0 && newNewCount === 0 ? 0 : finalNewCount;
    const newHeader = `@@ -${finalOriginalStart},${displayOriginalCount} +${finalNewStart},${displayNewCount} @@`;
    const fixedHunk = {
        header: newHeader,
        originalStartLine: finalOriginalStart,
        originalLineCount: displayOriginalCount,
        newStartLine: finalNewStart,
        newLineCount: displayNewCount,
        lines: newHunkLines,
        additions: hunk.additions,
        subtractions: hunk.subtractions,
        contextLines: [...contextBefore, ...contextAfter],
    };
    if (hunkIsEmpty(fixedHunk)) {
        console.log("Hunk became empty after fixing:", fixedHunk.header);
        return null;
    }
    return fixedHunk;
}
function fixPatch(originalContent, patch) {
    const originalHunks = parseHunks(patch);
    const fixedHunks = [];
    console.log(`Attempting to fix ${originalHunks.length} hunks...`);
    for (const hunk of originalHunks) {
        console.log(`\nProcessing Hunk: ${hunk.header}`);
        try {
            const fixed = fixSingleHunk(hunk, originalContent);
            if (fixed) {
                console.log(`Successfully fixed. New header: ${fixed.header}`);
                fixedHunks.push(fixed);
            }
            else {
                console.warn(`Could not fix hunk, discarding: ${hunk.header}`);
            }
        }
        catch (error) {
            console.error(`Error fixing hunk ${hunk.header}:`, error);
        }
    }
    const needsNoNewlineSuffix = !originalContent.endsWith("\n") && fixedHunks.length > 0;
    let finalPatch = hunksToPatch(fixedHunks);
    if (fixedHunks.length > 0) {
        const lastHunk = fixedHunks[fixedHunks.length - 1];
        const lastLine = lastHunk.lines[lastHunk.lines.length - 1];
        if (patch.includes("\\ No newline at end of file") &&
            !lastLine?.endsWith("\n") &&
            !finalPatch.endsWith("\\ No newline at end of file")) {
        }
    }
    if (patch.endsWith("\n") &&
        !finalPatch.endsWith("\n") &&
        finalPatch.length > 0) {
        finalPatch += "\n";
    }
    return finalPatch;
}
async function savePatchError(originalPatch, fixedPatch, fileContent, errorMsg) {
    const dirName = ".knowhow/tools/patchFile";
    const fileName = "errors.json";
    const filePath = `${dirName}/${fileName}`;
    try {
        if (!(await (0, utils_1.fileExists)(filePath))) {
            await (0, utils_1.mkdir)(dirName, { recursive: true });
            await (0, utils_1.writeFile)(filePath, "[]");
        }
        let errors = [];
        try {
            errors = JSON.parse(await (0, utils_1.readFile)(filePath, "utf8"));
            if (!Array.isArray(errors))
                errors = [];
        }
        catch (readError) {
            console.error("Error reading patch error log, starting fresh.", readError);
            errors = [];
        }
        errors.push({
            timestamp: new Date().toISOString(),
            error: errorMsg,
            originalPatch,
            fixedPatch,
            fileContent,
        });
        await (0, utils_1.writeFile)(filePath, JSON.stringify(errors, null, 2));
    }
    catch (logError) {
        console.error("Failed to save patch error information:", logError);
    }
}
async function patchFile(filePath, patch) {
    const toolService = (this instanceof services_1.ToolsService ? this : (0, services_1.services)().Tools);
    const context = toolService.getContext();
    let originalContent = "";
    try {
        if (!fs.existsSync(filePath)) {
            console.log(`File ${filePath} does not exist. Attempting to apply patch as creation.`);
            originalContent = "";
        }
        else {
            originalContent = await (0, utils_1.readFile)(filePath, "utf8");
        }
        let updatedContent = (0, diff_1.applyPatch)(originalContent, patch);
        let appliedPatch = patch;
        if (updatedContent === false) {
            console.warn("Initial patch apply failed. Attempting to fix patch...");
            let fixedPatch = null;
            try {
                fixedPatch = fixPatch(originalContent, patch);
                console.log("--- Attempted Fixed Patch ---");
                console.log(fixedPatch || "<Fixing resulted in empty patch>");
                console.log("---------------------------");
            }
            catch (fixError) {
                console.error("Error during fixPatch execution:", fixError);
                await savePatchError(patch, null, originalContent, `fixPatch function errored: ${fixError.message}`);
                return `An error occured while trying to fix the patch: ${fixError.message}`;
            }
            if (!fixedPatch || fixedPatch.trim() === "") {
                console.error("Patch could not be fixed or resulted in an empty patch.");
                await savePatchError(patch, fixedPatch, originalContent, "Patch fix resulted in empty or null patch.");
                return `Patch failed to apply and could not be fixed or resulted in no changes. Make sure you are making small patches`;
            }
            updatedContent = (0, diff_1.applyPatch)(originalContent, fixedPatch);
            appliedPatch = fixedPatch;
            if (updatedContent === false && !fixedPatch.endsWith("\n")) {
                console.log("Applying fixed patch failed, trying with added newline...");
                fixedPatch += "\n";
                updatedContent = (0, diff_1.applyPatch)(originalContent, fixedPatch);
                appliedPatch = fixedPatch;
            }
            if (updatedContent === false) {
                console.error("Applying the *fixed* patch also failed.");
                await savePatchError(patch, fixedPatch, originalContent, "Fixed patch also failed to apply.");
                return "Patch failed to apply even after attempting to fix it. Make sure you are making small patches.";
            }
            else {
                console.log("Successfully applied the *fixed* patch.");
            }
        }
        else {
            console.log("Successfully applied the original patch.");
        }
        const eventResults = [];
        if (context.Events) {
            eventResults.push(...(await context.Events.emitBlocking("file:pre-edit", {
                filePath,
                operation: "patch",
                patch: appliedPatch,
                originalContent,
                updatedContent: updatedContent,
            })));
        }
        await (0, utils_1.writeFile)(filePath, updatedContent);
        if (context.Events) {
            eventResults.push(...(await context.Events.emitBlocking("file:post-edit", {
                filePath,
                operation: "patch",
                patch: appliedPatch,
                originalContent,
                updatedContent: updatedContent,
            })));
        }
        let eventResultsText = "";
        if (eventResults && eventResults.length > 0) {
            if (eventResults.length > 0) {
                eventResultsText =
                    "\n\nAdditional Information:\n" +
                        JSON.stringify(eventResults, null, 2);
            }
        }
        return `Patch applied successfully.${filePath ? ` Use readFile on ${filePath} to verify changes.` : ""}${eventResultsText}`.trim();
    }
    catch (e) {
        console.error(`Error in patchFile function for ${filePath}:`, e);
        if (!String(e.message).includes("Patch failed to apply")) {
            await savePatchError(patch, null, originalContent, `Unexpected error in patchFile: ${e.message}`);
        }
        return `An error occured while applying the patch: ${e.message}`;
    }
}
function categorizeHunks(originalContent, patch) {
    const hunks = parseHunks(patch);
    const validHunks = hunks.filter((hunk) => (0, diff_1.applyPatch)(originalContent, hunksToPatch([hunk])));
    const invalidHunks = hunks.filter((hunk) => !(0, diff_1.applyPatch)(originalContent, hunksToPatch([hunk])));
    return { validHunks, invalidHunks };
}
//# sourceMappingURL=patch.js.map