import { coalesce } from '../../../base/common/arrays.js';
import { raceCancellation } from '../../../base/common/async.js';
import { toErrorMessage } from '../../../base/common/errorMessage.js';
import { Emitter } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { DisposableMap, DisposableStore } from '../../../base/common/lifecycle.js';
import { StopWatch } from '../../../base/common/stopwatch.js';
import { assertType } from '../../../base/common/types.js';
import { URI } from '../../../base/common/uri.js';
import { localizeWithPath } from '../../../nls.js';
import { ExtensionIdentifier } from '../../../platform/extensions/common/extensions.js';
import { MainContext } from './extHost.protocol.js';
import { ChatAgentRequest, ChatAgentResult, ChatAgentResolvedVariable, ChatResponsePart, ChatFollowup, ChatAgentUserActionEvent, ChatAgentCompletionItem, MarkdownString, ChatResponseMarkdownPart, ChatResponseFilesPart, ChatResponseAnchorPart, ChatResponseCommandButtonPart, ChatResponseProgressPart, ChatResponseReferencePart, ChatResponseProgress } from './extHostTypeConverters.js';
import { ChatRequestTurn, ChatResponseTurn, ChatResultFeedbackKind, ChatResponseReferencePart as ChatResponseReferencePart$1, ThemeIcon, ChatResponseMarkdownPart as ChatResponseMarkdownPart$1, ChatResponseFileTreePart, ChatResponseAnchorPart as ChatResponseAnchorPart$1, ChatResponseCommandButtonPart as ChatResponseCommandButtonPart$1, ChatResponseProgressPart as ChatResponseProgressPart$1 } from './extHostTypes.js';
import { InteractiveSessionVoteDirection } from '../../contrib/chat/common/chatService.js';
import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js';
class ChatAgentResponseStream {
    constructor(_extension, _request, _proxy, _logService, _commandsConverter, _sessionDisposables) {
        this._extension = _extension;
        this._request = _request;
        this._proxy = _proxy;
        this._logService = _logService;
        this._commandsConverter = _commandsConverter;
        this._sessionDisposables = _sessionDisposables;
        this._stopWatch = StopWatch.create(false);
        this._isClosed = false;
    }
    close() {
        this._isClosed = true;
    }
    get timings() {
        return {
            firstProgress: this._firstProgress,
            totalElapsed: this._stopWatch.elapsed()
        };
    }
    get apiObject() {
        if (!this._apiObject) {
            const that = this;
            this._stopWatch.reset();
            function throwIfDone(source) {
                if (that._isClosed) {
                    const err = ( new Error('Response stream has been closed'));
                    Error.captureStackTrace(err, source);
                    throw err;
                }
            }
            const _report = (progress) => {
                if (typeof this._firstProgress === 'undefined' && 'content' in progress) {
                    this._firstProgress = this._stopWatch.elapsed();
                }
                this._proxy.$handleProgressChunk(this._request.requestId, progress);
            };
            this._apiObject = {
                markdown(value) {
                    throwIfDone(this.markdown);
                    const part = new ChatResponseMarkdownPart$1(value);
                    const dto = ChatResponseMarkdownPart.to(part);
                    _report(dto);
                    return this;
                },
                filetree(value, baseUri) {
                    throwIfDone(this.filetree);
                    const part = new ChatResponseFileTreePart(value, baseUri);
                    const dto = ChatResponseFilesPart.to(part);
                    _report(dto);
                    return this;
                },
                anchor(value, title) {
                    throwIfDone(this.anchor);
                    const part = new ChatResponseAnchorPart$1(value, title);
                    const dto = ChatResponseAnchorPart.to(part);
                    _report(dto);
                    return this;
                },
                button(value) {
                    throwIfDone(this.anchor);
                    const part = new ChatResponseCommandButtonPart$1(value);
                    const dto = ChatResponseCommandButtonPart.to(part, that._commandsConverter, that._sessionDisposables);
                    _report(dto);
                    return this;
                },
                progress(value) {
                    throwIfDone(this.progress);
                    const part = new ChatResponseProgressPart$1(value);
                    const dto = ChatResponseProgressPart.to(part);
                    _report(dto);
                    return this;
                },
                reference(value) {
                    throwIfDone(this.reference);
                    if ('variableName' in value && !value.value) {
                        const matchingVarData = that._request.variables.variables.find(v => v.name === value.variableName);
                        if (matchingVarData) {
                            let references;
                            if (matchingVarData.references?.length) {
                                references = ( matchingVarData.references.map(r => ({
                                    kind: 'reference',
                                    reference: { variableName: value.variableName, value: r.reference }
                                })));
                            }
                            else {
                                const part = new ChatResponseReferencePart$1(value);
                                const dto = ChatResponseReferencePart.to(part);
                                references = [dto];
                            }
                            references.forEach(r => _report(r));
                            return this;
                        }
                    }
                    else {
                        const part = new ChatResponseReferencePart$1(value);
                        const dto = ChatResponseReferencePart.to(part);
                        _report(dto);
                    }
                    return this;
                },
                push(part) {
                    throwIfDone(this.push);
                    if (part instanceof ChatResponseReferencePart$1) {
                        this.reference(part.value);
                    }
                    else {
                        const dto = ChatResponsePart.to(part, that._commandsConverter, that._sessionDisposables);
                        _report(dto);
                    }
                    return this;
                },
                report(progress) {
                    throwIfDone(this.report);
                    if ('placeholder' in progress && 'resolvedContent' in progress) {
                        return;
                    }
                    const value = ChatResponseProgress.from(that._extension, progress);
                    if (!value) {
                        that._logService.error('Unknown progress type: ' + JSON.stringify(progress));
                        return;
                    }
                    _report(value);
                    return this;
                }
            };
        }
        return this._apiObject;
    }
}
class ExtHostChatAgents2 {
    static { this._idPool = 0; }
    constructor(mainContext, _logService, commands) {
        this._logService = _logService;
        this.commands = commands;
        this._agents = ( new Map());
        this._sessionDisposables = ( new DisposableMap());
        this._proxy = ( mainContext.getProxy(MainContext.MainThreadChatAgents2));
    }
    createChatAgent(extension, id, handler) {
        const handle = ExtHostChatAgents2._idPool++;
        const agent = ( new ExtHostChatAgent(extension, id, this._proxy, handle, handler));
        this._agents.set(handle, agent);
        this._proxy.$registerAgent(handle, extension.identifier, id, {}, undefined);
        return agent.apiAgent;
    }
    createDynamicChatAgent(extension, id, name, description, handler) {
        const handle = ExtHostChatAgents2._idPool++;
        const agent = ( new ExtHostChatAgent(extension, id, this._proxy, handle, handler));
        this._agents.set(handle, agent);
        this._proxy.$registerAgent(handle, extension.identifier, id, {}, { name, description });
        return agent.apiAgent;
    }
    async $invokeAgent(handle, request, context, token) {
        const agent = this._agents.get(handle);
        if (!agent) {
            throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`);
        }
        let sessionDisposables = this._sessionDisposables.get(request.sessionId);
        if (!sessionDisposables) {
            sessionDisposables = ( new DisposableStore());
            this._sessionDisposables.set(request.sessionId, sessionDisposables);
        }
        const stream = ( new ChatAgentResponseStream(
            agent.extension,
            request,
            this._proxy,
            this._logService,
            this.commands.converter,
            sessionDisposables
        ));
        try {
            const convertedHistory = await this.prepareHistoryTurns(request.agentId, context);
            const task = agent.invoke(ChatAgentRequest.to(request), { history: convertedHistory }, stream.apiObject, token);
            return await raceCancellation(Promise.resolve(task).then((result) => {
                if (result?.metadata) {
                    try {
                        JSON.stringify(result.metadata);
                    }
                    catch (err) {
                        const msg = `result.metadata MUST be JSON.stringify-able. Got error: ${err.message}`;
                        this._logService.error(`[${agent.extension.identifier.value}] [@${agent.id}] ${msg}`, agent.extension);
                        return { errorDetails: { message: msg }, timings: stream.timings };
                    }
                }
                return { errorDetails: result?.errorDetails, timings: stream.timings, metadata: result?.metadata };
            }), token);
        }
        catch (e) {
            this._logService.error(e, agent.extension);
            return { errorDetails: { message: ( localizeWithPath(
                'vs/workbench/api/common/extHostChatAgents2',
                'errorResponse',
                "Error from participant: {0}",
                toErrorMessage(e)
            )), responseIsIncomplete: true } };
        }
        finally {
            stream.close();
        }
    }
    async prepareHistoryTurns(agentId, context) {
        const res = [];
        for (const h of context.history) {
            const ehResult = ChatAgentResult.to(h.result);
            const result = agentId === h.request.agentId ?
                ehResult :
                { ...ehResult, metadata: undefined };
            res.push(new ChatRequestTurn(h.request.message, h.request.command, ( h.request.variables.variables.map(ChatAgentResolvedVariable.to)), h.request.agentId));
            const parts = coalesce(( h.response.map(r => ChatResponsePart.fromContent(r, this.commands.converter))));
            res.push(new ChatResponseTurn(parts, result, h.request.agentId, h.request.command));
        }
        return res;
    }
    $releaseSession(sessionId) {
        this._sessionDisposables.deleteAndDispose(sessionId);
    }
    async $provideFollowups(request, handle, result, context, token) {
        const agent = this._agents.get(handle);
        if (!agent) {
            return Promise.resolve([]);
        }
        const convertedHistory = await this.prepareHistoryTurns(agent.id, context);
        const ehResult = ChatAgentResult.to(result);
        return ( (await agent.provideFollowups(ehResult, { history: convertedHistory }, token))
            .filter(f => {
            const isValid = !f.participant || ( Iterable.some(( this._agents.values()), a => a.id === f.participant && ExtensionIdentifier.equals(a.extension.identifier, agent.extension.identifier)));
            if (!isValid) {
                this._logService.warn(`[@${agent.id}] ChatFollowup refers to an unknown participant: ${f.participant}`);
            }
            return isValid;
        })
            .map(f => ChatFollowup.from(f, request)));
    }
    $acceptFeedback(handle, result, vote, reportIssue) {
        const agent = this._agents.get(handle);
        if (!agent) {
            return;
        }
        const ehResult = ChatAgentResult.to(result);
        let kind;
        switch (vote) {
            case InteractiveSessionVoteDirection.Down:
                kind = ChatResultFeedbackKind.Unhelpful;
                break;
            case InteractiveSessionVoteDirection.Up:
                kind = ChatResultFeedbackKind.Helpful;
                break;
        }
        agent.acceptFeedback(reportIssue ?
            ( Object.freeze({ result: ehResult, kind, reportIssue })) :
            ( Object.freeze({ result: ehResult, kind })));
    }
    $acceptAction(handle, result, event) {
        const agent = this._agents.get(handle);
        if (!agent) {
            return;
        }
        if (event.action.kind === 'vote') {
            return;
        }
        const ehAction = ChatAgentUserActionEvent.to(result, event, this.commands.converter);
        if (ehAction) {
            agent.acceptAction(( Object.freeze(ehAction)));
        }
    }
    async $invokeCompletionProvider(handle, query, token) {
        const agent = this._agents.get(handle);
        if (!agent) {
            return [];
        }
        const items = await agent.invokeCompletionProvider(query, token);
        return ( items.map(ChatAgentCompletionItem.from));
    }
    async $provideWelcomeMessage(handle, token) {
        const agent = this._agents.get(handle);
        if (!agent) {
            return;
        }
        return await agent.provideWelcomeMessage(token);
    }
    async $provideSampleQuestions(handle, token) {
        const agent = this._agents.get(handle);
        if (!agent) {
            return;
        }
        return ( (await agent.provideSampleQuestions(token))
            .map(f => ChatFollowup.from(f, undefined)));
    }
}
class ExtHostChatAgent {
    constructor(extension, id, _proxy, _handle, _requestHandler) {
        this.extension = extension;
        this.id = id;
        this._proxy = _proxy;
        this._handle = _handle;
        this._requestHandler = _requestHandler;
        this._onDidReceiveFeedback = ( new Emitter());
        this._onDidPerformAction = ( new Emitter());
    }
    acceptFeedback(feedback) {
        this._onDidReceiveFeedback.fire(feedback);
    }
    acceptAction(event) {
        this._onDidPerformAction.fire(event);
    }
    async invokeCompletionProvider(query, token) {
        if (!this._agentVariableProvider) {
            return [];
        }
        return (await this._agentVariableProvider.provider.provideCompletionItems(query, token)) ?? [];
    }
    async provideFollowups(result, context, token) {
        if (!this._followupProvider) {
            return [];
        }
        const followups = await this._followupProvider.provideFollowups(result, context, token);
        if (!followups) {
            return [];
        }
        return followups
            .filter(f => !(f && 'commandId' in f))
            .filter(f => !(f && 'message' in f));
    }
    async provideWelcomeMessage(token) {
        if (!this._welcomeMessageProvider) {
            return [];
        }
        const content = await this._welcomeMessageProvider.provideWelcomeMessage(token);
        if (!content) {
            return [];
        }
        return ( content.map(item => {
            if (typeof item === 'string') {
                return item;
            }
            else {
                return MarkdownString.from(item);
            }
        }));
    }
    async provideSampleQuestions(token) {
        if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) {
            return [];
        }
        const content = await this._welcomeMessageProvider.provideSampleQuestions(token);
        if (!content) {
            return [];
        }
        return content;
    }
    get apiAgent() {
        let disposed = false;
        let updateScheduled = false;
        const updateMetadataSoon = () => {
            if (disposed) {
                return;
            }
            if (updateScheduled) {
                return;
            }
            updateScheduled = true;
            queueMicrotask(() => {
                this._proxy.$updateAgent(this._handle, {
                    fullName: this._fullName,
                    icon: !this._iconPath ? undefined :
                        this._iconPath instanceof URI ? this._iconPath :
                            'light' in this._iconPath ? this._iconPath.light :
                                undefined,
                    iconDark: !this._iconPath ? undefined :
                        'dark' in this._iconPath ? this._iconPath.dark :
                            undefined,
                    themeIcon: this._iconPath instanceof ThemeIcon ? this._iconPath : undefined,
                    hasFollowups: this._followupProvider !== undefined,
                    isSecondary: this._isSecondary,
                    helpTextPrefix: (!this._helpTextPrefix || typeof this._helpTextPrefix === 'string') ? this._helpTextPrefix : MarkdownString.from(this._helpTextPrefix),
                    helpTextVariablesPrefix: (!this._helpTextVariablesPrefix || typeof this._helpTextVariablesPrefix === 'string') ? this._helpTextVariablesPrefix : MarkdownString.from(this._helpTextVariablesPrefix),
                    helpTextPostfix: (!this._helpTextPostfix || typeof this._helpTextPostfix === 'string') ? this._helpTextPostfix : MarkdownString.from(this._helpTextPostfix),
                    sampleRequest: this._sampleRequest,
                    supportIssueReporting: this._supportIssueReporting,
                    requester: this._requester
                });
                updateScheduled = false;
            });
        };
        const that = this;
        return {
            get id() {
                return that.id;
            },
            get fullName() {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                return that._fullName ?? that.extension.displayName ?? that.extension.name;
            },
            set fullName(v) {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                that._fullName = v;
                updateMetadataSoon();
            },
            get iconPath() {
                return that._iconPath;
            },
            set iconPath(v) {
                that._iconPath = v;
                updateMetadataSoon();
            },
            get requestHandler() {
                return that._requestHandler;
            },
            set requestHandler(v) {
                assertType(typeof v === 'function', 'Invalid request handler');
                that._requestHandler = v;
            },
            get followupProvider() {
                return that._followupProvider;
            },
            set followupProvider(v) {
                that._followupProvider = v;
                updateMetadataSoon();
            },
            get isDefault() {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                return that._isDefault;
            },
            set isDefault(v) {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                that._isDefault = v;
                updateMetadataSoon();
            },
            get helpTextPrefix() {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                return that._helpTextPrefix;
            },
            set helpTextPrefix(v) {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                that._helpTextPrefix = v;
                updateMetadataSoon();
            },
            get helpTextVariablesPrefix() {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                return that._helpTextVariablesPrefix;
            },
            set helpTextVariablesPrefix(v) {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                that._helpTextVariablesPrefix = v;
                updateMetadataSoon();
            },
            get helpTextPostfix() {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                return that._helpTextPostfix;
            },
            set helpTextPostfix(v) {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                that._helpTextPostfix = v;
                updateMetadataSoon();
            },
            get isSecondary() {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                return that._isSecondary;
            },
            set isSecondary(v) {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                that._isSecondary = v;
                updateMetadataSoon();
            },
            get sampleRequest() {
                return that._sampleRequest;
            },
            set sampleRequest(v) {
                that._sampleRequest = v;
                updateMetadataSoon();
            },
            get supportIssueReporting() {
                checkProposedApiEnabled(that.extension, 'chatParticipantAdditions');
                return that._supportIssueReporting;
            },
            set supportIssueReporting(v) {
                checkProposedApiEnabled(that.extension, 'chatParticipantAdditions');
                that._supportIssueReporting = v;
                updateMetadataSoon();
            },
            get onDidReceiveFeedback() {
                return that._onDidReceiveFeedback.event;
            },
            set participantVariableProvider(v) {
                checkProposedApiEnabled(that.extension, 'chatParticipantAdditions');
                that._agentVariableProvider = v;
                if (v) {
                    if (!v.triggerCharacters.length) {
                        throw new Error('triggerCharacters are required');
                    }
                    that._proxy.$registerAgentCompletionsProvider(that._handle, v.triggerCharacters);
                }
                else {
                    that._proxy.$unregisterAgentCompletionsProvider(that._handle);
                }
            },
            get participantVariableProvider() {
                checkProposedApiEnabled(that.extension, 'chatParticipantAdditions');
                return that._agentVariableProvider;
            },
            set welcomeMessageProvider(v) {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                that._welcomeMessageProvider = v;
                updateMetadataSoon();
            },
            get welcomeMessageProvider() {
                checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
                return that._welcomeMessageProvider;
            },
            onDidPerformAction: !isProposedApiEnabled(this.extension, 'chatParticipantAdditions')
                ? undefined
                : this._onDidPerformAction.event,
            set requester(v) {
                that._requester = v;
                updateMetadataSoon();
            },
            get requester() {
                return that._requester;
            },
            dispose() {
                disposed = true;
                that._followupProvider = undefined;
                that._onDidReceiveFeedback.dispose();
                that._proxy.$unregisterAgent(that._handle);
            },
        };
    }
    invoke(request, context, response, token) {
        return this._requestHandler(request, context, response, token);
    }
}
export { ExtHostChatAgents2 };
