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

import { IMouseWheelEvent } from '../../../../base/browser/mouseEvent.js';
import { Event } from '../../../../base/common/event.js';
import { IDisposable } from '../../../../base/common/lifecycle.js';
import { URI } from '../../../../base/common/uri.js';
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { Selection } from '../../../../editor/common/core/selection.js';
import { EditDeltaInfo } from '../../../../editor/common/textModelEditSource.js';
import { MenuId } from '../../../../platform/actions/common/actions.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { PreferredGroup } from '../../../services/editor/common/editorService.js';
import { IChatAgentAttachmentCapabilities, IChatAgentCommand, IChatAgentData } from '../common/participants/chatAgents.js';
import { IChatResponseModel, IChatModelInputState } from '../common/model/chatModel.js';
import { IChatMode } from '../common/chatModes.js';
import { IParsedChatRequest } from '../common/requestParser/chatParserTypes.js';
import { CHAT_PROVIDER_ID } from '../common/participants/chatParticipantContribTypes.js';
import { ChatRequestQueueKind, IChatElicitationRequest, IChatLocationData, IChatSendRequestOptions } from '../common/chatService/chatService.js';
import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatPendingDividerViewModel } from '../common/model/chatViewModel.js';
import { ChatAgentLocation, ChatModeKind } from '../common/constants.js';
import { ChatAttachmentModel } from './attachments/chatAttachmentModel.js';
import { IChatEditorOptions } from './widgetHosts/editor/chatEditor.js';
import { ChatInputPart } from './widget/input/chatInputPart.js';
import { ChatWidget, IChatWidgetContrib } from './widget/chatWidget.js';
import { ICodeBlockActionContext } from './widget/chatContentParts/codeBlockPart.js';
import { AgentSessionProviders } from './agentSessions/agentSessions.js';

/**
 * A workspace item that can be selected in the workspace picker.
 */
export interface IWorkspacePickerItem {
	readonly uri: URI;
	readonly label: string;
	readonly isFolder: boolean;
}

/**
 * Delegate interface for the workspace picker.
 * Allows consumers to get and set the target workspace for chat submissions in empty window contexts.
 */
export interface IWorkspacePickerDelegate {
	/**
	 * Returns the list of available workspaces to select from.
	 */
	getWorkspaces(): IWorkspacePickerItem[];
	/**
	 * Returns the currently selected workspace, if any.
	 */
	getSelectedWorkspace(): IWorkspacePickerItem | undefined;
	/**
	 * Sets the currently selected workspace.
	 */
	setSelectedWorkspace(workspace: IWorkspacePickerItem | undefined): void;
	/**
	 * Event that fires when the selected workspace changes.
	 */
	onDidChangeSelectedWorkspace: Event<IWorkspacePickerItem | undefined>;
	/**
	 * Event that fires when the available workspaces change.
	 */
	onDidChangeWorkspaces: Event<void>;
	/**
	 * Command ID to execute when user wants to open a new folder.
	 */
	openFolderCommand: string;
}

/**
 * Delegate interface for the session target picker.
 * Allows consumers to get and optionally set the active session provider.
 */
export interface ISessionTypePickerDelegate {
	getActiveSessionProvider(): AgentSessionProviders | undefined;
	/**
	 * Optional setter for the active session provider.
	 * When provided, the picker will call this instead of executing the openNewChatSessionInPlace command.
	 * This allows the welcome view to maintain independent state from the main chat panel.
	 */
	setActiveSessionProvider?(provider: AgentSessionProviders): void;
	/**
	 * Optional getter for the pending delegation target - the target that will be used when submit is pressed.
	 */
	getPendingDelegationTarget?(): AgentSessionProviders | undefined;
	/**
	 * Optional setter for the pending delegation target.
	 * When a user selects a different session provider in a non-empty chat,
	 * this stores the target for delegation on the next submit instead of immediately creating a new session.
	 */
	setPendingDelegationTarget?(provider: AgentSessionProviders): void;
	/**
	 * Optional event that fires when the active session provider changes.
	 * When provided, listeners (like chatInputPart) can react to session type changes
	 * and update pickers accordingly.
	 */
	onDidChangeActiveSessionProvider?: Event<AgentSessionProviders>;
}

export const IChatWidgetService = createDecorator<IChatWidgetService>('chatWidgetService');

export interface IChatWidgetService {

	readonly _serviceBrand: undefined;

	/**
	 * Returns the most recently focused widget if any.
	 *
	 * ⚠️ Consider carefully if this is appropriate for your use case. If you
	 * can know what session you're interacting with, prefer {@link getWidgetBySessionResource}
	 * or similar methods to work nicely with multiple chat widgets.
	 */
	readonly lastFocusedWidget: IChatWidget | undefined;

