import * as vscode from "vscode"

import {
	type GroupEntry,
	type ModeConfig,
	type CustomModePrompts,
	type ToolGroup,
	type PromptComponent,
	DEFAULT_MODES,
	resolveI18nPrompt,
} from "@roo-code/types"

import { addCustomInstructions } from "../core/prompts/sections/custom-instructions"

import { TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "./tools"

export type Mode = string
export type CostrictCodeMode = "vibe" | "strict" | "plan" | "raw"
export type PromptTag = "systempromptmodified" | "rulesmodified" | "promptcustomized"

// Helper to extract group name regardless of format
export function getGroupName(group: GroupEntry): ToolGroup {
	if (typeof group === "string") {
		return group
	}

	return group[0]
}

// Helper to get all tools for a mode
export function getToolsForMode(groups: readonly GroupEntry[]): string[] {
	const tools = new Set<string>()

	// Add tools from each group (excluding customTools which are opt-in only)
	groups.forEach((group) => {
		const groupName = getGroupName(group)
		const groupConfig = TOOL_GROUPS[groupName]
		groupConfig.tools.forEach((tool: string) => tools.add(tool))
	})

	// Always add required tools
	ALWAYS_AVAILABLE_TOOLS.forEach((tool) => tools.add(tool))

	return Array.from(tools)
}

// Main modes configuration as an ordered array
export const modes = DEFAULT_MODES

// Export the default mode slug
export const defaultModeSlug = modes[0].slug

// Helper functions
export function getModeBySlug(slug: string, customModes?: ModeConfig[]): ModeConfig | undefined {
	// Check custom modes first
	const customMode = customModes?.find((mode) => mode.slug === slug)
	if (customMode) {
		return customMode
	}
	// Then check built-in modes
	return modes.find((mode) => mode.slug === slug)
}

export function getModeConfig(slug: string, customModes?: ModeConfig[]): ModeConfig {
	const mode = getModeBySlug(slug, customModes)
	if (!mode) {
		throw new Error(`No mode found for slug: ${slug}`)
	}
	return mode
}

// Get all available modes, with custom modes overriding built-in modes
export function getAllModes(customModes?: ModeConfig[]): ModeConfig[] {
	if (!customModes?.length) {
		return [...modes]
	}

	// Start with built-in modes
	const allModes = [...modes]

	// Process custom modes
	customModes.forEach((customMode) => {
		const index = allModes.findIndex((mode) => mode.slug === customMode.slug)
		if (index !== -1) {
			// Override existing mode
			allModes[index] = customMode
		} else {
			// Add new mode
			allModes.push(customMode)
		}
	})

	return allModes
}

// Filter modes based on costrictCodeMode setting
export function filterModesByCostrictCodeMode(
	modes: ModeConfig[],
	costrictCodeMode?: CostrictCodeMode,
	apiProvider?: string,
): ModeConfig[] {
	return modes.filter((mode) => {
		if (apiProvider === "costrict") {
			const modelGroup = mode.costrictCodeModeGroup ? mode.costrictCodeModeGroup?.split(",") : []

			if (costrictCodeMode === "vibe") return !mode.costrictCodeModeGroup || modelGroup.includes(costrictCodeMode)
			return modelGroup.includes(costrictCodeMode!)
		}

		return !mode.apiProvider || mode.apiProvider === apiProvider
	})
}

// Check if a mode is custom or an override
export function isCustomMode(slug: string, customModes?: ModeConfig[]): boolean {
	return !!customModes?.some((mode) => mode.slug === slug)
}

/**
 * Find a mode by its slug, don't fall back to built-in modes
 */
export function findModeBySlug(slug: string, modes: readonly ModeConfig[] | undefined): ModeConfig | undefined {
	return modes?.find((mode) => mode.slug === slug)
}

/**
 * Get the mode selection based on the provided mode slug, prompt component, and custom modes.
 * If a custom mode is found, it takes precedence over the built-in modes.
 * If no custom mode is found, the built-in mode is used with partial merging from promptComponent.
 * If neither is found, the default mode is used.
 */
export function getModeSelection(
	mode: string,
	promptComponent?: PromptComponent,
	customModes?: ModeConfig[],
	language?: string,
	modelId?: string,
) {
	const customMode = findModeBySlug(mode, customModes)
	const builtInMode = findModeBySlug(mode, modes)

	// If we have a custom mode, use it entirely
	if (customMode) {
		return {
			roleDefinition: customMode.roleDefinition || "",
			baseInstructions: customMode.customInstructions || "",
			description: customMode.description || "",
		}
	}

	// Otherwise, use built-in mode as base and merge with promptComponent and i18n
	const baseMode = builtInMode || modes[0] // fallback to default mode

	// modelFamily is undefined for now, reserved for future extension
	const i18n = resolveI18nPrompt(mode, language, undefined /* modelFamily */)
	return {
		roleDefinition: promptComponent?.roleDefinition || i18n?.roleDefinition || baseMode.roleDefinition || "",
		baseInstructions:
			promptComponent?.customInstructions || i18n?.customInstructions || baseMode.customInstructions || "",
		description: baseMode.description ?? "",
	}
}

// Custom error class for file restrictions
export class FileRestrictionError extends Error {
	constructor(mode: string, pattern: string, description: string | undefined, filePath: string, tool?: string) {
		const toolInfo = tool ? `Tool '${tool}' in mode '${mode}'` : `This mode (${mode})`
		super(
			`${toolInfo} can only edit files matching pattern: ${pattern}${description ? ` (${description})` : ""}. Got: ${filePath}`,
		)
		this.name = "FileRestrictionError"
	}
}

// Create the mode-specific default prompts
export const defaultPrompts: Readonly<CustomModePrompts> = Object.freeze(
	Object.fromEntries(
		modes.map((mode) => [
			mode.slug,
			{
				roleDefinition: mode.roleDefinition,
				whenToUse: mode.whenToUse,
				customInstructions: mode.customInstructions,
				description: mode.description,
			},
		]),
	),
)

// Helper function to get all modes with their prompt overrides from extension state
export async function getAllModesWithPrompts(context: vscode.ExtensionContext): Promise<ModeConfig[]> {
	const customModes = (await context.globalState.get<ModeConfig[]>("customModes")) || []
	const customModePrompts = (await context.globalState.get<CustomModePrompts>("customModePrompts")) || {}

	const allModes = getAllModes(customModes)
	return allModes.map((mode) => ({
		...mode,
		roleDefinition: customModePrompts[mode.slug]?.roleDefinition ?? mode.roleDefinition,
		whenToUse: customModePrompts[mode.slug]?.whenToUse ?? mode.whenToUse,
		customInstructions: customModePrompts[mode.slug]?.customInstructions ?? mode.customInstructions,
		// description is not overridable via customModePrompts, so we keep the original
	}))
}

// Helper function to get complete mode details with all overrides
export async function getFullModeDetails(
	modeSlug: string,
	customModes?: ModeConfig[],
	customModePrompts?: CustomModePrompts,
	options?: {
		cwd?: string
		globalCustomInstructions?: string
		language?: string
		shell?: string
	},
): Promise<ModeConfig> {
	// First get the base mode config from custom modes or built-in modes
	const baseMode = getModeBySlug(modeSlug, customModes) || modes.find((m) => m.slug === modeSlug) || modes[0]

	// Check for any prompt component overrides
	const promptComponent = customModePrompts?.[modeSlug]

	// Get the base custom instructions
	const baseCustomInstructions = promptComponent?.customInstructions || baseMode.customInstructions || ""
	const baseWhenToUse = promptComponent?.whenToUse || baseMode.whenToUse || ""
	const baseDescription = promptComponent?.description || baseMode.description || ""

	// If we have cwd, load and combine all custom instructions
	let fullCustomInstructions = baseCustomInstructions
	if (options?.cwd) {
		fullCustomInstructions = await addCustomInstructions(
			baseCustomInstructions,
			options.globalCustomInstructions || "",
			options.cwd,
			modeSlug,
			{ language: options.language, shell: options.shell },
		)
	}

	// Return mode with any overrides applied
	return {
		...baseMode,
		roleDefinition: promptComponent?.roleDefinition || baseMode.roleDefinition,
		whenToUse: baseWhenToUse,
		description: baseDescription,
		customInstructions: fullCustomInstructions,
	}
}

// Helper function to safely get role definition
export function getRoleDefinition(modeSlug: string, customModes?: ModeConfig[], language?: string): string {
	const mode = getModeBySlug(modeSlug, customModes)
	if (!mode) {
		console.warn(`No mode found for slug: ${modeSlug}`)
		return ""
	}

	// Do not apply i18n overrides for custom modes (including built-in overrides)
	const customMode = findModeBySlug(modeSlug, customModes)
	if (customMode) {
		return mode.roleDefinition
	}

	const i18nPrompt = resolveI18nPrompt(modeSlug, language)
	return i18nPrompt?.roleDefinition ?? mode.roleDefinition
}

// Helper function to safely get description
export function getDescription(modeSlug: string, customModes?: ModeConfig[], language?: string): string {
	const mode = getModeBySlug(modeSlug, customModes)
	if (!mode) {
		console.warn(`No mode found for slug: ${modeSlug}`)
		return ""
	}

	// Do not apply i18n overrides for custom modes (including built-in overrides)
	const customMode = findModeBySlug(modeSlug, customModes)
	if (customMode) {
		return mode.description ?? ""
	}

	const i18nPrompt = resolveI18nPrompt(modeSlug, language)
	return i18nPrompt?.description ?? mode.description ?? ""
}

// Helper function to safely get whenToUse
export function getWhenToUse(modeSlug: string, customModes?: ModeConfig[], language?: string): string {
	const mode = getModeBySlug(modeSlug, customModes)
	if (!mode) {
		console.warn(`No mode found for slug: ${modeSlug}`)
		return ""
	}

	// Do not apply i18n overrides for custom modes (including built-in overrides)
	const customMode = findModeBySlug(modeSlug, customModes)
	if (customMode) {
		return mode.whenToUse ?? ""
	}

	const i18nPrompt = resolveI18nPrompt(modeSlug, language)
	return i18nPrompt?.whenToUse ?? mode.whenToUse ?? ""
}

// Helper function to safely get custom instructions
export function getCustomInstructions(modeSlug: string, customModes?: ModeConfig[], language?: string): string {
	const mode = getModeBySlug(modeSlug, customModes)
	if (!mode) {
		console.warn(`No mode found for slug: ${modeSlug}`)
		return ""
	}

	// Do not apply i18n overrides for custom modes (including built-in overrides)
	const customMode = findModeBySlug(modeSlug, customModes)
	if (customMode) {
		return mode.customInstructions ?? ""
	}

	const i18nPrompt = resolveI18nPrompt(modeSlug, language)
	return i18nPrompt?.customInstructions ?? mode.customInstructions ?? ""
}

export function getPromptTagsForMode(
	modeSlug: string,
	customModePrompts?: CustomModePrompts,
	customModes?: ModeConfig[],
): PromptTag[] {
	const baseMode = getModeBySlug(modeSlug, customModes) || modes.find((mode) => mode.slug === modeSlug) || modes[0]
	const promptComponent = customModePrompts?.[modeSlug]
	const promptTags = new Set<PromptTag>()

	if (
		promptComponent?.roleDefinition !== undefined &&
		promptComponent.roleDefinition !== (baseMode.roleDefinition || "")
	) {
		promptTags.add("systempromptmodified")
	}

	if (
		promptComponent?.customInstructions !== undefined &&
		promptComponent.customInstructions !== (baseMode.customInstructions || "")
	) {
		promptTags.add("rulesmodified")
	}

	if (promptTags.size > 0) {
		promptTags.add("promptcustomized")
	}

	return Array.from(promptTags)
}
