/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { coalesce } from '../../../../../base/common/arrays.js';
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { Codicon } from '../../../../../base/common/codicons.js';
import { Emitter } from '../../../../../base/common/event.js';
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { ResourceSet } from '../../../../../base/common/map.js';
import { Schemas } from '../../../../../base/common/network.js';
import { IWorkbenchContribution } from '../../../../common/contributions.js';
import { IChatModel } from '../../common/model/chatModel.js';
import { convertLegacyChatSessionTiming, IChatDetail, IChatService, ResponseModelState } from '../../common/chatService/chatService.js';
import { ChatSessionStatus, IChatSessionItem, IChatSessionItemController, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js';
import { getChatSessionType } from '../../common/model/chatUri.js';
import { ILogService } from '../../../../../platform/log/common/log.js';

export class LocalAgentsSessionsController extends Disposable implements IChatSessionItemController, IWorkbenchContribution {

	static readonly ID = 'workbench.contrib.localAgentsSessionsController';

	readonly chatSessionType = localChatSessionType;

	private readonly _onDidChange = this._register(new Emitter<void>());
	readonly onDidChange = this._onDidChange.event;

	readonly _onDidChangeChatSessionItems = this._register(new Emitter<void>());
	readonly onDidChangeChatSessionItems = this._onDidChangeChatSessionItems.event;

	constructor(
		@IChatService private readonly chatService: IChatService,
		@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
		@ILogService private readonly logService: ILogService,
	) {
		super();

		this._register(this.chatSessionsService.registerChatSessionItemController(this.chatSessionType, this));

		this.registerListeners();
	}

	private _items: IChatSessionItem[] = [];
	get items(): readonly IChatSessionItem[] {
		return this._items;
	}

	async refresh(token: CancellationToken): Promise<void> {
		this._items = await this.provideChatSessionItems(token);
	}

	private registerListeners(): void {
		const refreshItems = async () => {
			this._onDidChangeChatSessionItems.fire();
			await this.refresh(CancellationToken.None);
			this._onDidChangeChatSessionItems.fire();
		};

		this._register(this.chatSessionsService.registerChatModelChangeListeners(this.chatService, Schemas.vscodeLocalChatSession, refreshItems));

		this._register(this.chatService.onDidDisposeSession(e => {
			const session = e.sessionResource.filter(resource => getChatSessionType(resource) === this.chatSessionType);
			if (session.length > 0) {
				refreshItems();
			}
		}));
	}

	private async provideChatSessionItems(token: CancellationToken): Promise<IChatSessionItem[]> {
		const sessions: IChatSessionItem[] = [];
		const sessionsByResource = new ResourceSet();

		for (const sessionDetail of await this.chatService.getLiveSessionItems()) {
			const editorSession = this.toChatSessionItem(sessionDetail);
			if (!editorSession) {
				continue;
			}

			sessionsByResource.add(sessionDetail.sessionResource);
			sessions.push(editorSession);
		}

		if (!token.isCancellationRequested) {
			const history = await this.getHistoryItems();
			sessions.push(...history.filter(historyItem => !sessionsByResource.has(historyItem.resource)));
		}

		return sessions;
	}

	private async getHistoryItems(): Promise<IChatSessionItem[]> {
		try {
			const historyItems = await this.chatService.getHistorySessionItems();

			return coalesce(historyItems.map(history => this.toChatSessionItem(history)));
		} catch (error) {
			return [];
		}
	}

	private toChatSessionItem(chat: IChatDetail): IChatSessionItem | undefined {
		const model = this.chatService.getSession(chat.sessionResource);

		let description: string | undefined;
		if (model) {
			if (!model.hasRequests) {
				return undefined; // ignore sessions without requests
			}

			description = this.chatSessionsService.getInProgressSessionDescription(model);
		}

		return {
			resource: chat.sessionResource,
			label: chat.title,
			description,
			status: model ? this.modelToStatus(model) : this.chatResponseStateToStatus(chat.lastResponseState),
			iconPath: Codicon.chatSparkle,
			timing: convertLegacyChatSessionTiming(chat.timing),
			changes: chat.stats ? {
				insertions: chat.stats.added,
				deletions: chat.stats.removed,
				files: chat.stats.fileCount,
			} : undefined
		};
	}

	private modelToStatus(model: IChatModel): ChatSessionStatus | undefined {
		if (model.requestInProgress.get()) {
			this.logService.trace(`[agent sessions] Session ${model.sessionResource.toString()} request is in progress.`);
			return ChatSessionStatus.InProgress;
		}

		const lastRequest = model.getRequests().at(-1);
		this.logService.trace(`[agent sessions] Session ${model.sessionResource.toString()} last request response: state ${lastRequest?.response?.state}, isComplete ${lastRequest?.response?.isComplete}, isCanceled ${lastRequest?.response?.isCanceled}, error: ${lastRequest?.response?.result?.errorDetails?.message}.`);
		if (lastRequest?.response) {
			if (lastRequest.response.state === ResponseModelState.NeedsInput) {
				return ChatSessionStatus.NeedsInput;
			} else if (lastRequest.response.isCanceled || lastRequest.response.result?.errorDetails?.code === 'canceled') {
				return ChatSessionStatus.Completed;
			} else if (lastRequest.response.result?.errorDetails) {
				return ChatSessionStatus.Failed;
			} else if (lastRequest.response.isComplete) {
				return ChatSessionStatus.Completed;
			} else {
				return ChatSessionStatus.InProgress;
			}
		}

		return undefined;
	}

	private chatResponseStateToStatus(state: ResponseModelState): ChatSessionStatus {
		switch (state) {
			case ResponseModelState.Cancelled:
			case ResponseModelState.Complete:
				return ChatSessionStatus.Completed;
			case ResponseModelState.Failed:
				return ChatSessionStatus.Failed;
			case ResponseModelState.Pending:
				return ChatSessionStatus.InProgress;
			case ResponseModelState.NeedsInput:
				return ChatSessionStatus.NeedsInput;
		}
	}
}