	readonly onDidAddWidget: Event<IChatWidget>;

	/**
	 * Fires when a chat session is no longer open in any chat widget.
	 */
	readonly onDidBackgroundSession: Event<URI>;

	/**
	 * Fires when the focused chat widget changes.
	 */
	readonly onDidChangeFocusedWidget: Event<IChatWidget | undefined>;

	/**
	 * Fires when the focused chat session changes, either because the focused widget
	 * changed or because the focused widget loaded a different session.
	 */
	readonly onDidChangeFocusedSession: Event<void>;

	/**
	 * Reveals the widget, focusing its input unless `preserveFocus` is true.
	 */
	reveal(widget: IChatWidget, preserveFocus?: boolean): Promise<boolean>;

	/**
	 * Reveals the last active widget, or creates a new chat if necessary.
	 */
	revealWidget(preserveFocus?: boolean): Promise<IChatWidget | undefined>;

	getAllWidgets(): ReadonlyArray<IChatWidget>;
	getWidgetByInputUri(uri: URI): IChatWidget | undefined;
	openSession(sessionResource: URI, target?: typeof ChatViewPaneTarget): Promise<IChatWidget | undefined>;
	openSession(sessionResource: URI, target?: PreferredGroup, options?: IChatEditorOptions): Promise<IChatWidget | undefined>;
	openSession(sessionResource: URI, target?: typeof ChatViewPaneTarget | PreferredGroup, options?: IChatEditorOptions): Promise<IChatWidget | undefined>;

	getWidgetBySessionResource(sessionResource: URI): IChatWidget | undefined;

	getWidgetsByLocations(location: ChatAgentLocation): ReadonlyArray<IChatWidget>;

	/**
	 * An IChatWidget registers itself when created.
	 */
	register(newWidget: IChatWidget): IDisposable;
}

export const ChatViewPaneTarget = Symbol('ChatViewPaneTarget');

export const IQuickChatService = createDecorator<IQuickChatService>('quickChatService');
export interface IQuickChatService {
	readonly _serviceBrand: undefined;
	readonly onDidClose: Event<void>;
	readonly enabled: boolean;
	readonly focused: boolean;
	/** Defined when quick chat is open */
	readonly sessionResource?: URI;
	toggle(options?: IQuickChatOpenOptions): void;
	focus(): void;
	open(options?: IQuickChatOpenOptions): void;
	close(): void;
	openInChatView(): void;
}

export interface IQuickChatOpenOptions {
	/**
	 * The query for quick chat.
	 */
	query: string;
	/**
	 * Whether the query is partial and will await more input from the user.
	 */
	isPartialQuery?: boolean;
	/**
	 * An optional selection range to apply to the query text box.
	 */
	selection?: Selection;
}

export const IChatAccessibilityService = createDecorator<IChatAccessibilityService>('chatAccessibilityService');
export interface IChatAccessibilityService {
	readonly _serviceBrand: undefined;
	acceptRequest(uri: URI, skipRequestSignal?: boolean): void;
	disposeRequest(requestId: URI): void;
	acceptResponse(widget: ChatWidget, container: HTMLElement, response: IChatResponseViewModel | string | undefined, requestId: URI | undefined, isVoiceInput?: boolean): void;
	acceptElicitation(message: IChatElicitationRequest): void;
}

export interface IChatCodeBlockInfo {
	readonly ownerMarkdownPartId: string;
	readonly codeBlockIndex: number;
	readonly elementId: string;
	readonly uri: URI | undefined;
	readonly uriPromise: Promise<URI | undefined>;
	codemapperUri: URI | undefined;
	readonly chatSessionResource: URI | undefined;
	focus(): void;
	readonly languageId?: string | undefined;
	readonly editDeltaInfo?: EditDeltaInfo | undefined;
}

export interface IChatFileTreeInfo {
	treeDataId: string;
	treeIndex: number;
	focus(): void;
}

export type ChatTreeItem = IChatRequestViewModel | IChatResponseViewModel | IChatPendingDividerViewModel;

export interface IChatListItemRendererOptions {
	readonly renderStyle?: 'compact' | 'minimal';
	readonly noHeader?: boolean;
	readonly noFooter?: boolean;
	readonly renderDetectedCommandsWithRequest?: boolean;
	readonly restorable?: boolean;
	readonly editable?: boolean;
	readonly renderTextEditsAsSummary?: (uri: URI) => boolean;
	readonly referencesExpandedWhenEmptyResponse?: boolean | ((mode: ChatModeKind) => boolean);
	readonly progressMessageAtBottomOfResponse?: boolean | ((mode: ChatModeKind) => boolean);
}

