
import { __decorate, __param } from '../../../../../../../../external/tslib/tslib.es6.js';
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
import '../../../../../base/common/observableInternal/index.js';
import { isEqual } from '../../../../../base/common/resources.js';
import { URI } from '../../../../../base/common/uri.js';
import { generateUuid } from '../../../../../base/common/uuid.js';
import { localize } from '../../../../../nls.js';
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.service.js';
import { SaveReason } from '../../../../common/editor.js';
import { GroupsOrder } from '../../../../services/editor/common/editorGroupsService.js';
import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.service.js';
import { ITextFileService } from '../../../../services/textfile/common/textfiles.service.js';
import { CellUri } from '../../../notebook/common/notebookCommon.js';
import { INotebookService } from '../../../notebook/common/notebookService.service.js';
import { ICodeMapperService } from '../chatCodeMapperService.service.js';
import { IChatService } from '../chatService.service.js';
import { ILanguageModelIgnoredFilesService } from '../ignoredFiles.service.js';
import { autorun } from '../../../../../base/common/observableInternal/autorun.js';

const codeInstructions = `
The user is very smart and can understand how to apply your edits to their files, you just need to provide minimal hints.
Avoid repeating existing code, instead use comments to represent regions of unchanged code. The user prefers that you are as concise as possible. For example:
// ...existing code...
{ changed code }
// ...existing code...
{ changed code }
// ...existing code...

Here is an example of how you should use format an edit to an existing Person class:
class Person {
	// ...existing code...
	age: number;
	// ...existing code...
	getAge() {
		return this.age;
	}
}
`;
const ExtensionEditToolId = 'vscode_editFile';
const InternalEditToolId = 'vscode_editFile_internal';
const EditToolData = {
    id: InternalEditToolId,
    displayName: ( localize(5006, "Edit File")),
    modelDescription: `Edit a file in the workspace. Use this tool once per file that needs to be modified, even if there are multiple changes for a file. Generate the "explanation" property first. ${codeInstructions}`,
    source: { type: 'internal' },
    inputSchema: {
        type: 'object',
        properties: {
            explanation: {
                type: 'string',
                description: 'A short explanation of the edit being made. Can be the same as the explanation you showed to the user.',
            },
            filePath: {
                type: 'string',
                description: 'An absolute path to the file to edit, or the URI of a untitled, not yet named, file, such as `untitled:Untitled-1.',
            },
            code: {
                type: 'string',
                description: 'The code change to apply to the file. ' + codeInstructions
            }
        },
        required: ['explanation', 'filePath', 'code']
    }
};
let EditTool = class EditTool {
    constructor(chatService, codeMapperService, workspaceContextService, ignoredFilesService, textFileService, notebookService, editorGroupsService) {
        this.chatService = chatService;
        this.codeMapperService = codeMapperService;
        this.workspaceContextService = workspaceContextService;
        this.ignoredFilesService = ignoredFilesService;
        this.textFileService = textFileService;
        this.notebookService = notebookService;
        this.editorGroupsService = editorGroupsService;
    }
    async invoke(invocation, countTokens, token) {
        if (!invocation.context) {
            throw ( new Error('toolInvocationToken is required for this tool'));
        }
        const parameters = invocation.parameters;
        const fileUri = URI.revive(parameters.file);
        const uri = CellUri.parse(fileUri)?.notebook || fileUri;
        if (!this.workspaceContextService.isInsideWorkspace(uri) && !this.notebookService.getNotebookTextModel(uri)) {
            const groupsByLastActive = this.editorGroupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
            const uriIsOpenInSomeEditor = ( groupsByLastActive.some((group) => {
                return ( group.editors.some((editor) => {
                    return isEqual(editor.resource, uri);
                }));
            }));
            if (!uriIsOpenInSomeEditor) {
                throw ( new Error(
                    `File ${uri.fsPath} can't be edited because it's not inside the current workspace`
                ));
            }
        }
        if (await this.ignoredFilesService.fileIsIgnored(uri, token)) {
            throw ( new Error(
                `File ${uri.fsPath} can't be edited because it is configured to be ignored by Copilot`
            ));
        }
        const model = this.chatService.getSession(invocation.context?.sessionId);
        const request = model.getRequests().at(-1);
        if (request.response?.response.getMarkdown().length) {
            model.acceptResponseProgress(request, {
                kind: 'undoStop',
                id: generateUuid(),
            });
        }
        model.acceptResponseProgress(request, {
            kind: 'markdownContent',
            content: ( new MarkdownString('\n````\n'))
        });
        model.acceptResponseProgress(request, {
            kind: 'codeblockUri',
            uri,
            isEdit: true
        });
        model.acceptResponseProgress(request, {
            kind: 'markdownContent',
            content: ( new MarkdownString(parameters.code + '\n````\n'))
        });
        if (this.notebookService.hasSupportedNotebooks(uri) && (this.notebookService.getNotebookTextModel(uri))) {
            model.acceptResponseProgress(request, {
                kind: 'notebookEdit',
                edits: [],
                uri
            });
        }
        else {
            model.acceptResponseProgress(request, {
                kind: 'textEdit',
                edits: [],
                uri
            });
        }
        const editSession = model.editingSession;
        if (!editSession) {
            throw ( new Error('This tool must be called from within an editing session'));
        }
        const result = await this.codeMapperService.mapCode({
            codeBlocks: [{ code: parameters.code, resource: uri, markdownBeforeBlock: parameters.explanation }],
            location: 'tool',
            chatRequestId: invocation.chatRequestId
        }, {
            textEdit: (target, edits) => {
                model.acceptResponseProgress(request, { kind: 'textEdit', uri: target, edits });
            },
            notebookEdit(target, edits) {
                model.acceptResponseProgress(request, { kind: 'notebookEdit', uri: target, edits });
            },
        }, token);
        if (this.notebookService.hasSupportedNotebooks(uri) && (this.notebookService.getNotebookTextModel(uri))) {
            model.acceptResponseProgress(request, { kind: 'notebookEdit', uri, edits: [], done: true });
        }
        else {
            model.acceptResponseProgress(request, { kind: 'textEdit', uri, edits: [], done: true });
        }
        if (result?.errorMessage) {
            throw ( new Error(result.errorMessage));
        }
        let dispose;
        await ( new Promise((resolve) => {
            let wasFileBeingModified = false;
            dispose = autorun((r) => {
                const entries = editSession.entries.read(r);
                const currentFile = entries?.find((e) => ( e.modifiedURI.toString()) === ( uri.toString()));
                if (currentFile) {
                    if (currentFile.isCurrentlyBeingModifiedBy.read(r)) {
                        wasFileBeingModified = true;
                    }
                    else if (wasFileBeingModified) {
                        resolve(true);
                    }
                }
            });
        })).finally(() => {
            dispose.dispose();
        });
        await this.textFileService.save(uri, {
            reason: SaveReason.AUTO,
            skipSaveParticipants: true,
        });
        return {
            content: [{ kind: 'text', value: 'The file was edited successfully' }]
        };
    }
    async prepareToolInvocation(parameters, token) {
        return {
            presentation: 'hidden'
        };
    }
};
EditTool = ( __decorate([
    ( __param(0, IChatService)),
    ( __param(1, ICodeMapperService)),
    ( __param(2, IWorkspaceContextService)),
    ( __param(3, ILanguageModelIgnoredFilesService)),
    ( __param(4, ITextFileService)),
    ( __param(5, INotebookService)),
    ( __param(6, IEditorGroupsService))
], EditTool));
class EditToolInputProcessor {
    processInput(input) {
        if (!input.filePath) {
            return input;
        }
        const filePath = input.filePath;
        return {
            file: filePath.startsWith('untitled:') ? ( URI.parse(filePath)) : URI.file(filePath),
            explanation: input.explanation,
            code: input.code,
        };
    }
}

export { EditTool, EditToolData, EditToolInputProcessor, ExtensionEditToolId, InternalEditToolId };
