import { ApiHandlerModel, ApiProviderInfo } from "@core/api"
import { AnthropicModelId, anthropicModels } from "@/shared/api"

const CLAUDE_VERSION_MATCH_REGEX = /[-_ ]([\d](?:\.[05])?)[-_ ]?/

export function isNextGenModelProvider(providerInfo: ApiProviderInfo): boolean {
	const providerId = normalize(providerInfo.providerId)
	return [
		"cline",
		"anthropic",
		"gemini",
		"vertex",
		"openrouter",
		"openai",
		"minimax",
		"openai-native",
		"openai-compatible",
		"openai-codex",
		"baseten",
		"vercel-ai-gateway",
		"oca",
	].some((id) => providerId === id)
}

export function modelDoesntSupportWebp(apiHandlerModel: ApiHandlerModel): boolean {
	const modelId = apiHandlerModel.id.toLowerCase()
	return modelId.includes("grok")
}

/**
 * Determines if reasoning content should be skipped for a given model
 * Currently skips reasoning for:
 * - Grok-4 models since they only display "thinking" without useful information
 * - Devstral models since they don't support reasoning_details field
 */
export function shouldSkipReasoningForModel(modelId?: string): boolean {
	if (!modelId) {
		return false
	}
	return modelId.includes("grok-4") || modelId.includes("devstral") || modelId.includes("glm")
}

export function isAnthropicModelId(modelId: string): modelId is AnthropicModelId {
	const CLAUDE_MODELS = ["sonnet", "opus", "haiku"]
	return modelId in anthropicModels || CLAUDE_MODELS.some((substring) => modelId.includes(substring))
}

export function isClaude4PlusModelFamily(id: string): boolean {
	const modelId = normalize(id)
	// Claude Code short aliases are always Claude 4+
	// These are used by ClaudeCodeHandler.getModel() when user selects "sonnet" or "opus"
	// Check before isAnthropicModelId to avoid type guard narrowing issues
	if (modelId === "sonnet" || modelId === "opus") {
		return true
	}
	if (!isAnthropicModelId(modelId)) {
		return false
	}
	// `-latest` aliases on the Anthropic API and OpenRouter currently resolve to
	// Claude 4.x (e.g. `claude-haiku-latest` → Haiku 4.5). Treat them as
	// next-gen so they don't get routed to the generic prompt variant whose
	// format-reminder placeholders confuse smaller / older models.
	if (modelId.endsWith("-latest") || /\b(?:sonnet|opus|haiku)-latest\b/.test(modelId)) {
		return true
	}
	// Get model version number
	const versionMatch = modelId.match(CLAUDE_VERSION_MATCH_REGEX)
	if (!versionMatch) {
		return false
	}
	const version = parseFloat(versionMatch[1])
	// Check if version is 4.0 or higher
	return version >= 4
}

export function isGemini2dot5ModelFamily(id: string): boolean {
	const modelId = normalize(id)
	return modelId.includes("gemini-2.5")
}

export function isGrok4ModelFamily(id: string): boolean {
	const modelId = normalize(id)
	return modelId.includes("grok-4")
}

export function isGPT5ModelFamily(id: string): boolean {
	const modelId = normalize(id)
	return modelId.includes("gpt-5") || modelId.includes("gpt5")
}

export function isGPT51Model(id: string): boolean {
	const modelId = normalize(id)
	return modelId.includes("gpt-5.1") || modelId.includes("gpt-5-1")
}

export function isGPT52Model(id: string): boolean {
	const modelId = normalize(id)
	return modelId.includes("gpt-5.2") || modelId.includes("gpt-5-2")
}

export function isGLMModelFamily(id: string): boolean {
	const modelId = normalize(id)
	return (
		modelId.includes("glm-4.6") ||
		modelId.includes("glm-4.5") ||
		modelId.includes("z-ai/glm") ||
		modelId.includes("zai-org/glm")
	)
}

export function isMinimaxModelFamily(id: string): boolean {
	const modelId = normalize(id)
	return modelId.includes("minimax")
}

export function isHermesModelFamily(id: string): boolean {
	const modelId = normalize(id)
	return (
		modelId.includes("hermes-4") ||
		modelId.includes("hermes4") ||
		modelId.includes("nous/hermes-4") ||
		modelId.includes("nous/hermes4") ||
		modelId.includes("nous-hermes-4") ||
		modelId.includes("nous/hermes4") ||
		modelId.includes("nousresearch/hermes-4") ||
		modelId.includes("nousresearch/hermes4")
	)
}

export function isNextGenOpenSourceModelFamily(id: string): boolean {
	const modelId = normalize(id)
	return ["kimi-k2"].some((substring) => modelId.includes(substring))
}

export function isDevstralModelFamily(id: string): boolean {
	const modelId = normalize(id)
	return modelId.includes("devstral")
}