export interface IChatWidgetViewOptions {
	autoScroll?: boolean | ((mode: ChatModeKind) => boolean);
	renderInputOnTop?: boolean;
	renderFollowups?: boolean;
	renderStyle?: 'compact' | 'minimal';
	renderInputToolbarBelowInput?: boolean;
	supportsFileReferences?: boolean;
	filter?: (item: ChatTreeItem) => boolean;
	/** Action triggered when 'clear' is called on the widget. */
	clear?: () => Promise<void>;
	rendererOptions?: IChatListItemRendererOptions;
	menus?: {
		/**
		 * The menu that is inside the input editor, use for send, dictation
		 */
		executeToolbar?: MenuId;
		/**
		 * The menu that next to the input editor, use for close, config etc
		 */
		inputSideToolbar?: MenuId;
		/**
		 * The telemetry source for all commands of this widget
		 */
		telemetrySource?: string;
	};
	defaultElementHeight?: number;
	editorOverflowWidgetsDomNode?: HTMLElement;
	enableImplicitContext?: boolean;
	enableWorkingSet?: 'explicit' | 'implicit';
	supportsChangingModes?: boolean;
	dndContainer?: HTMLElement;
	inputEditorMinLines?: number;
	defaultMode?: IChatMode;
	/**
	 * Optional delegate for the session target picker.
	 * When provided, allows the widget to maintain independent state for the selected session type.
	 * This is useful for contexts like the welcome view where target selection should not
	 * immediately open a new session.
	 */
	sessionTypePickerDelegate?: ISessionTypePickerDelegate;

	/**
	 * Optional delegate for the workspace picker.
	 * When provided, shows a workspace picker in the chat input allowing users to select
	 * a target workspace for their request. This is useful for empty window contexts where
	 * the user wants to send a request to a specific workspace.
	 */
	workspacePickerDelegate?: IWorkspacePickerDelegate;

	/**
	 * Optional handler for chat submission.
	 * When provided, this handler is called before the normal input acceptance flow.
	 * If it returns true (handled), the normal submission is skipped.
	 * This is useful for contexts like the welcome view where submission should
	 * redirect to a different workspace rather than executing locally.
	 */
	submitHandler?: (query: string, mode: ChatModeKind) => Promise<boolean>;

	/**
	 * Whether we are running in the sessions window.
	 * When true, the secondary toolbar (permissions picker) is hidden.
	 */
	isSessionsWindow?: boolean;
}

export interface IChatViewViewContext {
	viewId: string;
}

export function isIChatViewViewContext(context: IChatWidgetViewContext): context is IChatViewViewContext {
	return typeof (context as IChatViewViewContext).viewId === 'string';
}

export interface IChatResourceViewContext {
	isQuickChat?: boolean;
	isInlineChat?: boolean;
}

export function isIChatResourceViewContext(context: IChatWidgetViewContext): context is IChatResourceViewContext {
	return !isIChatViewViewContext(context);
}

export type IChatWidgetViewContext = IChatViewViewContext | IChatResourceViewContext | {};

export interface IChatAcceptInputOptions {
	noCommandDetection?: boolean;
	isVoiceInput?: boolean;
	enableImplicitContext?: boolean; // defaults to true
	// Whether to store the input to history. This defaults to 'true' if the input
	// box's current content is being accepted, or 'false' if a specific input
	// is being submitted to the widget.
	storeToHistory?: boolean;
	/**
	 * When set, queues this message to be sent after the current request completes.
	 * If Steering, also sets yieldRequested on any active request to signal it should wrap up.
	 */
	queue?: ChatRequestQueueKind;
	/**
	 * When true, always queues the request regardless of whether a request is currently in progress.
	 * The request stays in the pending queue until explicitly processed.
	 */
	alwaysQueue?: boolean;
}

export interface IChatWidgetViewModelChangeEvent {
	readonly previousSessionResource: URI | undefined;
	readonly currentSessionResource: URI | undefined;
}

export interface IChatWidget {
	readonly domNode: HTMLElement;
	readonly onDidChangeViewModel: Event<IChatWidgetViewModelChangeEvent>;
	readonly onDidAcceptInput: Event<void>;
	readonly onDidHide: Event<void>;
	readonly onDidShow: Event<void>;
	readonly onDidSubmitAgent: Event<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>;
	readonly onDidChangeAgent: Event<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>;
	readonly onDidChangeParsedInput: Event<void>;
	readonly onDidChangeActiveInputEditor: Event<void>;
	readonly onDidFocus: Event<void>;
	readonly location: ChatAgentLocation;
	readonly viewContext: IChatWidgetViewContext;
	readonly viewModel: IChatViewModel | undefined;
	readonly inputEditor: ICodeEditor;
	readonly supportsFileReferences: boolean;
	readonly attachmentCapabilities: IChatAgentAttachmentCapabilities;
	readonly parsedInput: IParsedChatRequest;
	readonly lockedAgentId: string | undefined;
	lastSelectedAgent: IChatAgentData | undefined;
	readonly scopedContextKeyService: IContextKeyService;
	readonly input: ChatInputPart;
	readonly attachmentModel: ChatAttachmentModel;
	readonly locationData?: IChatLocationData;
	readonly contribs: readonly IChatWidgetContrib[];

