/**
 * Google (Gemini) adapter for the Vvveb AI pipeline - generateContent API.
 *
 * Handles: text parts, "thought" reasoning parts, functionCall calls,
 * functionResponse turns, inlineData images/PDFs/audio, fileData upload
 * references, usageMetadata and the Google error shape.
 *
 * Streaming uses :streamGenerateContent?alt=sse; each SSE data payload is a
 * partial GenerateContentResponse whose parts carry incremental deltas.
 */
(function (window) {
	"use strict";

	const Ai = window.VvvebAi;

	const BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';

	function buildContents(messages) {
		const contents = [];
		for (const m of messages || []) {
			if (m.role === 'system') continue; // sent as systemInstruction
			if (m.role === 'tool') {
				// neutral tool answer message -> functionResponse part
				contents.push({
					role: 'user',
					parts: [{ functionResponse: { name: m.name || 'tool', response: { result: Ai.textOf(m) } } }]
				});
				continue;
			}
			const role = m.role === 'assistant' ? 'model' : 'user';
			const parts = [];

			for (const b of m.content || []) {
				switch (b.type) {
					case 'text':
						parts.push({ text: b.text });
						break;
					case 'thinking':
						parts.push({ text: b.text, thought: true });
						break;
					case 'image': {
						if (b.fileId || (b.url && !/^data:/.test(b.url))) {
							parts.push({ fileData: { fileUri: b.fileId || b.url, mimeType: b.mediaType || 'image/png' } });
						} else {
							parts.push({ inlineData: { mimeType: b.mediaType || 'image/png', data: Ai.dataUrlParts(b.dataUrl)[1] } });
						}
						break;
					}
					case 'file': {
						if (b.fileId) {
							parts.push({ fileData: { fileUri: b.fileId, mimeType: b.mediaType || 'application/pdf' } });
						} else {
							parts.push({ inlineData: { mimeType: b.mediaType || 'application/pdf', data: Ai.dataUrlParts(b.dataUrl)[1] } });
						}
						break;
					}
					case 'audio':
						parts.push({ inlineData: { mimeType: b.mediaType || 'audio/webm', data: Ai.dataUrlParts(b.dataUrl)[1] } });
						break;
					case 'tool_call':
						parts.push({ functionCall: { name: b.name, args: b.arguments || {} } });
						break;
					case 'tool_result':
						parts.push({
							functionResponse: {
								name: b.name || 'tool',
								response: { result: typeof b.plain === 'string' ? b.plain : JSON.stringify(b.plain == null ? '' : b.plain) }
							}
						});
						break;
				}
			}
			if (!parts.length) parts.push({ text: '' });
			contents.push({ role, parts });
		}
		return contents;
	}

	function buildRequest(payload, options) {
		options = options || {};
		const model = payload.model || options.model || 'gemini-2.0-flash';
		const method = payload.stream ? 'streamGenerateContent?alt=sse' : 'generateContent';

		const body = {
			contents: buildContents(payload.messages),
			generationConfig: {
				temperature: Ai.numOr(payload.temperature, options.temperature, 0),
				maxOutputTokens: parseInt(payload.maxTokens || options.max_tokens, 10) || 1024
			}
		};
		if (payload.system) body.systemInstruction = { parts: [{ text: String(payload.system) }] };
		if (payload.tools && payload.tools.length) {
			body.tools = [{
				functionDeclarations: payload.tools.map(t => {
					let isCustom = (t.type||'').toLowerCase()==='custom';
					return {
						name: t.name || t?.function?.name,
						description: t.description || t?.function?.description || '',
						parameters: isCustom ? (t.parameters || { type: 'object', properties: { code: {type:'string', description:'Code to execute'} } }) : (t.parameters || t?.function?.parameters || { type: 'object', properties: {} })
					};
				})
			}];
		}
		if (payload.thinking && payload.thinking.budgetTokens) {
			body.generationConfig.thinkingConfig = { thinkingBudget: parseInt(payload.thinking.budgetTokens, 10) || 2048 };
		}

		const headers = {};
		if (!options.omitAuth && options.key) headers['x-goog-api-key'] = options.key;

		const url = options.url ||
			BASE_URL + '/models/' + encodeURIComponent(model) + ':' + method;

		return { url, headers, body };
	}

	function parseParts(parts) {
		let text = '', thinking = '';
		const toolCalls = [];
		for (const p of parts || []) {
			if (p.functionCall) {
				toolCalls.push({
					id: p.functionCall.id || ('call_' + Math.random().toString(36).slice(2)),
					name: p.functionCall.name,
					arguments: p.functionCall.args || {}
				});
			} else if (typeof p.text === 'string') {
				let t = p.text;
				if (t && (/<(tool_calls|tool_call|invoke)/i.test(t) || /\[thinking|\[think|<think/i.test(t))){
					let extra = Ai.extractToolCallsFromText ? Ai.extractToolCallsFromText(t) : [];
					for (let c of extra) toolCalls.push(c);
					if (Ai.stripToolCallTags) t = Ai.stripToolCallTags(t); if (Ai.stripThinkingTags) t = Ai.stripThinkingTags(t);
					if (!t) continue;
					if (p.thought) thinking += t;
					else text += t;
				} else {
					if (p.thought) thinking += t;
					else text += t;
				}
			}
		}
		return { text, thinking, toolCalls };
	}

	function parseResponse(data) {
		const cand = (data.candidates && data.candidates[0]) || {};
		const parsed = parseParts(cand.content && cand.content.parts);
		return Object.assign(parsed, {
			usage: {
				inputTokens: data.usageMetadata && data.usageMetadata.promptTokenCount,
				outputTokens: data.usageMetadata && data.usageMetadata.candidatesTokenCount,
				cachedTokens: data.usageMetadata && (data.usageMetadata.cachedContentTokenCount ?? 0),
				totalTokens: data.usageMetadata && ((data.usageMetadata.promptTokenCount||0)+(data.usageMetadata.candidatesTokenCount||0))
			},
			stopReason: cand.finishReason,
			raw: data
		});
	}

	function createStreamParser(emit) {
		let text = '', thinking = '', usage = null, stopReason = null;
		const toolCalls = [];

		const sse = Ai.createSseParser(function (data) {
			const json = Ai.safeJsonParse(data, null);
			if (!json) return;
			if (json.error) { emit({ type: 'error', error: new Error(Ai.extractError(json)) }); return; }
			if (json.usageMetadata) usage = json.usageMetadata;

			const cand = (json.candidates && json.candidates[0]) || {};
			if (cand.finishReason) stopReason = cand.finishReason;

			for (const p of (cand.content && cand.content.parts) || []) {
				if (p.functionCall) {
					const call = {
						id: p.functionCall.id || ('call_' + Math.random().toString(36).slice(2)),
						name: p.functionCall.name,
						arguments: p.functionCall.args || {}
					};
					toolCalls.push(call);
					emit({ type: 'tool-call', call });
				} else if (typeof p.text === 'string' && p.text) {
					let raw = p.text;
					if ((Ai.stripToolCallTags && /<(tool_calls|tool_call|invoke)/i.test(raw)) || (Ai.stripThinkingTags && /\[thinking|\[think|<think/i.test(raw))){
						let extracted = Ai.extractToolCallsFromText ? Ai.extractToolCallsFromText(raw) : [];
						for (let call of extracted){ toolCalls.push(call); emit({ type: 'tool-call', call }); }
						let cleaned = Ai.stripToolCallTags(raw);
						if (!cleaned || !cleaned.trim()) continue;
						raw = cleaned;
					}
					if (p.thought) { thinking += raw; emit({ type: 'thinking-delta', text: raw }); }
					else { text += raw; emit({ type: 'text-delta', text: raw }); }
				}
			}
		});

		return {
			feed: sse.feed,
			end: sse.end,
			getResult() {
				return {
					text, thinking, toolCalls,
					usage: {
						inputTokens: usage && usage.promptTokenCount,
						outputTokens: usage && usage.candidatesTokenCount,
						cachedTokens: usage && (usage.cachedContentTokenCount ?? 0),
						totalTokens: usage && ((usage.promptTokenCount||0)+(usage.candidatesTokenCount||0))
					},
					stopReason,
					raw: null
				};
			}
		};
	}

	Ai.registerProvider('google', {
		name: 'google',
		label: 'Google Gemini',
		defaultModel: 'gemini-2.0-flash',
		buildRequest,
		parseResponse,
		createStreamParser,
		normalizeError: Ai.extractError
	});
})(window);
