(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.TextEditorDocument = exports.FileNotFoundError = exports.InvalidPathError = exports.InvalidViewRangeError = exports.InvalidLineNumberError = exports.MultipleMatchesError = exports.NoMatchFoundError = void 0;
    exports.formatTextWithLineNumbers = formatTextWithLineNumbers;
    exports.findDivergenceContext = findDivergenceContext;
    exports.parseStrReplacements = parseStrReplacements;
    class NoMatchFoundError extends Error {
        constructor(_searchStr, nearMatchContext) {
            const base = 'No exact match found for str_replace. The old_str content was not found in the file.';
            const message = nearMatchContext ? `${base}\n${nearMatchContext}` : base;
            super(message);
            this.name = 'NoMatchFoundError';
        }
    }
    exports.NoMatchFoundError = NoMatchFoundError;
    class MultipleMatchesError extends Error {
        constructor(count) {
            super(`Found ${count} matches. Please provide more context to make the replacement unique.`);
            this.name = 'MultipleMatchesError';
        }
    }
    exports.MultipleMatchesError = MultipleMatchesError;
    class InvalidLineNumberError extends Error {
        constructor(line, maxLine) {
            super(`Invalid line number ${line}. File has ${maxLine} lines (valid range: 0-${maxLine}).`);
            this.name = 'InvalidLineNumberError';
        }
    }
    exports.InvalidLineNumberError = InvalidLineNumberError;
    class InvalidViewRangeError extends Error {
        constructor(start, end, maxLine) {
            super(`Invalid view range: end (${end}) must be >= start (${start}). File has ${maxLine} lines (valid range: 1-${maxLine}).`);
            this.name = 'InvalidViewRangeError';
        }
    }
    exports.InvalidViewRangeError = InvalidViewRangeError;
    class InvalidPathError extends Error {
        constructor(path, supportedPath, message) {
            super(message ?? `Invalid path "${path}". Only ${supportedPath} is supported.`);
            this.name = 'InvalidPathError';
        }
    }
    exports.InvalidPathError = InvalidPathError;
    class FileNotFoundError extends Error {
        constructor(message = 'No file content exists yet. Use create first.') {
            super(message);
            this.name = 'FileNotFoundError';
        }
    }
    exports.FileNotFoundError = FileNotFoundError;
    const PREVIEW_MAX_LENGTH = 80;
    const MIN_PREFIX_LENGTH = 10;
    const CONTEXT_LINES = 3;
    const OLD_STR_CONTEXT_LENGTH = 40;
    function truncatePreview(str) {
        if (str.length <= PREVIEW_MAX_LENGTH)
            return str;
        return str.slice(0, PREVIEW_MAX_LENGTH) + '...';
    }
    function escapeWhitespace(str) {
        return str.replace(/\n/g, '\\n').replace(/\t/g, '\\t').replace(/\r/g, '\\r');
    }
    function isObjectRecord(value) {
        return typeof value === 'object' && value !== null && !Array.isArray(value);
    }
    function isUnknownArray(value) {
        return Array.isArray(value);
    }
    function formatTextWithLineNumbers(text) {
        const lines = text.split('\n');
        return lines.map((line, i) => `${i + 1}: ${line}`).join('\n');
    }
    function findDivergenceContext(code, searchStr) {
        let lo = 0;
        let hi = searchStr.length;
        while (lo < hi) {
            const mid = Math.ceil((lo + hi) / 2);
            if (code.includes(searchStr.substring(0, mid))) {
                lo = mid;
            }
            else {
                hi = mid - 1;
            }
        }
        const matchLength = lo;
        if (matchLength < MIN_PREFIX_LENGTH)
            return undefined;
        const matchPos = code.indexOf(searchStr.substring(0, matchLength));
        const divergePos = matchPos + matchLength;
        const percentage = Math.round((matchLength / searchStr.length) * 100);
        const codeUpToDiverge = code.substring(0, divergePos);
        const divergeLine = codeUpToDiverge.split('\n').length;
        const oldStrRemainder = searchStr.substring(matchLength, matchLength + OLD_STR_CONTEXT_LENGTH);
        const oldStrPrefix = searchStr.substring(Math.max(0, matchLength - 20), matchLength);
        const codeLines = code.split('\n');
        const startLine = Math.max(0, divergeLine - CONTEXT_LINES);
        const endLine = Math.min(codeLines.length, divergeLine + 1);
        const fileContext = codeLines
            .slice(startLine, endLine)
            .map((line, i) => `    ${startLine + i + 1}: ${line}`)
            .join('\n');
        return (`Closest match (${percentage}% of old_str matched, diverges at line ${divergeLine}):\n` +
            `  old_str: ...${escapeWhitespace(oldStrPrefix)}>>> ${escapeWhitespace(oldStrRemainder)}\n` +
            `  file:\n${fileContext}`);
    }
    function parseStrReplacements(raw) {
        let parsed = raw;
        if (typeof parsed === 'string') {
            try {
                parsed = JSON.parse(parsed);
            }
            catch {
                throw new Error('replacements must be a JSON array of {old_str, new_str} objects, but received an invalid JSON string.');
            }
        }
        if (!isUnknownArray(parsed)) {
            throw new Error('replacements must be an array of {old_str, new_str} objects. Example: {"replacements": [{"old_str": "foo", "new_str": "bar"}]}');
        }
        const replacements = [];
        for (let i = 0; i < parsed.length; i++) {
            const item = parsed[i];
            if (!isObjectRecord(item) || typeof item.old_str !== 'string') {
                throw new Error(`replacements[${i}] is missing a valid "old_str" string. Each replacement must have {old_str: string, new_str: string}.`);
            }
            if (typeof item.new_str !== 'string') {
                throw new Error(`replacements[${i}] is missing a valid "new_str" string. Each replacement must have {old_str: string, new_str: string}.`);
            }
            replacements.push({ old_str: item.old_str, new_str: item.new_str });
        }
        return replacements;
    }
    class TextEditorDocument {
        constructor(options = {}) {
            this.text = options.initialText ?? null;
            this.options = options;
        }
        execute(command) {
            if (command.command === 'create') {
                this.validateCreatePath(command.path);
            }
            this.validatePath(command.path);
            switch (command.command) {
                case 'view':
                    return this.handleView(command);
                case 'create':
                    return this.handleCreate(command);
                case 'str_replace':
                    return this.handleStrReplace(command);
                case 'insert':
                    return this.handleInsert(command);
            }
        }
        executeBatch(replacements) {
            if (this.text === null) {
                throw this.createFileNotFoundError();
            }
            if (replacements.length === 0) {
                return 'No replacements to apply.';
            }
            const snapshot = this.text;
            const results = [];
            for (let i = 0; i < replacements.length; i++) {
                const { old_str, new_str } = replacements[i];
                const preview = truncatePreview(old_str);
                const count = this.countOccurrences(this.text, old_str);
                if (count === 0) {
                    this.text = snapshot;
                    results.push({
                        index: i,
                        old_str: preview,
                        status: 'failed',
                        error: new NoMatchFoundError(old_str).message,
                    });
                    this.appendNotAttemptedResults(results, replacements, i + 1);
                    return results;
                }
                if (count > 1) {
                    this.text = snapshot;
                    results.push({
                        index: i,
                        old_str: preview,
                        status: 'failed',
                        error: new MultipleMatchesError(count).message,
                    });
                    this.appendNotAttemptedResults(results, replacements, i + 1);
                    return results;
                }
                this.text = this.text.replace(old_str, this.escapeReplacement(new_str));
                results.push({ index: i, old_str: preview, status: 'success' });
            }
            return `All ${replacements.length} replacements applied successfully.`;
        }
        getText() {
            return this.text;
        }
        setText(text) {
            this.text = text;
        }
        hasText() {
            return this.text !== null;
        }
        clearText() {
            this.text = null;
        }
        validateCreatePath(path) {
            const supportedPath = this.options.supportedPath;
            if (supportedPath === undefined || path === supportedPath) {
                return;
            }
            throw new Error(this.options.createInvalidPathMessage?.(path, supportedPath) ??
                `Cannot create "${path}". Only ${supportedPath} is supported.`);
        }
        validatePath(path) {
            const supportedPath = this.options.supportedPath;
            if (supportedPath === undefined || path === supportedPath) {
                return;
            }
            throw this.createInvalidPathError(path, supportedPath, this.options.invalidPathMessage?.(path, supportedPath));
        }
        handleView(command) {
            if (this.text === null) {
                throw this.createFileNotFoundError();
            }
            const lines = this.text.split('\n');
            if (command.view_range) {
                const [start, rawEnd] = command.view_range;
                const end = rawEnd === -1 ? lines.length : rawEnd;
                if (start < 1 || start > lines.length) {
                    throw new InvalidLineNumberError(start, lines.length);
                }
                if (end < start) {
                    throw new InvalidViewRangeError(start, end, lines.length);
                }
                const startIndex = start - 1;
                const endIndex = Math.min(end, lines.length);
                const selectedLines = lines.slice(startIndex, endIndex);
                return selectedLines.map((line, i) => `${startIndex + i + 1}: ${line}`).join('\n');
            }
            return formatTextWithLineNumbers(this.text);
        }
        handleCreate(command) {
            this.text = command.file_text;
            return 'File created successfully.';
        }
        handleStrReplace(command) {
            if (this.text === null) {
                throw this.createFileNotFoundError();
            }
            const { old_str, new_str } = command;
            const count = this.countOccurrences(this.text, old_str);
            if (count === 0) {
                const normalized = old_str.endsWith('\n') ? old_str.slice(0, -1) : old_str + '\n';
                const normalizedCount = this.countOccurrences(this.text, normalized);
                if (normalizedCount === 1) {
                    this.text = this.text.replace(normalized, this.escapeReplacement(new_str));
                    return 'Edit applied successfully.';
                }
                const context = findDivergenceContext(this.text, old_str);
                throw new NoMatchFoundError(old_str, context);
            }
            if (count > 1) {
                throw new MultipleMatchesError(count);
            }
            this.text = this.text.replace(old_str, this.escapeReplacement(new_str));
            return 'Edit applied successfully.';
        }
        handleInsert(command) {
            if (this.text === null) {
                throw this.createFileNotFoundError();
            }
            const { insert_line, insert_text } = command;
            const lines = this.text.split('\n');
            if (insert_line < 0 || insert_line > lines.length) {
                throw new InvalidLineNumberError(insert_line, lines.length);
            }
            lines.splice(insert_line, 0, insert_text);
            this.text = lines.join('\n');
            return 'Text inserted successfully.';
        }
        countOccurrences(text, search) {
            if (search.length === 0) {
                return 0;
            }
            let count = 0;
            let pos = 0;
            while ((pos = text.indexOf(search, pos)) !== -1) {
                count++;
                pos += search.length;
            }
            return count;
        }
        appendNotAttemptedResults(results, replacements, startIndex) {
            for (let i = startIndex; i < replacements.length; i++) {
                results.push({
                    index: i,
                    old_str: truncatePreview(replacements[i].old_str),
                    status: 'not_attempted',
                });
            }
        }
        createInvalidPathError(path, supportedPath, message) {
            return new InvalidPathError(path, supportedPath, message);
        }
        createFileNotFoundError() {
            return new FileNotFoundError(this.options.fileNotFoundMessage);
        }
        escapeReplacement(replacement) {
            return replacement.replace(/\$/g, '$$$$');
        }
    }
    exports.TextEditorDocument = TextEditorDocument;
});
//# sourceMappingURL=text-editor.js.map