import { raceCancellation } from '../../../base/common/async.js';
import { toDisposable } from '../../../base/common/lifecycle.js';
import { URI } from '../../../base/common/uri.js';
import { MainContext } from './extHost.protocol.js';
import { ApiCommand, ApiCommandArgument, ApiCommandResult } from './extHostCommands.js';
import { Range, Selection as Selection$1, Position, MarkdownString, WorkspaceEdit as WorkspaceEdit$1, TextEdit, ChatInlineFollowup, InteractiveEditorResponseFeedbackKind } from './extHostTypeConverters.js';
import { Selection, WorkspaceEdit, MarkdownString as MarkdownString$1 } from './extHostTypes.js';
class ProviderWrapper {
    static { this._pool = 0; }
    constructor(extension, provider) {
        this.extension = extension;
        this.provider = provider;
        this.handle = ProviderWrapper._pool++;
    }
}
class SessionWrapper {
    constructor(session) {
        this.session = session;
        this.responses = [];
    }
}
class ExtHostInteractiveEditor {
    static { this._nextId = 0; }
    constructor(mainContext, extHostCommands, _documents, _logService) {
        this._documents = _documents;
        this._logService = _logService;
        this._inputProvider = ( new Map());
        this._inputSessions = ( new Map());
        this._proxy = ( mainContext.getProxy(MainContext.MainThreadInlineChat));
        extHostCommands.registerApiCommand(( new ApiCommand(
            'vscode.editorChat.start',
            'inlineChat.start',
            'Invoke a new editor chat session',
            [( new ApiCommandArgument('Run arguments', '', _v => true, v => {
                    if (!v) {
                        return undefined;
                    }
                    return {
                        initialRange: v.initialRange ? Range.from(v.initialRange) : undefined,
                        initialSelection: Selection.isSelection(v.initialSelection) ? Selection$1.from(v.initialSelection) : undefined,
                        message: v.message,
                        autoSend: v.autoSend,
                        position: v.position ? Position.from(v.position) : undefined,
                    };
                }))],
            ApiCommandResult.Void
        )));
    }
    registerProvider(extension, provider, metadata) {
        const wrapper = ( new ProviderWrapper(extension, provider));
        this._inputProvider.set(wrapper.handle, wrapper);
        this._proxy.$registerInteractiveEditorProvider(wrapper.handle, metadata?.label ?? extension.displayName ?? extension.name, extension.identifier, typeof provider.handleInteractiveEditorResponseFeedback === 'function', typeof provider.provideFollowups === 'function', metadata?.supportReportIssue ?? false);
        return toDisposable(() => {
            this._proxy.$unregisterInteractiveEditorProvider(wrapper.handle);
            this._inputProvider.delete(wrapper.handle);
        });
    }
    async $prepareSession(handle, uri, range, token) {
        const entry = this._inputProvider.get(handle);
        if (!entry) {
            this._logService.warn('CANNOT prepare session because the PROVIDER IS GONE');
            return undefined;
        }
        const document = this._documents.getDocument(URI.revive(uri));
        const selection = Selection$1.to(range);
        const session = await entry.provider.prepareInteractiveEditorSession({ document, selection }, token);
        if (!session) {
            return undefined;
        }
        if (session.wholeRange && !session.wholeRange.contains(selection)) {
            throw new Error(`InteractiveEditorSessionProvider returned a wholeRange that does not contain the selection.`);
        }
        const id = ExtHostInteractiveEditor._nextId++;
        this._inputSessions.set(id, ( new SessionWrapper(session)));
        return {
            id,
            placeholder: session.placeholder,
            input: session.input,
            slashCommands: session.slashCommands?.map(c => ({ command: c.command, detail: c.detail, refer: c.refer, executeImmediately: c.executeImmediately })),
            wholeRange: Range.from(session.wholeRange),
            message: session.message
        };
    }
    async $provideResponse(handle, item, request, token) {
        const entry = this._inputProvider.get(handle);
        if (!entry) {
            return undefined;
        }
        const sessionData = this._inputSessions.get(item.id);
        if (!sessionData) {
            return;
        }
        const apiRequest = {
            prompt: request.prompt,
            selection: Selection$1.to(request.selection),
            wholeRange: Range.to(request.wholeRange),
            attempt: request.attempt,
            live: request.live,
            previewDocument: this._documents.getDocument(URI.revive(request.previewDocument)),
            withIntentDetection: request.withIntentDetection,
        };
        let done = false;
        const progress = {
            report: async (value) => {
                if (!request.live && value.edits?.length) {
                    throw new Error('Progress reporting is only supported for live sessions');
                }
                if (done || token.isCancellationRequested) {
                    return;
                }
                await this._proxy.$handleProgressChunk(request.requestId, {
                    message: value.message,
                    edits: value.edits?.map(TextEdit.from),
                    editsShouldBeInstant: value.editsShouldBeInstant,
                    slashCommand: value.slashCommand?.command,
                    markdownFragment: MarkdownString$1.isMarkdownString(value.content) ? value.content.value : value.content
                });
            }
        };
        const task = Promise.resolve(entry.provider.provideInteractiveEditorResponse(sessionData.session, apiRequest, progress, token));
        let res;
        try {
            res = await raceCancellation(task, token);
        }
        finally {
            done = true;
        }
        if (!res) {
            return undefined;
        }
        const id = sessionData.responses.push(res) - 1;
        const stub = {
            wholeRange: Range.from(res.wholeRange),
            placeholder: res.placeholder,
        };
        if (!ExtHostInteractiveEditor._isEditResponse(res)) {
            return {
                ...stub,
                id,
                type: "editorEdit" ,
                message: MarkdownString.from(res.contents),
                edits: []
            };
        }
        const { edits, contents } = res;
        const message = contents !== undefined ? MarkdownString.from(contents) : undefined;
        if (edits instanceof WorkspaceEdit) {
            return {
                ...stub,
                id,
                type: "bulkEdit" ,
                edits: WorkspaceEdit$1.from(edits),
                message
            };
        }
        else {
            return {
                ...stub,
                id,
                type: "editorEdit" ,
                edits: ( edits.map(TextEdit.from)),
                message
            };
        }
    }
    async $provideFollowups(handle, sessionId, responseId, token) {
        const entry = this._inputProvider.get(handle);
        const sessionData = this._inputSessions.get(sessionId);
        const response = sessionData?.responses[responseId];
        if (entry && response && entry.provider.provideFollowups) {
            const task = Promise.resolve(entry.provider.provideFollowups(sessionData.session, response, token));
            const followups = await raceCancellation(task, token);
            return followups?.map(ChatInlineFollowup.from);
        }
        return undefined;
    }
    $handleFeedback(handle, sessionId, responseId, kind) {
        const entry = this._inputProvider.get(handle);
        const sessionData = this._inputSessions.get(sessionId);
        const response = sessionData?.responses[responseId];
        if (entry && response) {
            const apiKind = InteractiveEditorResponseFeedbackKind.to(kind);
            entry.provider.handleInteractiveEditorResponseFeedback?.(sessionData.session, response, apiKind);
        }
    }
    $releaseSession(handle, sessionId) {
    }
    static _isEditResponse(thing) {
        return typeof thing === 'object' && typeof thing.edits === 'object';
    }
}
export { ExtHostInteractiveEditor };