	readonly supportsChangingModes: boolean;

	getContrib<T extends IChatWidgetContrib>(id: string): T | undefined;
	reveal(item: ChatTreeItem): void;
	focus(item: ChatTreeItem): void;
	getSibling(item: ChatTreeItem, type: 'next' | 'previous'): ChatTreeItem | undefined;
	getFocus(): ChatTreeItem | undefined;
	setInput(query?: string): void;
	getInput(): string;
	refreshParsedInput(): void;
	logInputHistory(): void;
	acceptInput(query?: string, options?: IChatAcceptInputOptions): Promise<IChatResponseModel | undefined>;
	startEditing(requestId: string): void;
	finishedEditing(completedEdit?: boolean): void;
	rerunLastRequest(): Promise<void>;
	setInputPlaceholder(placeholder: string): void;
	resetInputPlaceholder(): void;
	/**
	 * Focuses the response item in the list.
	 * @param lastFocused Focuses the most recently focused response. Otherwise, focuses the last response.
	 */
	focusResponseItem(lastFocused?: boolean): void;
	focusInput(): void;
	/**
	 * Focuses the Todos view in the chat widget.
	 * @returns Whether the operation succeeded (i.e., the Todos view was focused).
	 */
	focusTodosView(): boolean;
	/**
	 * Toggles focus between the Todos view and the previous focus target in the chat widget.
	 * @returns Whether the operation succeeded (i.e., the focus was toggled).
	 */
	toggleTodosViewFocus(): boolean;
	/**
	 * Focuses the question carousel in the chat widget.
	 * @returns Whether the operation succeeded (i.e., the question carousel was focused).
	 */
	focusQuestionCarousel(): boolean;
	/**
	 * Toggles focus between the question carousel and the chat input.
	 * @returns Whether the operation succeeded (i.e., the focus was toggled).
	 */
	toggleQuestionCarouselFocus(): boolean;
	/**
	 * Navigates to the previous question in the question carousel.
	 * @returns Whether the operation succeeded (i.e., a previous question exists).
	 */
	navigateToPreviousQuestion(): boolean;
	/**
	 * Navigates to the next question in the question carousel.
	 * @returns Whether the operation succeeded (i.e., a next question exists).
	 */
	navigateToNextQuestion(): boolean;
	/**
	 * Toggles focus between the tip widget and the chat input.
	 * Returns false if no tip is visible.
	 * @returns Whether the operation succeeded (i.e., the focus was toggled).
	 */
	toggleTipFocus(): boolean;
	hasInputFocus(): boolean;
	getModeRequestOptions(): Partial<IChatSendRequestOptions>;
	getCodeBlockInfoForEditor(uri: URI): IChatCodeBlockInfo | undefined;
	getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[];
	getFileTreeInfosForResponse(response: IChatResponseViewModel): IChatFileTreeInfo[];
	getLastFocusedFileTreeForResponse(response: IChatResponseViewModel): IChatFileTreeInfo | undefined;
	clear(): Promise<void>;
	getViewState(): IChatModelInputState | undefined;
	lockToCodingAgent(name: string, displayName: string, agentId?: string): void;
	unlockFromCodingAgent(): void;
	handleDelegationExitIfNeeded(sourceAgent: Pick<IChatAgentData, 'id' | 'name'> | undefined, targetAgent: IChatAgentData | undefined): Promise<void>;

	delegateScrollFromMouseWheelEvent(event: IMouseWheelEvent): void;
}


export interface ICodeBlockActionContextProvider {
	getCodeBlockContext(editor?: ICodeEditor): ICodeBlockActionContext | undefined;
}

export const IChatCodeBlockContextProviderService = createDecorator<IChatCodeBlockContextProviderService>('chatCodeBlockContextProviderService');
export interface IChatCodeBlockContextProviderService {
	readonly _serviceBrand: undefined;
	readonly providers: ICodeBlockActionContextProvider[];
	registerProvider(provider: ICodeBlockActionContextProvider, id: string): IDisposable;
}

export const ChatViewId = `workbench.panel.chat.view.${CHAT_PROVIDER_ID}`;
export const ChatViewContainerId = 'workbench.panel.chat';