export function isGemini3ModelFamily(id: string): boolean {
	const modelId = normalize(id)
	return modelId.includes("gemini3") || modelId.includes("gemini-3")
}

export function isDeepSeekNativeToolsModelFamily(id: string): boolean {
	const modelId = normalize(id)
	if (!modelId.includes("deepseek")) {
		return false
	}
	if (modelId.includes("speciale")) {
		return false
	}
	// 3.2 and V4-class DeepSeek models expose OpenAI-compatible tool_calls;
	// older 3.1/V3 chat models do not.
	return modelId.includes("3.2") || modelId.includes("v4") || /(?:^|[^0-9])4\.\d/.test(modelId)
}

export function isNextGenModelFamily(id: string): boolean {
	const modelId = normalize(id)
	return (
		isClaude4PlusModelFamily(modelId) ||
		isGemini2dot5ModelFamily(modelId) ||
		isGrok4ModelFamily(modelId) ||
		isGPT5ModelFamily(modelId) ||
		isMinimaxModelFamily(modelId) ||
		isGemini3ModelFamily(modelId) ||
		isNextGenOpenSourceModelFamily(modelId) ||
		isDeepSeekNativeToolsModelFamily(modelId)
	)
}

export function isLocalModel(providerInfo: ApiProviderInfo): boolean {
	const localProviders = ["lmstudio", "ollama"]
	return localProviders.includes(normalize(providerInfo.providerId))
}

/**
 * Parses a price string and converts it from per-token to per-million-tokens
 * @param priceString The price string to parse (e.g. from API responses)
 * @returns The price multiplied by 1,000,000 for per-million-token pricing, or 0 if invalid
 */
export function parsePrice(priceString: string | undefined): number {
	if (!priceString || priceString === "" || priceString === "0") {
		return 0
	}
	const parsed = parseFloat(priceString)
	if (Number.isNaN(parsed)) {
		return 0
	}
	// Convert from per-token to per-million-tokens (multiply by 1,000,000)
	return parsed * 1_000_000
}

/**
 * Determines if the given provider and model combination will use native tool calling.
 * Helpful if we need to quickly check this for prompts or other logic.
 * @param providerInfo The provider and model information
 * @param enableNativeToolCalls Whether the native tool calls setting is enabled
 * @returns true if the model will use native tool calling, false otherwise
 */
/**
 * Coarse classification of how reliably a given model emits parseable tool
 * calls under Cline's XML format. Used by the consecutive-mistake recovery
 * path in Task to decide when to switch to a richer hint or surface a
 * model-compatibility notice to the user.
 *
 * "high" — well-tested next-gen models with strong instruction following
 *          (Claude 4+, GPT-5, Gemini 2.5/3, Grok-4).
 * "medium" — known to work but with format quirks the normalizer covers
 *          (DeepSeek 3.2/V4, GLM, Hermes, Kimi).
 * "low" — everything else: older small models (Haiku 3.x), local Ollama /
 *          LMStudio, unknown OpenRouter / OpenAI-compatible IDs. These are
 *          the ones most likely to mimic the format-reminder placeholders
 *          literally and burn retries in the noToolsUsed loop.
 */
export type ToolCallReliabilityTier = "high" | "medium" | "low"

export function getToolCallReliabilityTier(providerInfo: ApiProviderInfo): ToolCallReliabilityTier {
	const id = normalize(providerInfo.model.id)
	if (
		isClaude4PlusModelFamily(id) ||
		isGPT5ModelFamily(id) ||
		isGemini2dot5ModelFamily(id) ||
		isGemini3ModelFamily(id) ||
		isGrok4ModelFamily(id) ||
		isMinimaxModelFamily(id)
	) {
		return "high"
	}
	if (
		isDeepSeekNativeToolsModelFamily(id) ||
		isNextGenOpenSourceModelFamily(id) ||
		isGLMModelFamily(id) ||
		isHermesModelFamily(id)
	) {
		return "medium"
	}
	return "low"
}

export function isNativeToolCallingConfig(providerInfo: ApiProviderInfo, enableNativeToolCalls: boolean): boolean {
	if (!enableNativeToolCalls) {
		return false
	}
	if (!isNextGenModelProvider(providerInfo)) {
		return false
	}
	const modelId = providerInfo.model.id.toLowerCase()
	return isNextGenModelFamily(modelId)
}

function normalize(text: string): string {
	let id = text.trim().toLowerCase()
	// Strip OpenRouter / Vertex / similar "vendor/model" prefixes so downstream
	// version regexes and substring checks work uniformly on the bare model ID.
	const slash = id.lastIndexOf("/")
	if (slash >= 0) {
		id = id.slice(slash + 1)
	}
	return id
}
