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

import { CancellationToken } from '../../../../../../base/common/cancellation.js';
import { Event } from '../../../../../../base/common/event.js';
import { IDisposable } from '../../../../../../base/common/lifecycle.js';
import { URI } from '../../../../../../base/common/uri.js';
import { ITextModel } from '../../../../../../editor/common/model.js';
import { ExtensionIdentifier, IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js';
import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js';
import { IChatModeInstructions, IVariableReference } from '../../chatModes.js';
import { PromptsType } from '../promptTypes.js';
import { IHandOff, ParsedPromptFile } from '../promptFileParser.js';
import { ResourceSet } from '../../../../../../base/common/map.js';
import { IResolvedPromptSourceFolder } from '../config/promptFileLocations.js';

/**
 * Activation events for prompt file providers.
 */
export const CUSTOM_AGENT_PROVIDER_ACTIVATION_EVENT = 'onCustomAgentProvider';
export const INSTRUCTIONS_PROVIDER_ACTIVATION_EVENT = 'onInstructionsProvider';
export const PROMPT_FILE_PROVIDER_ACTIVATION_EVENT = 'onPromptFileProvider';
export const SKILL_PROVIDER_ACTIVATION_EVENT = 'onSkillProvider';

/**
 * Context for querying prompt files.
 */
export interface IPromptFileContext { }

/**
 * Represents a prompt file resource from an external provider.
 */
export interface IPromptFileResource {
	/**
	 * The URI to the agent or prompt resource file.
	 */
	readonly uri: URI;
}

/**
 * Provides prompt services.
 */
export const IPromptsService = createDecorator<IPromptsService>('IPromptsService');

/**
 * Where the prompt is stored.
 */
export enum PromptsStorage {
	local = 'local',
	user = 'user',
	extension = 'extension'
}

/**
 * The type of source for extension agents.
 */
export enum ExtensionAgentSourceType {
	contribution = 'contribution',
	provider = 'provider',
}

/**
 * Represents a prompt path with its type.
 * This is used for both prompt files and prompt source folders.
 */
export type IPromptPath = IExtensionPromptPath | ILocalPromptPath | IUserPromptPath;


export interface IPromptPathBase {
	/**
	 * URI of the prompt.
	 */
	readonly uri: URI;

	/**
	 * Storage of the prompt.
	 */
	readonly storage: PromptsStorage;

	/**
	 * Type of the prompt (e.g. 'prompt' or 'instructions').
	 */
	readonly type: PromptsType;

	/**
	 * Identifier of the contributing extension (only when storage === PromptsStorage.extension).
	 */
	readonly extension?: IExtensionDescription;

	readonly name?: string;

	readonly description?: string;
}

export interface IExtensionPromptPath extends IPromptPathBase {
	readonly storage: PromptsStorage.extension;
	readonly extension: IExtensionDescription;
	readonly source: ExtensionAgentSourceType;
	readonly name?: string;
	readonly description?: string;
}
export interface ILocalPromptPath extends IPromptPathBase {
	readonly storage: PromptsStorage.local;
}
export interface IUserPromptPath extends IPromptPathBase {
	readonly storage: PromptsStorage.user;
}

export type IAgentSource = {
	readonly storage: PromptsStorage.extension;
	readonly extensionId: ExtensionIdentifier;
	readonly type: ExtensionAgentSourceType;
} | {
	readonly storage: PromptsStorage.local | PromptsStorage.user;
};

/**
 * The visibility/availability of an agent.
 * - 'all': available as custom agent in picker AND can be used as subagent
 * - 'user': only available in the custom agent picker
 * - 'agent': only usable as subagent by the subagent tool
 * - 'hidden': neither in picker nor usable as subagent
 */
export type ICustomAgentVisibility = {
	readonly userInvokable: boolean;
	readonly agentInvokable: boolean;
};

export function isCustomAgentVisibility(obj: unknown): obj is ICustomAgentVisibility {
	if (typeof obj !== 'object' || obj === null) {
		return false;
	}
	const v = obj as { userInvokable?: unknown; agentInvokable?: unknown };
	return typeof v.userInvokable === 'boolean' && typeof v.agentInvokable === 'boolean';
}

export interface ICustomAgent {
	/**
	 * URI of a custom agent file.
	 */
	readonly uri: URI;

	/**
	 * Name of the custom agent as used in prompt files or contexts
	 */
	readonly name: string;

	/**
	 * Description of the agent
	 */
	readonly description?: string;

	/**
	 * Tools metadata in the prompt header.
	 */
	readonly tools?: readonly string[];

	/**
	 * Model metadata in the prompt header.
	 */
	readonly model?: readonly string[];

	/**
	 * Argument hint metadata in the prompt header that describes what inputs the agent expects or supports.
	 */
	readonly argumentHint?: string;

	/**
	 * Target metadata in the prompt header.
	 */
	readonly target?: string;

	/**
	 * What visibility the agent has (user invokable, subagent invokable).
	 */
	readonly visibility: ICustomAgentVisibility;

	/**
	 * Contents of the custom agent file body and other agent instructions.
	 */
	readonly agentInstructions: IChatModeInstructions;

	/**
	 * Hand-offs defined in the custom agent file.
	 */
	readonly handOffs?: readonly IHandOff[];

	/**
	 * List of subagent names that can be used by the agent.
	 * If empty, no subagents are available. If ['*'] or undefined, all agents can be used.
	 */
	readonly agents?: readonly string[];

	/**
	 * Where the agent was loaded from.
	 */
	readonly source: IAgentSource;
}

export interface IAgentInstructions {
	readonly content: string;
	readonly toolReferences: readonly IVariableReference[];
	readonly metadata?: Record<string, boolean | string | number>;
}

export interface IChatPromptSlashCommand {
	readonly name: string;
	readonly description: string | undefined;
	readonly argumentHint: string | undefined;
	readonly promptPath: IPromptPath;
	readonly parsedPromptFile: ParsedPromptFile;
}

export interface IAgentSkill {
	readonly uri: URI;
	readonly storage: PromptsStorage;
	readonly name: string;
	readonly description: string | undefined;
}

/**
 * Reason why a prompt file was skipped during discovery.
 */
export type PromptFileSkipReason =
	| 'missing-name'
	| 'missing-description'
	| 'name-mismatch'
	| 'duplicate-name'
	| 'parse-error'
	| 'disabled';

/**
 * Result of discovering a single prompt file.
 */
export interface IPromptFileDiscoveryResult {
	readonly uri: URI;
	readonly storage: PromptsStorage;
	readonly status: 'loaded' | 'skipped';
	readonly name?: string;
	readonly skipReason?: PromptFileSkipReason;
	/** Error message if parse-error */
	readonly errorMessage?: string;
	/** For duplicates, the URI of the file that took precedence */
	readonly duplicateOf?: URI;
	/** Extension ID if from extension */
	readonly extensionId?: string;
}

/**
 * Summary of prompt file discovery for a specific type.
 */
export interface IPromptDiscoveryInfo {
	readonly type: PromptsType;
	readonly files: readonly IPromptFileDiscoveryResult[];
}

/**
 * Provides prompt services.
 */
export interface IPromptsService extends IDisposable {
	readonly _serviceBrand: undefined;

	/**
	 * The parsed prompt file for the provided text model.
	 * @param textModel Returns the parsed prompt file.
	 */
	getParsedPromptFile(textModel: ITextModel): ParsedPromptFile;

	/**
	 * List all available prompt files.
	 */
	listPromptFiles(type: PromptsType, token: CancellationToken): Promise<readonly IPromptPath[]>;

	/**
	 * List all available prompt files.
	 */
	listPromptFilesForStorage(type: PromptsType, storage: PromptsStorage, token: CancellationToken): Promise<readonly IPromptPath[]>;

	/**
	 * Get a list of prompt source folders based on the provided prompt type.
	 */
	getSourceFolders(type: PromptsType): Promise<readonly IPromptPath[]>;

	/**
	 * Get a list of resolved prompt source folders with full metadata.
	 * This includes displayPath, isDefault, and storage information.
	 * Used for diagnostics and config-info displays.
	 */
	getResolvedSourceFolders(type: PromptsType): Promise<readonly IResolvedPromptSourceFolder[]>;

	/**
	 * Validates if the provided command name is a valid prompt slash command.
	 */
	isValidSlashCommandName(name: string): boolean;

	/**
	 * Gets the prompt file for a slash command.
	 */
	resolvePromptSlashCommand(command: string, token: CancellationToken): Promise<IChatPromptSlashCommand | undefined>;

	/**
	 * Event that is triggered when the slash command to ParsedPromptFile cache is updated.
	 * Event handlers can use {@link resolvePromptSlashCommand} to retrieve the latest data.
	 */
	readonly onDidChangeSlashCommands: Event<void>;

	/**
	 * Returns a prompt command if the command name is valid.
	 */
	getPromptSlashCommands(token: CancellationToken): Promise<readonly IChatPromptSlashCommand[]>;

	/**
	 * Returns the prompt command name for the given URI.
	 */
	getPromptSlashCommandName(uri: URI, token: CancellationToken): Promise<string>;

	/**
	 * Event that is triggered when the list of custom agents changes.
	 */
	readonly onDidChangeCustomAgents: Event<void>;

	/**
	 * Finds all available custom agents
	 */
	getCustomAgents(token: CancellationToken): Promise<readonly ICustomAgent[]>;

	/**
	 * Parses the provided URI
	 * @param uris
	 */
	parseNew(uri: URI, token: CancellationToken): Promise<ParsedPromptFile>;

	/**
	 * Internal: register a contributed file. Returns a disposable that removes the contribution.
	 * Not intended for extension authors; used by contribution point handler.
	 */
	registerContributedFile(type: PromptsType, uri: URI, extension: IExtensionDescription, name: string | undefined, description: string | undefined): IDisposable;


	getPromptLocationLabel(promptPath: IPromptPath): string;

	/**
	 * Gets list of all AGENTS.md files in the workspace.
	 */
	findAgentMDsInWorkspace(token: CancellationToken): Promise<URI[]>;

	/**
	 * Gets list of AGENTS.md files.
	 * @param includeNested Whether to include AGENTS.md files from subfolders, or only from the root.
	 */
	listAgentMDs(token: CancellationToken, includeNested: boolean): Promise<URI[]>;

	/**
	 * Gets list of .github/copilot-instructions.md files.
	 */
	listCopilotInstructionsMDs(token: CancellationToken): Promise<URI[]>;

	/**
	 * For a chat mode file URI, return the name of the agent file that it should use.
	 * @param oldURI
	 */
	getAgentFileURIFromModeFile(oldURI: URI): URI | undefined;

	/**
	 * Returns the list of disabled prompt file URIs for a given type. By default no prompt files are disabled.
	 */
	getDisabledPromptFiles(type: PromptsType): ResourceSet;

	/**
	 * Persists the set of disabled prompt file URIs for the given type.
	 */
	setDisabledPromptFiles(type: PromptsType, uris: ResourceSet): void;

	/**
	 * Registers a prompt file provider that can provide prompt files for repositories.
	 * @param extension The extension registering the provider.
	 * @param type The type of contribution.
	 * @param provider The provider implementation with optional change event.
	 * @returns A disposable that unregisters the provider when disposed.
	 */
	registerPromptFileProvider(extension: IExtensionDescription, type: PromptsType, provider: {
		onDidChangePromptFiles?: Event<void>;
		providePromptFiles: (context: IPromptFileContext, token: CancellationToken) => Promise<IPromptFileResource[] | undefined>;
	}): IDisposable;

	/**
	 * Gets list of agent skills files.
	 */
	findAgentSkills(token: CancellationToken): Promise<IAgentSkill[] | undefined>;

	/**
	 * Gets detailed discovery information for a prompt type.
	 * This includes all files found and their load/skip status with reasons.
	 * Used for diagnostics and config-info displays.
	 */
	getPromptDiscoveryInfo(type: PromptsType, token: CancellationToken): Promise<IPromptDiscoveryInfo>;
}
