import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import { alert } from '../../../../base/browser/ui/aria/aria.js';
import { Barrier, DeferredPromise, Queue } from '../../../../base/common/async.js';
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
import { onUnexpectedError } from '../../../../base/common/errors.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { Lazy } from '../../../../base/common/lazy.js';
import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { MovingAverage } from '../../../../base/common/numbers.js';
import { StopWatch } from '../../../../base/common/stopwatch.js';
import { assertType } from '../../../../base/common/types.js';
import { Position } from '../../../../editor/common/core/position.js';
import { Range } from '../../../../editor/common/core/range.js';
import { Selection } from '../../../../editor/common/core/selection.js';
import { CompletionItemKind, TextEdit } from '../../../../editor/common/languages.js';
import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js';
import { InlineCompletionsController } from '../../../../editor/contrib/inlineCompletions/browser/inlineCompletionsController.js';
import { localizeWithPath } from '../../../../nls.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.service.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.service.js';
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.service.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../../platform/log/common/log.service.js';
import { showChatView } from '../../chat/browser/chat.js';
import { IChatWidgetService } from '../../chat/browser/chat.service.js';
import { IChatService } from '../../chat/common/chatService.service.js';
import { IInlineChatSavingService } from './inlineChatSavingService.service.js';
import { SessionPrompt, EmptyResponse, ErrorResponse, ReplyResponse, Session } from './inlineChatSession.js';
import { IInlineChatSessionService } from './inlineChatSessionService.service.js';
import { LiveStrategy, PreviewStrategy } from './inlineChatStrategies.js';
import { InlineChatZoneWidget } from './inlineChatZoneWidget.js';
import { INLINE_CHAT_ID, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, InlineChatConfigKeys, EditMode, InlineChatResponseType } from '../common/inlineChat.js';
import { InlineChatContentWidget } from './inlineChatContentWidget.js';
import { MessageController } from '../../../../editor/contrib/message/browser/messageController.js';
import { ChatRequestRemovalReason } from '../../chat/common/chatModel.js';
import { InlineChatError } from './inlineChatSessionServiceImpl.js';
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
import { ChatInputPart } from '../../chat/browser/chatInputPart.js';
import { IViewsService } from '../../../services/views/common/viewsService.service.js';
import { DefaultModelSHA1Computer } from '../../../../editor/common/services/modelService.js';
import { generateUuid } from '../../../../base/common/uuid.js';
import { isEqual } from '../../../../base/common/resources.js';
import { ChatAgentLocation } from '../../chat/common/chatAgents.js';
import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.service.js';
import { escapeRegExpCharacters } from '../../../../base/common/strings.js';
const _moduleId = "vs/workbench/contrib/inlineChat/browser/inlineChatController";
var State;
( ((function(State) {
    State["CREATE_SESSION"] = "CREATE_SESSION";
    State["INIT_UI"] = "INIT_UI";
    State["WAIT_FOR_INPUT"] = "WAIT_FOR_INPUT";
    State["SHOW_REQUEST"] = "SHOW_REQUEST";
    State["SHOW_RESPONSE"] = "SHOW_RESPONSE";
    State["PAUSE"] = "PAUSE";
    State["CANCEL"] = "CANCEL";
    State["ACCEPT"] = "DONE";
})(State || (State = {}))));
var Message;
( ((function(Message) {
    Message[Message["NONE"] = 0] = "NONE";
    Message[Message["ACCEPT_SESSION"] = 1] = "ACCEPT_SESSION";
    Message[Message["CANCEL_SESSION"] = 2] = "CANCEL_SESSION";
    Message[Message["PAUSE_SESSION"] = 4] = "PAUSE_SESSION";
    Message[Message["CANCEL_REQUEST"] = 8] = "CANCEL_REQUEST";
    Message[Message["CANCEL_INPUT"] = 16] = "CANCEL_INPUT";
    Message[Message["ACCEPT_INPUT"] = 32] = "ACCEPT_INPUT";
})(Message || (Message = {}))));
class InlineChatRunOptions {
    static isInlineChatRunOptions(options) {
        const { initialSelection, initialRange, message, autoSend, position, existingSession } = options;
        if (typeof message !== 'undefined' && typeof message !== 'string'
            || typeof autoSend !== 'undefined' && typeof autoSend !== 'boolean'
            || typeof initialRange !== 'undefined' && !Range.isIRange(initialRange)
            || typeof initialSelection !== 'undefined' && !Selection.isISelection(initialSelection)
            || typeof position !== 'undefined' && !Position.isIPosition(position)
            || typeof existingSession !== 'undefined' && !(existingSession instanceof Session)) {
            return false;
        }
        return true;
    }
}
let InlineChatController = class InlineChatController {
    static get(editor) {
        return editor.getContribution(INLINE_CHAT_ID);
    }
    get chatWidget() {
        if (this._ui.value.content.isVisible) {
            return this._ui.value.content.chatWidget;
        }
        else {
            return this._ui.value.zone.widget.chatWidget;
        }
    }
    constructor(_editor, _instaService, _inlineChatSessionService, _inlineChatSavingService, _editorWorkerService, _logService, _configurationService, _dialogService, contextKeyService, _chatService, _languageFeatureService, _chatWidgetService, notebookEditorService) {
        this._editor = _editor;
        this._instaService = _instaService;
        this._inlineChatSessionService = _inlineChatSessionService;
        this._inlineChatSavingService = _inlineChatSavingService;
        this._editorWorkerService = _editorWorkerService;
        this._logService = _logService;
        this._configurationService = _configurationService;
        this._dialogService = _dialogService;
        this._chatService = _chatService;
        this._languageFeatureService = _languageFeatureService;
        this._chatWidgetService = _chatWidgetService;
        this._isDisposed = false;
        this._store = ( (new DisposableStore()));
        this._messages = this._store.add(( (new Emitter())));
        this._onWillStartSession = this._store.add(( (new Emitter())));
        this.onWillStartSession = this._onWillStartSession.event;
        this._sessionStore = this._store.add(( (new DisposableStore())));
        this._stashedSession = this._store.add(( (new MutableDisposable())));
        this._forcedPlaceholder = undefined;
        this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService);
        this._ctxUserDidEdit = CTX_INLINE_CHAT_USER_DID_EDIT.bindTo(contextKeyService);
        this._ctxResponseType = CTX_INLINE_CHAT_RESPONSE_TYPE.bindTo(contextKeyService);
        this._ctxRequestInProgress = CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService);
        this._ui = ( (new Lazy(() => {
            let location = ChatAgentLocation.Editor;
            for (const notebookEditor of notebookEditorService.listNotebookEditors()) {
                for (const [, codeEditor] of notebookEditor.codeEditors) {
                    if (codeEditor === this._editor) {
                        location = ChatAgentLocation.Notebook;
                        break;
                    }
                }
            }
            const content = this._store.add(_instaService.createInstance(InlineChatContentWidget, location, this._editor));
            const zone = this._store.add(_instaService.createInstance(InlineChatZoneWidget, location, this._editor));
            return { content, zone };
        })));
        this._store.add(this._editor.onDidChangeModel(async (e) => {
            if (this._session || !e.newModelUrl) {
                return;
            }
            const existingSession = this._inlineChatSessionService.getSession(this._editor, e.newModelUrl);
            if (!existingSession) {
                return;
            }
            this._log('session RESUMING after model change', e);
            await this.run({ existingSession });
        }));
        this._store.add(this._inlineChatSessionService.onDidEndSession(e => {
            if (e.session === this._session && e.endedByExternalCause) {
                this._log('session ENDED by external cause');
                this._session = undefined;
                this._strategy?.cancel();
                this._resetWidget();
                this.cancelSession();
            }
        }));
        this._store.add(this._inlineChatSessionService.onDidMoveSession(async (e) => {
            if (e.editor === this._editor) {
                this._log('session RESUMING after move', e);
                await this.run({ existingSession: e.session });
            }
        }));
        this._log('NEW controller');
    }
    dispose() {
        if (this._currentRun) {
            this._messages.fire(this._session?.chatModel.hasRequests
                ? Message.PAUSE_SESSION
                : Message.CANCEL_SESSION);
        }
        this._store.dispose();
        this._isDisposed = true;
        this._log('DISPOSED controller');
    }
    _log(message, ...more) {
        if (message instanceof Error) {
            this._logService.error(message, ...more);
        }
        else {
            this._logService.trace(`[IE] (editor:${this._editor.getId()})${message}`, ...more);
        }
    }
    getMessage() {
        return this._ui.value.zone.widget.responseContent;
    }
    getId() {
        return INLINE_CHAT_ID;
    }
    _getMode() {
        return this._configurationService.getValue(InlineChatConfigKeys.Mode);
    }
    getWidgetPosition() {
        return this._ui.value.zone.position;
    }
    async run(options = {}) {
        try {
            this.finishExistingSession();
            if (this._currentRun) {
                await this._currentRun;
            }
            if (options.initialSelection) {
                this._editor.setSelection(options.initialSelection);
            }
            this._stashedSession.clear();
            this._onWillStartSession.fire();
            this._currentRun = this._nextState(State.CREATE_SESSION, options);
            await this._currentRun;
        }
        catch (error) {
            onUnexpectedError(error);
            if (this._session) {
                this._inlineChatSessionService.releaseSession(this._session);
            }
            this[State.PAUSE]();
        }
        finally {
            this._currentRun = undefined;
        }
    }
    async _nextState(state, options) {
        let nextState = state;
        while (nextState && !this._isDisposed) {
            this._log('setState to ', nextState);
            nextState = await this[nextState](options);
        }
    }
    async [State.CREATE_SESSION](options) {
        assertType(this._session === undefined);
        assertType(this._editor.hasModel());
        let session = options.existingSession;
        let initPosition;
        if (options.position) {
            initPosition = Position.lift(options.position).delta(-1);
            delete options.position;
        }
        const widgetPosition = this._showWidget(true, initPosition);
        let errorMessage = ( localizeWithPath(_moduleId, 0, "Failed to start editor chat"));
        if (!session) {
            const createSessionCts = ( (new CancellationTokenSource()));
            const msgListener = Event.once(this._messages.event)(m => {
                this._log('state=_createSession) message received', m);
                if (m === Message.ACCEPT_INPUT) {
                    options.autoSend = true;
                    this._ui.value.zone.widget.updateInfo(( localizeWithPath(_moduleId, 1, "Getting ready...")));
                }
                else {
                    createSessionCts.cancel();
                }
            });
            try {
                session = await this._inlineChatSessionService.createSession(this._editor, { editMode: this._getMode(), wholeRange: options.initialRange }, createSessionCts.token);
            }
            catch (error) {
                if (error instanceof InlineChatError || error?.name === InlineChatError.code) {
                    errorMessage = error.message;
                }
            }
            createSessionCts.dispose();
            msgListener.dispose();
            if (createSessionCts.token.isCancellationRequested) {
                if (session) {
                    this._inlineChatSessionService.releaseSession(session);
                }
                return State.CANCEL;
            }
        }
        delete options.initialRange;
        delete options.existingSession;
        if (!session) {
            MessageController.get(this._editor)?.showMessage(errorMessage, widgetPosition);
            this._log('Failed to start editor chat');
            return State.CANCEL;
        }
        await session.chatModel.waitForInitialization();
        switch (session.editMode) {
            case EditMode.Preview:
                this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._editor, this._ui.value.zone);
                break;
            case EditMode.Live:
            default:
                this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._ui.value.zone);
                break;
        }
        this._session = session;
        return State.INIT_UI;
    }
    async [State.INIT_UI](options) {
        assertType(this._session);
        assertType(this._strategy);
        InlineCompletionsController.get(this._editor)?.hide();
        this._sessionStore.clear();
        const wholeRangeDecoration = this._editor.createDecorationsCollection();
        const updateWholeRangeDecoration = () => {
            const newDecorations = this._strategy?.getWholeRangeDecoration() ?? [];
            wholeRangeDecoration.set(newDecorations);
        };
        this._sessionStore.add(toDisposable(() => wholeRangeDecoration.clear()));
        this._sessionStore.add(this._session.wholeRange.onDidChange(updateWholeRangeDecoration));
        updateWholeRangeDecoration();
        this._sessionStore.add(this._ui.value.content.onDidBlur(() => this.cancelSession()));
        this._ui.value.content.setSession(this._session);
        this._updatePlaceholder();
        this._showWidget(!this._session.chatModel.hasRequests);
        this._ui.value.zone.widget.updateToolbar(true);
        this._sessionStore.add(this._editor.onDidChangeModel((e) => {
            const msg = this._session?.chatModel.hasRequests
                ? Message.PAUSE_SESSION
                : Message.CANCEL_SESSION;
            this._log('model changed, pause or cancel session', msg, e);
            this._messages.fire(msg);
        }));
        const altVersionNow = this._editor.getModel()?.getAlternativeVersionId();
        this._sessionStore.add(this._editor.onDidChangeModelContent(e => {
            if (!this._session?.hunkData.ignoreTextModelNChanges) {
                this._ctxUserDidEdit.set(altVersionNow !== this._editor.getModel()?.getAlternativeVersionId());
            }
            if (this._session?.hunkData.ignoreTextModelNChanges || this._strategy?.hasFocus()) {
                return;
            }
            const wholeRange = this._session.wholeRange;
            let shouldFinishSession = false;
            if (this._configurationService.getValue(InlineChatConfigKeys.FinishOnType)) {
                for (const { range } of e.changes) {
                    shouldFinishSession = !Range.areIntersectingOrTouching(range, wholeRange.value);
                }
            }
            this._session.recordExternalEditOccurred(shouldFinishSession);
            if (shouldFinishSession) {
                this._log('text changed outside of whole range, FINISH session');
                this.finishExistingSession();
            }
        }));
        this._sessionStore.add(this._session.chatModel.onDidChange(async (e) => {
            if (e.kind === 'removeRequest') {
                await this._session.undoChangesUntil(e.requestId);
            }
        }));
        this._sessionStore.add(this._languageFeatureService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, {
            _debugDisplayName: 'inline chat commands',
            triggerCharacters: ['/'],
            provideCompletionItems: (model, position, context, token) => {
                if (position.lineNumber !== 1) {
                    return undefined;
                }
                if (!this._session || !this._session.agent.slashCommands) {
                    return undefined;
                }
                const widget = this._chatWidgetService.getWidgetByInputUri(model.uri);
                if (widget !== this._ui.value.zone.widget.chatWidget && widget !== this._ui.value.content.chatWidget) {
                    return undefined;
                }
                const result = { suggestions: [], incomplete: false };
                for (const command of this._session.agent.slashCommands) {
                    const withSlash = `/${command.name}`;
                    result.suggestions.push({
                        label: { label: withSlash, description: command.description ?? '' },
                        kind: CompletionItemKind.Text,
                        insertText: `${withSlash} `,
                        range: Range.fromPositions(( (new Position(1, 1))), position),
                    });
                }
                return result;
            }
        }));
        const updateSlashDecorations = (collection, model) => {
            const newDecorations = [];
            for (const command of (this._session?.agent.slashCommands ?? []).sort((a, b) => b.name.length - a.name.length)) {
                const withSlash = `/${command.name}`;
                const firstLine = model.getLineContent(1);
                if (firstLine.match(( (new RegExp(`^${escapeRegExpCharacters(withSlash)}(\\s|$)`))))) {
                    newDecorations.push({
                        range: ( (new Range(1, 1, 1, withSlash.length + 1))),
                        options: {
                            description: 'inline-chat-slash-command',
                            inlineClassName: 'inline-chat-slash-command',
                        }
                    });
                    if (firstLine.trim() === `/${command.name}`) {
                        newDecorations.push({
                            range: ( (new Range(1, withSlash.length, 1, withSlash.length))),
                            options: {
                                description: 'inline-chat-slash-command-detail',
                                after: {
                                    content: `${command.description}`,
                                    inlineClassName: 'inline-chat-slash-command-detail'
                                }
                            }
                        });
                    }
                    break;
                }
            }
            collection.set(newDecorations);
        };
        const inputInputEditor = this._ui.value.content.chatWidget.inputEditor;
        const zoneInputEditor = this._ui.value.zone.widget.chatWidget.inputEditor;
        const inputDecorations = inputInputEditor.createDecorationsCollection();
        const zoneDecorations = zoneInputEditor.createDecorationsCollection();
        this._sessionStore.add(inputInputEditor.onDidChangeModelContent(() => updateSlashDecorations(inputDecorations, inputInputEditor.getModel())));
        this._sessionStore.add(zoneInputEditor.onDidChangeModelContent(() => updateSlashDecorations(zoneDecorations, zoneInputEditor.getModel())));
        this._sessionStore.add(toDisposable(() => {
            inputDecorations.clear();
            zoneDecorations.clear();
        }));
        if (!this._session.chatModel.hasRequests) {
            return State.WAIT_FOR_INPUT;
        }
        else if (options.isUnstashed) {
            delete options.isUnstashed;
            return State.SHOW_RESPONSE;
        }
        else {
            return State.SHOW_RESPONSE;
        }
    }
    async [State.WAIT_FOR_INPUT](options) {
        assertType(this._session);
        assertType(this._strategy);
        this._updatePlaceholder();
        if (options.message) {
            this.updateInput(options.message);
            alert(options.message);
            delete options.message;
            this._showWidget(false);
        }
        let message = Message.NONE;
        let request;
        const barrier = ( (new Barrier()));
        const store = ( (new DisposableStore()));
        store.add(this._session.chatModel.onDidChange(e => {
            if (e.kind === 'addRequest') {
                request = e.request;
                message = Message.ACCEPT_INPUT;
                barrier.open();
            }
        }));
        store.add(this._strategy.onDidAccept(() => this.acceptSession()));
        store.add(this._strategy.onDidDiscard(() => this.cancelSession()));
        store.add(Event.once(this._messages.event)(m => {
            this._log('state=_waitForInput) message received', m);
            message = m;
            barrier.open();
        }));
        if (options.autoSend) {
            delete options.autoSend;
            this._showWidget(false);
            this._ui.value.zone.widget.chatWidget.acceptInput();
        }
        await barrier.wait();
        store.dispose();
        if (message & (Message.CANCEL_INPUT | Message.CANCEL_SESSION)) {
            return State.CANCEL;
        }
        if (message & Message.PAUSE_SESSION) {
            return State.PAUSE;
        }
        if (message & Message.ACCEPT_SESSION) {
            this._ui.value.zone.widget.selectAll(false);
            return State.ACCEPT;
        }
        if (!request?.message.text) {
            return State.WAIT_FOR_INPUT;
        }
        this._session.addInput(( (new SessionPrompt(request, this._editor.getModel().getAlternativeVersionId()))));
        return State.SHOW_REQUEST;
    }
    async [State.SHOW_REQUEST]() {
        assertType(this._session);
        assertType(this._session.chatModel.requestInProgress);
        this._ctxRequestInProgress.set(true);
        const { chatModel } = this._session;
        const request = chatModel.getRequests().at(-1);
        assertType(request);
        assertType(request.response);
        this._showWidget(false);
        this._ui.value.zone.widget.selectAll(false);
        this._ui.value.zone.widget.updateInfo('');
        const { response } = request;
        const responsePromise = ( (new DeferredPromise()));
        const store = ( (new DisposableStore()));
        const progressiveEditsCts = store.add(( (new CancellationTokenSource())));
        const progressiveEditsAvgDuration = ( (new MovingAverage()));
        const progressiveEditsClock = StopWatch.create();
        const progressiveEditsQueue = ( (new Queue()));
        let next = State.SHOW_RESPONSE;
        store.add(Event.once(this._messages.event)(message => {
            this._log('state=_makeRequest) message received', message);
            this._chatService.cancelCurrentRequestForSession(chatModel.sessionId);
            if (message & Message.CANCEL_SESSION) {
                next = State.CANCEL;
            }
            else if (message & Message.PAUSE_SESSION) {
                next = State.PAUSE;
            }
            else if (message & Message.ACCEPT_SESSION) {
                next = State.ACCEPT;
            }
        }));
        store.add(chatModel.onDidChange(e => {
            if (e.kind === 'removeRequest' && e.requestId === request.id) {
                progressiveEditsCts.cancel();
                responsePromise.complete();
                if (e.reason === ChatRequestRemovalReason.Resend) {
                    next = State.SHOW_REQUEST;
                }
                else {
                    next = State.CANCEL;
                }
            }
        }));
        store.add(this._ui.value.zone.widget.chatWidget.inputEditor.onDidChangeModelContent(() => {
            this._chatService.cancelCurrentRequestForSession(chatModel.sessionId);
        }));
        let lastLength = 0;
        let isFirstChange = true;
        const sha1 = ( (new DefaultModelSHA1Computer()));
        const textModel0Sha1 = sha1.canComputeSHA1(this._session.textModel0)
            ? sha1.computeSHA1(this._session.textModel0)
            : generateUuid();
        const editState = { sha1: textModel0Sha1, applied: 0 };
        let localEditGroup;
        const handleResponse = () => {
            this._updateCtxResponseType();
            if (!localEditGroup) {
                localEditGroup = response.response.value.find(part => part.kind === 'textEditGroup' && isEqual(part.uri, this._session?.textModelN.uri));
            }
            if (localEditGroup) {
                localEditGroup.state ??= editState;
                const edits = localEditGroup.edits;
                const newEdits = edits.slice(lastLength);
                if (newEdits.length > 0) {
                    lastLength = edits.length;
                    progressiveEditsAvgDuration.update(progressiveEditsClock.elapsed());
                    progressiveEditsClock.reset();
                    progressiveEditsQueue.queue(async () => {
                        const startThen = this._session.wholeRange.value.getStartPosition();
                        for (const edits of newEdits) {
                            await this._makeChanges(edits, {
                                duration: progressiveEditsAvgDuration.value,
                                token: progressiveEditsCts.token
                            }, isFirstChange);
                            isFirstChange = false;
                        }
                        const startNow = this._session.wholeRange.value.getStartPosition();
                        if (!startNow.equals(startThen) || !this._ui.value.zone.position?.equals(startNow)) {
                            this._showWidget(false, startNow.delta(-1));
                        }
                    });
                }
            }
            if (response.isCanceled) {
                progressiveEditsCts.cancel();
                responsePromise.complete();
            }
            else if (response.isComplete) {
                responsePromise.complete();
            }
        };
        store.add(response.onDidChange(handleResponse));
        handleResponse();
        await responsePromise.p;
        await progressiveEditsQueue.whenIdle();
        if (response.isCanceled) {
            await this._session.undoChangesUntil(response.requestId);
        }
        store.dispose();
        const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { computeMoves: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, ignoreTrimWhitespace: false }, 'advanced');
        this._session.wholeRange.fixup(diff?.changes ?? []);
        await this._session.hunkData.recompute(editState, diff);
        this._ctxRequestInProgress.set(false);
        return next;
    }
    async [State.SHOW_RESPONSE]() {
        assertType(this._session);
        assertType(this._strategy);
        const { response } = this._session.lastExchange;
        let newPosition;
        if (response instanceof EmptyResponse) {
            const status = ( localizeWithPath(_moduleId, 2, "No results, please refine your input and try again"));
            this._ui.value.zone.widget.updateStatus(status, { classes: ['warn'] });
            return State.WAIT_FOR_INPUT;
        }
        else if (response instanceof ErrorResponse) {
            if (!response.isCancellation) {
                this._ui.value.zone.widget.updateStatus(response.message, { classes: ['error'] });
                this._strategy?.cancel();
            }
        }
        else if (response instanceof ReplyResponse) {
            this._ui.value.zone.widget.updateStatus('');
            const position = await this._strategy.renderChanges();
            if (position) {
                const selection = this._editor.getSelection();
                if (selection?.containsPosition(position)) {
                    if (position.lineNumber - selection.startLineNumber > 8) {
                        newPosition = position;
                    }
                }
                else {
                    newPosition = position;
                }
            }
        }
        this._showWidget(false, newPosition);
        return State.WAIT_FOR_INPUT;
    }
    async [State.PAUSE]() {
        this._resetWidget();
        this._strategy?.dispose?.();
        this._session = undefined;
    }
    async [State.ACCEPT]() {
        assertType(this._session);
        assertType(this._strategy);
        this._sessionStore.clear();
        try {
            await this._strategy.apply();
        }
        catch (err) {
            this._dialogService.error(( localizeWithPath(_moduleId, 3, "Failed to apply changes.", toErrorMessage(err))));
            this._log('FAILED to apply changes');
            this._log(err);
        }
        this._inlineChatSessionService.releaseSession(this._session);
        this._resetWidget();
        this._strategy?.dispose();
        this._strategy = undefined;
        this._session = undefined;
    }
    async [State.CANCEL]() {
        if (this._session) {
            assertType(this._strategy);
            this._sessionStore.clear();
            const shouldStash = !this._session.isUnstashed && this._session.chatModel.hasRequests && this._session.hunkData.size === this._session.hunkData.pending;
            let undoCancelEdits = [];
            try {
                undoCancelEdits = this._strategy.cancel();
            }
            catch (err) {
                this._dialogService.error(( localizeWithPath(_moduleId, 4, "Failed to discard changes.", toErrorMessage(err))));
                this._log('FAILED to discard changes');
                this._log(err);
            }
            this._stashedSession.clear();
            if (shouldStash) {
                this._stashedSession.value = this._inlineChatSessionService.stashSession(this._session, this._editor, undoCancelEdits);
            }
            else {
                this._inlineChatSessionService.releaseSession(this._session);
            }
        }
        this._resetWidget();
        this._strategy?.dispose();
        this._strategy = undefined;
        this._session = undefined;
    }
    _showWidget(initialRender = false, position) {
        assertType(this._editor.hasModel());
        this._ctxVisible.set(true);
        let widgetPosition;
        if (position) {
            widgetPosition = position;
        }
        else if (this._ui.rawValue?.zone?.position) {
            if (this._ui.rawValue?.zone.position.lineNumber === 1) {
                widgetPosition = this._ui.rawValue?.zone.position.delta(-1);
            }
            else {
                widgetPosition = this._ui.rawValue?.zone.position;
            }
        }
        else {
            widgetPosition = this._editor.getSelection().getStartPosition().delta(-1);
        }
        if (this._session && !position && (this._session.hasChangedText || this._session.chatModel.hasRequests)) {
            widgetPosition = this._session.wholeRange.value.getStartPosition().delta(-1);
        }
        if (this._ui.rawValue?.zone?.position) {
            this._ui.value.zone.updatePositionAndHeight(widgetPosition);
        }
        else if (initialRender) {
            const selection = this._editor.getSelection();
            widgetPosition = selection.getStartPosition();
            this._ui.value.content.show(widgetPosition);
        }
        else {
            this._ui.value.content.hide();
            this._ui.value.zone.show(widgetPosition);
            if (this._session) {
                this._ui.value.zone.widget.setChatModel(this._session.chatModel);
            }
        }
        return widgetPosition;
    }
    _resetWidget() {
        this._sessionStore.clear();
        this._ctxVisible.reset();
        this._ctxUserDidEdit.reset();
        this._ui.rawValue?.content.hide();
        this._ui.rawValue?.zone?.hide();
        if (this._editor.hasWidgetFocus()) {
            this._editor.focus();
        }
    }
    _updateCtxResponseType() {
        if (!this._session) {
            this._ctxResponseType.set(InlineChatResponseType.None);
            return;
        }
        const hasLocalEdit = (response) => {
            return (
                 (response.value.some(
                    part => part.kind === 'textEditGroup' && isEqual(part.uri, this._session?.textModelN.uri)
                ))
            );
        };
        let responseType = InlineChatResponseType.None;
        for (const request of this._session.chatModel.getRequests()) {
            if (!request.response) {
                continue;
            }
            responseType = InlineChatResponseType.Messages;
            if (hasLocalEdit(request.response.response)) {
                responseType = InlineChatResponseType.MessagesAndEdits;
                break;
            }
        }
        this._ctxResponseType.set(responseType);
    }
    async _makeChanges(edits, opts, undoStopBefore) {
        assertType(this._session);
        assertType(this._strategy);
        const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this._session.textModelN.uri, edits);
        this._log('edits from PROVIDER and after making them MORE MINIMAL', this._session.agent.extensionId, edits, moreMinimalEdits);
        if (moreMinimalEdits?.length === 0) {
            return;
        }
        const actualEdits = !opts && moreMinimalEdits ? moreMinimalEdits : edits;
        const editOperations = ( (actualEdits.map(TextEdit.asEditOperation)));
        const editsObserver = {
            start: () => this._session.hunkData.ignoreTextModelNChanges = true,
            stop: () => this._session.hunkData.ignoreTextModelNChanges = false,
        };
        this._inlineChatSavingService.markChanged(this._session);
        if (opts) {
            await this._strategy.makeProgressiveChanges(editOperations, editsObserver, opts, undoStopBefore);
        }
        else {
            await this._strategy.makeChanges(editOperations, editsObserver, undoStopBefore);
        }
    }
    _updatePlaceholder() {
        this._ui.value.zone.widget.placeholder = this._getPlaceholderText();
    }
    _getPlaceholderText() {
        return this._forcedPlaceholder ?? this._session?.agent.description ?? '';
    }
    showSaveHint() {
        const status = ( localizeWithPath(_moduleId, 5, "Accept or discard changes to continue saving"));
        this._ui.value.zone.widget.updateStatus(status, { classes: ['warn'] });
    }
    acceptInput() {
        return this.chatWidget.acceptInput();
    }
    updateInput(text, selectAll = true) {
        this._ui.value.content.chatWidget.setInput(text);
        this._ui.value.zone.widget.chatWidget.setInput(text);
        if (selectAll) {
            const newSelection = ( (new Selection(1, 1, Number.MAX_SAFE_INTEGER, 1)));
            this._ui.value.content.chatWidget.inputEditor.setSelection(newSelection);
            this._ui.value.zone.widget.chatWidget.inputEditor.setSelection(newSelection);
        }
    }
    cancelCurrentRequest() {
        this._messages.fire(Message.CANCEL_INPUT | Message.CANCEL_REQUEST);
    }
    arrowOut(up) {
        if (this._ui.value.zone.position && this._editor.hasModel()) {
            const { column } = this._editor.getPosition();
            const { lineNumber } = this._ui.value.zone.position;
            const newLine = up ? lineNumber : lineNumber + 1;
            this._editor.setPosition({ lineNumber: newLine, column });
            this._editor.focus();
        }
    }
    focus() {
        this._ui.value.zone.widget.focus();
    }
    hasFocus() {
        return this._ui.value.zone.widget.hasFocus();
    }
    moveHunk(next) {
        this.focus();
        this._strategy?.move?.(next);
    }
    async viewInChat() {
        if (!this._strategy || !this._session) {
            return;
        }
        let someApplied = false;
        let lastEdit;
        const uri = this._editor.getModel()?.uri;
        const requests = this._session.chatModel.getRequests();
        for (const request of requests) {
            if (!request.response) {
                continue;
            }
            for (const part of request.response.response.value) {
                if (part.kind === 'textEditGroup' && isEqual(part.uri, uri)) {
                    someApplied = someApplied || Boolean(part.state?.applied);
                    lastEdit = part;
                }
            }
        }
        const doEdits = this._strategy.cancel();
        if (someApplied) {
            assertType(lastEdit);
            lastEdit.edits = [doEdits];
        }
        await this._instaService.invokeFunction(moveToPanelChat, this._session?.chatModel);
        this.cancelSession();
    }
    toggleDiff() {
        this._strategy?.toggleDiff?.();
    }
    acceptSession() {
        const response = this._session?.chatModel.getRequests().at(-1)?.response;
        if (response) {
            this._chatService.notifyUserAction({
                sessionId: response.session.sessionId,
                requestId: response.requestId,
                agentId: response.agent?.id,
                result: response.result,
                action: {
                    kind: 'inlineChat',
                    action: 'accepted'
                }
            });
        }
        this._messages.fire(Message.ACCEPT_SESSION);
    }
    acceptHunk() {
        return this._strategy?.acceptHunk();
    }
    discardHunk() {
        return this._strategy?.discardHunk();
    }
    async cancelSession() {
        const response = this._session?.chatModel.getRequests().at(-1)?.response;
        if (response) {
            this._chatService.notifyUserAction({
                sessionId: response.session.sessionId,
                requestId: response.requestId,
                agentId: response.agent?.id,
                result: response.result,
                action: {
                    kind: 'inlineChat',
                    action: 'discarded'
                }
            });
        }
        this._messages.fire(Message.CANCEL_SESSION);
    }
    finishExistingSession() {
        if (this._session) {
            if (this._session.editMode === EditMode.Preview) {
                this._log('finishing existing session, using CANCEL', this._session.editMode);
                this.cancelSession();
            }
            else {
                this._log('finishing existing session, using APPLY', this._session.editMode);
                this.acceptSession();
            }
        }
    }
    unstashLastSession() {
        const result = this._stashedSession.value?.unstash();
        if (result) {
            this._inlineChatSavingService.markChanged(result);
        }
        return result;
    }
    joinCurrentRun() {
        return this._currentRun;
    }
};
InlineChatController = ( (__decorate([
    ( (__param(1, IInstantiationService))),
    ( (__param(2, IInlineChatSessionService))),
    ( (__param(3, IInlineChatSavingService))),
    ( (__param(4, IEditorWorkerService))),
    ( (__param(5, ILogService))),
    ( (__param(6, IConfigurationService))),
    ( (__param(7, IDialogService))),
    ( (__param(8, IContextKeyService))),
    ( (__param(9, IChatService))),
    ( (__param(10, ILanguageFeaturesService))),
    ( (__param(11, IChatWidgetService))),
    ( (__param(12, INotebookEditorService)))
], InlineChatController)));
async function moveToPanelChat(accessor, model) {
    const viewsService = accessor.get(IViewsService);
    const chatService = accessor.get(IChatService);
    const widget = await showChatView(viewsService);
    if (widget && widget.viewModel && model) {
        for (const request of model.getRequests().slice()) {
            await chatService.adoptRequest(widget.viewModel.model.sessionId, request);
        }
        widget.focusLastMessage();
    }
}
export { InlineChatController, InlineChatRunOptions, State };
