import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { IInlineChatService } from '../common/inlineChat.js';
import { Range } from '../../../../editor/common/core/range.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
import { DisposableStore, DisposableMap, toDisposable } from '../../../../base/common/lifecycle.js';
import { createTextBufferFactoryFromSnapshot } from '../../../../editor/common/model/textModel.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { Iterable } from '../../../../base/common/iterator.js';
import { raceCancellation } from '../../../../base/common/async.js';
import { Session, SessionWholeRange, HunkData, StashedSession, ReplyResponse, SessionExchange, EmptyResponse, ErrorResponse } from './inlineChatSession.js';
import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js';
import { Schemas } from '../../../../base/common/network.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { generateUuid } from '../../../../base/common/uuid.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { UntitledTextEditorInput } from '../../../services/untitled/common/untitledTextEditorInput.js';
import { DEFAULT_EDITOR_ASSOCIATION } from '../../../common/editor.js';
import { InteractiveSessionVoteDirection, IChatService } from '../../chat/common/chatService.js';
import { IChatAgentService, ChatAgentLocation } from '../../chat/common/chatAgents.js';
import { Progress } from '../../../../platform/progress/common/progress.js';
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
import { coalesceInPlace, isNonEmptyArray } from '../../../../base/common/arrays.js';
import { MarkdownString } from '../../../../base/common/htmlContent.js';
import { nullExtensionDescription } from '../../../services/extensions/common/extensions.js';
import { Codicon } from '../../../../base/common/codicons.js';
let BridgeAgent = class BridgeAgent {
    constructor(_data, _sessions, _instaService) {
        this._data = _data;
        this._sessions = _sessions;
        this._instaService = _instaService;
    }
    _findSessionDataByRequest(request) {
        let data;
        for (const candidate of ( this._sessions.values())) {
            if (candidate.session.chatModel.sessionId === request.sessionId) {
                data = candidate;
                break;
            }
        }
        return data;
    }
    async invoke(request, progress, history, token) {
        if (token.isCancellationRequested) {
            return {};
        }
        const data = this._findSessionDataByRequest(request);
        if (!data) {
            throw new Error('FAILED to find session');
        }
        const { session, editor } = data;
        if (!session.lastInput) {
            throw new Error('FAILED to find last input');
        }
        const modelAltVersionIdNow = session.textModelN.getAlternativeVersionId();
        const progressEdits = [];
        const inlineRequest = {
            requestId: request.requestId,
            prompt: request.message,
            attempt: session.lastInput.attempt,
            withIntentDetection: session.lastInput.withIntentDetection,
            live: session.editMode !== "preview" ,
            previewDocument: session.textModelN.uri,
            selection: editor.getSelection(),
            wholeRange: session.wholeRange.trackedInitialRange,
        };
        const inlineProgress = ( new Progress(data => {
            if (data.slashCommand && !inlineRequest.prompt.startsWith('/')) {
                const command = this._data.slashCommands.find(c => c.name === data.slashCommand);
                progress({ kind: 'agentDetection', agentId: this._data.id, command });
            }
            if (data.markdownFragment) {
                progress({ kind: 'markdownContent', content: ( new MarkdownString(data.markdownFragment)) });
            }
            if (isNonEmptyArray(data.edits)) {
                progressEdits.push(data.edits);
                progress({ kind: 'textEdit', uri: session.textModelN.uri, edits: data.edits });
            }
        }));
        let result;
        let response;
        try {
            result = await data.session.provider.provideResponse(session.session, inlineRequest, inlineProgress, token);
            if (result) {
                if (result.message) {
                    inlineProgress.report({ markdownFragment: result.message.value });
                }
                if (Array.isArray(result.edits)) {
                    inlineProgress.report({ edits: result.edits });
                }
                const markdownContents = result.message ?? ( new MarkdownString('', { supportThemeIcons: true, supportHtml: true, isTrusted: false }));
                response = this._instaService.createInstance(ReplyResponse, result, markdownContents, session.textModelN.uri, modelAltVersionIdNow, progressEdits, request.requestId);
            }
            else {
                response = ( new EmptyResponse());
            }
        }
        catch (e) {
            response = ( new ErrorResponse(e));
        }
        session.addExchange(( new SessionExchange(session.lastInput, response)));
        return { metadata: { inlineChatResponse: result } };
    }
    async provideFollowups(request, result, history, token) {
        if (!result.metadata?.inlineChatResponse) {
            return [];
        }
        const data = this._findSessionDataByRequest(request);
        if (!data) {
            return [];
        }
        const inlineFollowups = await data.session.provider.provideFollowups?.(data.session.session, result.metadata?.inlineChatResponse, token);
        if (!inlineFollowups) {
            return [];
        }
        const chatFollowups = ( inlineFollowups.map(f => {
            if (f.kind === 'reply') {
                return {
                    kind: 'reply',
                    message: f.message,
                    agentId: request.agentId,
                    title: f.title,
                    tooltip: f.tooltip,
                };
            }
            else {
                return undefined;
            }
        }));
        coalesceInPlace(chatFollowups);
        return chatFollowups;
    }
};
BridgeAgent = ( __decorate([
    ( __param(2, IInstantiationService))
], BridgeAgent));
class InlineChatError extends Error {
    static { this.code = 'InlineChatError'; }
    constructor(message) {
        super(message);
        this.name = InlineChatError.code;
    }
}
let InlineChatSessionServiceImpl = class InlineChatSessionServiceImpl {
    constructor(_inlineChatService, _telemetryService, _modelService, _textModelService, _editorWorkerService, _logService, _instaService, _editorService, _chatService, _chatAgentService) {
        this._inlineChatService = _inlineChatService;
        this._telemetryService = _telemetryService;
        this._modelService = _modelService;
        this._textModelService = _textModelService;
        this._editorWorkerService = _editorWorkerService;
        this._logService = _logService;
        this._instaService = _instaService;
        this._editorService = _editorService;
        this._chatService = _chatService;
        this._chatAgentService = _chatAgentService;
        this._store = ( new DisposableStore());
        this._onWillStartSession = this._store.add(( new Emitter()));
        this.onWillStartSession = this._onWillStartSession.event;
        this._onDidMoveSession = this._store.add(( new Emitter()));
        this.onDidMoveSession = this._onDidMoveSession.event;
        this._onDidEndSession = this._store.add(( new Emitter()));
        this.onDidEndSession = this._onDidEndSession.event;
        this._onDidStashSession = this._store.add(( new Emitter()));
        this.onDidStashSession = this._onDidStashSession.event;
        this._sessions = ( new Map());
        this._keyComputers = ( new Map());
        this._recordings = [];
        const that = this;
        const agentData = {
            id: 'editor',
            name: 'editor',
            extensionId: nullExtensionDescription.identifier,
            isDefault: true,
            locations: [ChatAgentLocation.Editor],
            get slashCommands() {
                let candidate;
                for (const data of ( that._sessions.values())) {
                    if (data.editor.hasWidgetFocus()) {
                        candidate = data.session;
                        break;
                    }
                }
                if (!candidate || !candidate.session.slashCommands) {
                    return [];
                }
                return ( candidate.session.slashCommands.map(c => {
                    return {
                        name: c.command,
                        description: c.detail ?? '',
                    };
                }));
            },
            defaultImplicitVariables: [],
            metadata: {
                isSticky: false,
                themeIcon: Codicon.copilot,
            },
        };
        this._store.add(this._chatAgentService.registerDynamicAgent(agentData, this._instaService.createInstance(BridgeAgent, agentData, this._sessions)));
        const mapping = this._store.add(( new DisposableMap()));
        const registerFakeChatProvider = (provider) => {
            const d = this._chatService.registerProvider({
                id: this._asChatProviderBrigdeName(provider),
                prepareSession() {
                    return {
                        id: Math.random()
                    };
                }
            });
            mapping.set(provider, d);
        };
        this._store.add(_inlineChatService.onDidChangeProviders(e => {
            if (e.added) {
                registerFakeChatProvider(e.added);
            }
            if (e.removed) {
                mapping.deleteAndDispose(e.removed);
            }
        }));
        for (const provider of _inlineChatService.getAllProvider()) {
            registerFakeChatProvider(provider);
        }
    }
    dispose() {
        this._store.dispose();
        this._sessions.forEach(x => x.store.dispose());
        this._sessions.clear();
    }
    _asChatProviderBrigdeName(provider) {
        return `inlinechat:${provider.label}:${ExtensionIdentifier.toKey(provider.extensionId)}`;
    }
    async createSession(editor, options, token) {
        const provider = Iterable.first(this._inlineChatService.getAllProvider());
        if (!provider) {
            this._logService.trace('[IE] NO provider found');
            return undefined;
        }
        const chatModel = this._chatService.startSession(this._asChatProviderBrigdeName(provider), token);
        if (!chatModel) {
            this._logService.trace('[IE] NO chatModel found');
            return undefined;
        }
        const store = ( new DisposableStore());
        store.add(toDisposable(() => {
            this._chatService.clearSession(chatModel.sessionId);
            chatModel.dispose();
        }));
        this._onWillStartSession.fire(editor);
        const textModel = editor.getModel();
        const selection = editor.getSelection();
        let rawSession;
        try {
            rawSession = await raceCancellation(Promise.resolve(provider.prepareInlineChatSession(textModel, selection, token)), token);
        }
        catch (error) {
            this._logService.error('[IE] FAILED to prepare session', provider.extensionId);
            this._logService.error(error);
            throw new InlineChatError(error?.message || 'Failed to prepare session');
        }
        if (!rawSession) {
            this._logService.trace('[IE] NO session', provider.extensionId);
            return undefined;
        }
        this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.extensionId}`);
        store.add(this._chatService.onDidPerformUserAction(e => {
            if (e.sessionId !== chatModel.sessionId || e.action.kind !== 'vote') {
                return;
            }
            const request = chatModel.getRequests().find(request => request.id === e.requestId);
            const candidate = request?.response?.result?.metadata?.inlineChatResponse;
            if (candidate) {
                provider.handleInlineChatResponseFeedback?.(rawSession, candidate, e.action.direction === InteractiveSessionVoteDirection.Down ? 0  : 1 );
            }
        }));
        store.add(this._inlineChatService.onDidChangeProviders(e => {
            if (e.removed === provider) {
                this._logService.trace(`[IE] provider GONE for ${editor.getId()}, ${provider.extensionId}`);
                this._releaseSession(session, true);
            }
        }));
        const id = generateUuid();
        const targetUri = textModel.uri;
        let textModelN;
        if (options.editMode === "preview" ) {
            textModelN = store.add(this._modelService.createModel(createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), { languageId: textModel.getLanguageId(), onDidChange: Event.None }, targetUri.with({ scheme: Schemas.vscode, authority: 'inline-chat', path: '', query: ( ( new URLSearchParams({ id, 'textModelN': '' })).toString()) })));
        }
        else {
            store.add((await this._textModelService.createModelReference(textModel.uri)));
            textModelN = textModel;
        }
        const textModel0 = store.add(this._modelService.createModel(createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), { languageId: textModel.getLanguageId(), onDidChange: Event.None }, targetUri.with({ scheme: Schemas.vscode, authority: 'inline-chat', path: '', query: ( ( new URLSearchParams({ id, 'textModel0': '' })).toString()) }), true));
        if (targetUri.scheme === Schemas.untitled) {
            store.add(this._editorService.onDidCloseEditor(() => {
                if (!this._editorService.isOpened({ resource: targetUri, typeId: UntitledTextEditorInput.ID, editorId: DEFAULT_EDITOR_ASSOCIATION.id })) {
                    this._releaseSession(session, true);
                }
            }));
        }
        let wholeRange = options.wholeRange;
        if (!wholeRange) {
            wholeRange = rawSession.wholeRange ? Range.lift(rawSession.wholeRange) : editor.getSelection();
        }
        if (token.isCancellationRequested) {
            store.dispose();
            return undefined;
        }
        const session = ( new Session(
            options.editMode,
            targetUri,
            textModel0,
            textModelN,
            provider,
            rawSession,
            store.add(( new SessionWholeRange(textModelN, wholeRange))),
            store.add(( new HunkData(this._editorWorkerService, textModel0, textModelN))),
            chatModel
        ));
        const key = this._key(editor, session.targetUri);
        if (( this._sessions.has(key))) {
            store.dispose();
            throw new Error(`Session already stored for ${key}`);
        }
        this._sessions.set(key, { session, editor, store });
        return session;
    }
    moveSession(session, target) {
        const newKey = this._key(target, session.targetUri);
        const existing = this._sessions.get(newKey);
        if (existing) {
            if (existing.session !== session) {
                throw new Error(`Cannot move session because the target editor already/still has one`);
            }
            else {
                return;
            }
        }
        let found = false;
        for (const [oldKey, data] of this._sessions) {
            if (data.session === session) {
                found = true;
                this._sessions.delete(oldKey);
                this._sessions.set(newKey, { ...data, editor: target });
                this._logService.trace(`[IE] did MOVE session for ${data.editor.getId()} to NEW EDITOR ${target.getId()}, ${session.provider.extensionId}`);
                this._onDidMoveSession.fire({ session, editor: target });
                break;
            }
        }
        if (!found) {
            throw new Error(`Cannot move session because it is not stored`);
        }
    }
    releaseSession(session) {
        this._releaseSession(session, false);
    }
    _releaseSession(session, byServer) {
        let tuple;
        for (const candidate of this._sessions) {
            if (candidate[1].session === session) {
                tuple = candidate;
                break;
            }
        }
        if (!tuple) {
            return;
        }
        this._keepRecording(session);
        this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData());
        const [key, value] = tuple;
        this._sessions.delete(key);
        this._logService.trace(`[IE] did RELEASED session for ${value.editor.getId()}, ${session.provider.extensionId}`);
        this._onDidEndSession.fire({ editor: value.editor, session, endedByExternalCause: byServer });
        value.store.dispose();
    }
    stashSession(session, editor, undoCancelEdits) {
        this._keepRecording(session);
        const result = this._instaService.createInstance(StashedSession, editor, session, undoCancelEdits);
        this._onDidStashSession.fire({ editor, session });
        this._logService.trace(`[IE] did STASH session for ${editor.getId()}, ${session.provider.extensionId}`);
        return result;
    }
    getCodeEditor(session) {
        for (const [, data] of this._sessions) {
            if (data.session === session) {
                return data.editor;
            }
        }
        throw new Error('session not found');
    }
    getSession(editor, uri) {
        const key = this._key(editor, uri);
        return this._sessions.get(key)?.session;
    }
    _key(editor, uri) {
        const item = this._keyComputers.get(uri.scheme);
        return item
            ? item.getComparisonKey(editor, uri)
            : `${editor.getId()}@${( uri.toString())}`;
    }
    registerSessionKeyComputer(scheme, value) {
        this._keyComputers.set(scheme, value);
        return toDisposable(() => this._keyComputers.delete(scheme));
    }
    _keepRecording(session) {
        const newLen = this._recordings.unshift(session.asRecording());
        if (newLen > 5) {
            this._recordings.pop();
        }
    }
    recordings() {
        return this._recordings;
    }
};
InlineChatSessionServiceImpl = ( __decorate([
    ( __param(0, IInlineChatService)),
    ( __param(1, ITelemetryService)),
    ( __param(2, IModelService)),
    ( __param(3, ITextModelService)),
    ( __param(4, IEditorWorkerService)),
    ( __param(5, ILogService)),
    ( __param(6, IInstantiationService)),
    ( __param(7, IEditorService)),
    ( __param(8, IChatService)),
    ( __param(9, IChatAgentService))
], InlineChatSessionServiceImpl));
export { InlineChatError, InlineChatSessionServiceImpl };
