import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import { CancellationError } from '../../../../base/common/errors.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { DisposableStore, toDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
import { Schemas } from '../../../../base/common/network.js';
import { generateUuid } from '../../../../base/common/uuid.js';
import { Range } from '../../../../editor/common/core/range.js';
import { createTextBufferFactoryFromSnapshot } from '../../../../editor/common/model/textModel.js';
import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../../platform/log/common/log.service.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.service.js';
import { DEFAULT_EDITOR_ASSOCIATION } from '../../../common/editor.js';
import { ChatAgentLocation } from '../../chat/common/chatAgents.js';
import { IChatAgentService } from '../../chat/common/chatAgents.service.js';
import { IChatService } from '../../chat/common/chatService.service.js';
import { CTX_INLINE_CHAT_HAS_AGENT } from '../common/inlineChat.js';
import { IEditorService } from '../../../services/editor/common/editorService.service.js';
import { UntitledTextEditorInput } from '../../../services/untitled/common/untitledTextEditorInput.js';
import { ReplyResponse, SessionExchange, Session, SessionWholeRange, HunkData, StashedSession, ErrorResponse, EmptyResponse } from './inlineChatSession.js';
import { IChatVariablesService } from '../../chat/common/chatVariables.service.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.service.js';
class InlineChatError extends Error {
    static { this.code = 'InlineChatError'; }
    constructor(message) {
        super(message);
        this.name = InlineChatError.code;
    }
}
const _inlineChatContext = '_inlineChatContext';
const _inlineChatDocument = '_inlineChatDocument';
class InlineChatContext {
    static { this.variableName = '_inlineChatContext'; }
    constructor(uri, selection, wholeRange) {
        this.uri = uri;
        this.selection = selection;
        this.wholeRange = wholeRange;
    }
}
let InlineChatSessionServiceImpl = class InlineChatSessionServiceImpl {
    constructor(_telemetryService, _modelService, _textModelService, _editorWorkerService, _logService, _instaService, _editorService, _chatService, _chatAgentService, chatVariableService) {
        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 = [];
        this._store.add(chatVariableService.registerVariable({ id: _inlineChatContext, name: _inlineChatContext, description: '', hidden: true }, async (_message, _arg, model) => {
            for (const [, data] of this._sessions) {
                if (data.session.chatModel === model) {
                    return JSON.stringify(( new InlineChatContext(
                        data.session.textModelN.uri,
                        data.editor.getSelection(),
                        data.session.wholeRange.trackedInitialRange
                    )));
                }
            }
            return undefined;
        }));
        this._store.add(chatVariableService.registerVariable({ id: _inlineChatDocument, name: _inlineChatDocument, description: '', hidden: true }, async (_message, _arg, model) => {
            for (const [, data] of this._sessions) {
                if (data.session.chatModel === model) {
                    return data.session.textModelN.uri;
                }
            }
            return undefined;
        }));
    }
    dispose() {
        this._store.dispose();
        this._sessions.forEach(x => x.store.dispose());
        this._sessions.clear();
    }
    async createSession(editor, options, token) {
        const agent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Editor);
        if (!agent) {
            this._logService.trace('[IE] NO agent found');
            return undefined;
        }
        this._onWillStartSession.fire(editor);
        const textModel = editor.getModel();
        const selection = editor.getSelection();
        const store = ( new DisposableStore());
        this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${agent.extensionId}`);
        const chatModel = this._chatService.startSession(ChatAgentLocation.Editor, token);
        if (!chatModel) {
            this._logService.trace('[IE] NO chatModel found');
            return undefined;
        }
        store.add(toDisposable(() => {
            this._chatService.clearSession(chatModel.sessionId);
            chatModel.dispose();
        }));
        const lastResponseListener = store.add(( new MutableDisposable()));
        store.add(chatModel.onDidChange(e => {
            if (e.kind !== 'addRequest' || !e.request.response) {
                return;
            }
            const { response } = e.request;
            lastResponseListener.value = response.onDidChange(() => {
                if (!response.isComplete) {
                    return;
                }
                lastResponseListener.clear();
                let inlineResponse;
                if (response.isCanceled) {
                    inlineResponse = ( new ErrorResponse(( new CancellationError())));
                }
                else if (response.result?.errorDetails) {
                    inlineResponse = ( new ErrorResponse(( new Error(response.result.errorDetails.message))));
                }
                else if (response.response.value.length === 0) {
                    inlineResponse = ( new EmptyResponse());
                }
                else {
                    inlineResponse = this._instaService.createInstance(ReplyResponse, session.textModelN.uri, e.request, response);
                }
                session.addExchange(( new SessionExchange(session.lastInput, inlineResponse)));
                if (inlineResponse instanceof ReplyResponse && inlineResponse.untitledTextModel) {
                    this._textModelService.createModelReference(inlineResponse.untitledTextModel.resource).then(ref => {
                        store.add(ref);
                    });
                }
            });
        }));
        store.add(this._chatAgentService.onDidChangeAgents(e => {
            if (e === undefined && !this._chatAgentService.getAgent(agent.id)) {
                this._logService.trace(`[IE] provider GONE for ${editor.getId()}, ${agent.extensionId}`);
                this._releaseSession(session, true);
            }
        }));
        const id = generateUuid();
        const targetUri = textModel.uri;
        store.add((await this._textModelService.createModelReference(textModel.uri)));
        const 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 = ( new Range(
                selection.selectionStartLineNumber,
                selection.selectionStartColumn,
                selection.positionLineNumber,
                selection.positionColumn
            ));
        }
        if (token.isCancellationRequested) {
            store.dispose();
            return undefined;
        }
        const session = ( new Session(
            options.editMode,
            targetUri,
            textModel0,
            textModelN,
            agent,
            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.agent.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.agent.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.agent.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, ITelemetryService)),
    ( __param(1, IModelService)),
    ( __param(2, ITextModelService)),
    ( __param(3, IEditorWorkerService)),
    ( __param(4, ILogService)),
    ( __param(5, IInstantiationService)),
    ( __param(6, IEditorService)),
    ( __param(7, IChatService)),
    ( __param(8, IChatAgentService)),
    ( __param(9, IChatVariablesService))
], InlineChatSessionServiceImpl));
let InlineChatEnabler = class InlineChatEnabler {
    static { this.Id = 'inlineChat.enabler'; }
    constructor(contextKeyService, chatAgentService) {
        this._store = ( new DisposableStore());
        this._ctxHasProvider = CTX_INLINE_CHAT_HAS_AGENT.bindTo(contextKeyService);
        this._store.add(chatAgentService.onDidChangeAgents(() => {
            const hasEditorAgent = Boolean(chatAgentService.getDefaultAgent(ChatAgentLocation.Editor));
            this._ctxHasProvider.set(hasEditorAgent);
        }));
    }
    dispose() {
        this._ctxHasProvider.reset();
        this._store.dispose();
    }
};
InlineChatEnabler = ( __decorate([
    ( __param(0, IContextKeyService)),
    ( __param(1, IChatAgentService))
], InlineChatEnabler));
export { InlineChatEnabler, InlineChatError, InlineChatSessionServiceImpl };
