'use strict';

const axios = require('axios');

axios.defaults.timeout = 0;

class OllamaProvider {
	constructor(config) {
		this.config = config;
		this.baseUrl = config.baseUrl || 'http://localhost:11434';
		this.model = config.model || 'llama3.2';
		this.messages = [];
		this.tools = [];
	}

	supportsTools() {
		return true;
	}

	setTools(tools) {
		this.tools = tools;
	}

	async start(history) {
		this.messages = history || [];

		if (this.config.prompt) {
			console.log('Ollama provider initialized with model:', this.model);
		}
	}

	__settings() {
		return {
			temperature: this.config.temperature || 1,
			top_p: this.config.topP || 0.95,
			top_k: this.config.topK || 64,
			num_predict: this.config.maxOutputTokens || 2048,
			num_ctx: this.config.num_ctx,
		};
	}

	__jsonFormat() {
		return {
			type: 'array',
			items: {
				type: 'object',
				properties: {
					text: { type: 'string' },
					delay: { type: 'number' }
				},
				required: ['text', 'delay']
			}
		};
	}

	/**
	 * Strip markdown code fences (```json ... ```) from a response string.
	 * Ollama models (especially smaller ones) sometimes wrap their output in fences
	 * even when the system prompt says not to.
	 */
	static stripMarkdownFences(text) {
		if (!text || typeof text !== 'string') return text;
		let cleaned = text.trim();
		// Remove leading ```json or ``` fences
		cleaned = cleaned.replace(/^```(?:json)?\s*\n?/, '');
		// Remove trailing ``` fences
		cleaned = cleaned.replace(/\n?```\s*$/, '');
		return cleaned.trim();
	}

	async chat(message, retryCount = 0) {
		try {
			const messages = [
				{
					role: 'system',
					content: this.config.prompt || 'You are a helpful assistant.'
				},
				...this.messages.map(msg => ({
					role: msg.role === 'model' ? 'assistant' : 'user',
					content: msg.parts ? msg.parts.map(p => p.text).join('') : (msg.content || '')
				})),
				{
					role: 'user',
					content: message
				}
			];

			const requestBody = {
				model: this.model,
				messages: messages,
				stream: false,
				options: this.__settings()
			};

			// Only set format when NO tools are configured.
			// format + tools together confuses smaller models — they try to
			// satisfy both constraints and produce garbage (_/empty responses).
			const hasTools = this.tools && this.tools.length > 0;
			if (!hasTools) {
				requestBody.format = this.__jsonFormat();
			}

			if (hasTools) {
				requestBody.tools = this.tools.map(tool => ({
					type: 'function',
					function: {
						name: tool.name,
						description: tool.description,
						parameters: tool.parameters
					}
				}));
			}

			const response = await axios.post(
				`${this.baseUrl}/api/chat`,
				requestBody,
				{
					headers: {
						'Content-Type': 'application/json'
					}
				}
			);

			const rawContent = response.data.message.content;
			const messageData = response.data.message;
			console.log('Ollama response', rawContent)

			// Update history
			this.messages.push({
				role: 'user',
				parts: [{ text: message }],
				content: message
			});

			this.messages.push({
				role: 'model',
				parts: [{ text: rawContent }],
				content: rawContent
			});

			// The text() closure strips markdown code fences so consumers
			// (processResponse, getToolCalls) get clean content.
			const result = {
				response: {
					text: () => {
						let content = messageData.content || rawContent;
						content = OllamaProvider.stripMarkdownFences(content);
						return content;
					}
				}
			};

			// Ollama may return tool calls in messageData.tool_calls
			if (messageData.tool_calls) {
				result.tool_calls = messageData.tool_calls;
			}

			return result;
		} catch (error) {
			const errorDetails = {
				message: error.message,
				status: error.response?.status,
				data: error.response?.data,
				url: error.config?.url,
				retryCount: retryCount
			};
			console.log('Ollama API error details:', errorDetails);

			if (retryCount > 3) {
				throw new Error(`Ollama API error after ${retryCount} retries: ${error.message}`);
			}
			const baseDelay = Math.min(1000 * Math.pow(2, retryCount), 30000);
			const jitter = Math.random() * 1000;
			await new Promise(resolve => setTimeout(resolve, baseDelay + jitter));
			return await this.chat(message, retryCount + 1);
		}
	}

	setPrompt(prompt) {
		this.config.prompt = prompt;
	}

	getResponse(result) {
		return result.response.text();
	}

	async close() {
		this.messages = [];
	}
}

module.exports = OllamaProvider;
