// AGPL-3.0-only
//
// This is free software for the public good of a permacomputer
// hosted at permacomputer.com, an always-on computer by the people, for the
// people. One which is durable, easy to repair, and distributed like tap water
// for machine learning intelligence.
//
// The permacomputer is community-owned infrastructure optimized around
// four values:
//
//   TRUTH    - First principles, math & science, open source code
//   FREEDOM  - Voluntary partnerships, freedom from tyranny & corporate control
//   HARMONY  - Minimal waste, self-renewing systems with diverse connections
//   LOVE     - Be yourself without hurting others, cooperation through natural law
//
// This software contributes to that vision by making machine learning
// accessible to everyone through a free, open, embeddable chat interface.
// Code is seeds to sprout on any abandoned technology.
//
// Learn more: https://www.permacomputer.com
//
// NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
//
// Copyright 2025-2026 TimeHexOn & foxhop & russell@unturf
// https://www.permacomputer.com

import { API_KEY, getAPIConfig } from "./config.js";
import { getSelectedModel, getSelectedModelEndpoint, getSelectedModelMaxTokens, getSelectedModelMaxCompletionTokens, modelRegistry } from "./models.js";
import { countTokens, countTokensWithUpstream } from "./token_estimator.js";

// Languages supported by Hermes 3 model (in English for reference)
export const SUPPORTED_LANGUAGES = {
	en: "English",
	zh: "Chinese (Simplified)",
	hi: "Hindi",
	es: "Spanish",
	fr: "French",
	ar: "Arabic",
	bn: "Bengali",
	ru: "Russian",
	pt: "Portuguese",
	ur: "Urdu",
	id: "Indonesian",
	de: "German",
	ja: "Japanese",
	sw: "Swahili",
	mr: "Marathi",
	te: "Telugu",
	tr: "Turkish",
	"zh-tw": "Chinese (Traditional)",
	ko: "Korean",
	it: "Italian",
	nl: "Dutch",
	pl: "Polish",
	vi: "Vietnamese",
	th: "Thai",
	uk: "Ukrainian",
	sv: "Swedish",
};

// REMOVED: splitHTMLIntoChunks function - no longer using chunking

// Native language names with proper scripts, accents, and authentic forms
export const NATIVE_LANGUAGE_NAMES = {
	en: "English",
	zh: "中文（简体）",
	hi: "हिंदी",
	es: "Español",
	fr: "Français",
	ar: "العَرَبِيَّة",
	bn: "বাংলা",
	ru: "Русский",
	pt: "Português",
	ur: "اُردُو",
	id: "Bahasa Indonesia",
	de: "Deutsch",
	ja: "日本語",
	sw: "Kiswahili",
	mr: "मराठी",
	te: "తెలుగు",
	tr: "Türkçe",
	"zh-tw": "中文（繁體）",
	ko: "한국어",
	it: "Italiano",
	nl: "Nederlands",
	pl: "Polski",
	vi: "Tiếng Việt",
	th: "ภาษาไทย",
	uk: "Українська",
	sv: "Svenska",
};

// Send message with custom history (for translation with minimal system prompt)
async function* sendMessageWithHistory(messageHistory) {
	// Get API configuration (custom or default)
	const apiConfig = await getAPIConfig();
	
	let apiUrl, headers, model;
	
	if (apiConfig.isCustom) {
		// Use custom API configuration
		apiUrl = `${apiConfig.endpoint}/chat/completions`;
		headers = apiConfig.headers;
		model = apiConfig.model;
	} else {
		// Use default Hermes configuration
		apiUrl = `${getSelectedModelEndpoint()}/chat/completions`;
		headers = {
			"Content-Type": "application/json",
			Authorization: `Bearer ${API_KEY}`,
		};
		model = getSelectedModel();
	}

	// Calculate available tokens for output based on current prompt
	const modelMaxTokens = getSelectedModelMaxTokens();
	const inputText = messageHistory.map(msg => msg.content).join('');
	const inputTokens = Math.ceil(inputText.length / 4); // Simple token estimation
	const outputReserve = Math.floor(modelMaxTokens * 0.4); // Reserve 40% for output
	let availableTokens = Math.max(512, modelMaxTokens - inputTokens - outputReserve);
	
	// Cap output tokens based on model's max completion tokens limit
	const maxCompletionTokens = getSelectedModelMaxCompletionTokens();
	availableTokens = Math.min(availableTokens, maxCompletionTokens);
	console.log(`Capped available tokens to model's max completion limit: ${maxCompletionTokens}`);
	
	console.log("Translation using endpoint:", apiUrl);
	console.log("Translation using model:", model);
	console.log("Translation token calculation: max=" + modelMaxTokens + ", input≈" + inputTokens + ", outputReserve=" + outputReserve + ", available≈" + availableTokens);

	const response = await fetch(apiUrl, {
		method: "POST",
		headers: headers,
		body: JSON.stringify({
			model: model,
			messages: messageHistory,
			temperature: 0.3, // Lower temperature for more consistent translation
			max_tokens: availableTokens,
			stream: true,
		}),
	});

	if (!response.ok) {
		const errorText = await response.text();
		console.error("Translation API Error:", {
			status: response.status,
			url: apiUrl,
			response: errorText
		});
		throw new Error(`HTTP error! status: ${response.status} from ${apiUrl}: ${errorText}`);
	}

	const reader = response.body.getReader();
	const decoder = new TextDecoder();
	let streamBuffer = "";

	while (true) {
		const { done, value } = await reader.read();
		if (done) break;

		streamBuffer += decoder.decode(value, { stream: true });
		const lines = streamBuffer.split("\n");

		for (let i = 0; i < lines.length - 1; i++) {
			const line = lines[i].trim();
			if (line.startsWith("data: ")) {
				const jsonData = line.slice(6);
				if (jsonData === "[DONE]") continue;

				try {
					const parsedData = JSON.parse(jsonData);
					const content = parsedData.choices[0].delta.content;
					if (content) {
						yield content;
					}
				} catch (error) {
					console.error("Error parsing JSON:", error);
				}
			}
		}

		streamBuffer = lines[lines.length - 1];
	}
}

// Preserve code blocks, URLs, and special formatting during translation
export function preserveSpecialContent(text) {
	const preservations = [];
	let preservedText = text;

	// HTML code elements (most important in HTML context)
	preservedText = preservedText.replace(
		/<script[\s\S]*?<\/script>/gi,
		(match) => {
			const index = preservations.length;
			const placeholder = `__CODE_${index}__`;
			preservations.push({ type: "CODE", content: match, placeholder });
			return placeholder;
		},
	);

	preservedText = preservedText.replace(/<pre[\s\S]*?<\/pre>/gi, (match) => {
		const index = preservations.length;
		const placeholder = `__CODE_${index}__`;
		preservations.push({ type: "CODE", content: match, placeholder });
		return placeholder;
	});

	preservedText = preservedText.replace(/<code[\s\S]*?<\/code>/gi, (match) => {
		const index = preservations.length;
		const placeholder = `__CODE_${index}__`;
		preservations.push({ type: "CODE", content: match, placeholder });
		return placeholder;
	});

	// Other code-related HTML elements
	preservedText = preservedText.replace(
		/<(kbd|samp|var)[\s\S]*?<\/\1>/gi,
		(match) => {
			const index = preservations.length;
			const placeholder = `__CODE_${index}__`;
			preservations.push({ type: "CODE", content: match, placeholder });
			return placeholder;
		},
	);

	// Markdown code blocks (for mixed content)
	preservedText = preservedText.replace(/```[\s\S]*?```/g, (match) => {
		const index = preservations.length;
		const placeholder = `__CODE_${index}__`;
		preservations.push({ type: "CODE", content: match, placeholder });
		return placeholder;
	});

	// Inline markdown code
	preservedText = preservedText.replace(/`[^`\n]+`/g, (match) => {
		const index = preservations.length;
		const placeholder = `__CODE_${index}__`;
		preservations.push({ type: "CODE", content: match, placeholder });
		return placeholder;
	});

	// URLs and URIs
	preservedText = preservedText.replace(
		/https?:\/\/[^\s<>"{}|\\^`[\]]+/g,
		(match) => {
			const index = preservations.length;
			const placeholder = `__URI_${index}__`;
			preservations.push({ type: "URI", content: match, placeholder });
			return placeholder;
		},
	);

	// Email addresses
	preservedText = preservedText.replace(
		/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
		(match) => {
			const index = preservations.length;
			const placeholder = `__URI_${index}__`;
			preservations.push({ type: "URI", content: match, placeholder });
			return placeholder;
		},
	);

	// Other HTML tags (after code elements are preserved)
	preservedText = preservedText.replace(/<[^>]+>/g, (match) => {
		const index = preservations.length;
		const placeholder = `__HTML_${index}__`;
		preservations.push({ type: "HTML", content: match, placeholder });
		return placeholder;
	});

	return { preservedText, preservations };
}

// Restore preserved content after translation
export function restoreSpecialContent(translatedText, preservations) {
	let restoredText = translatedText;

	// By reversing the array, we restore the outer elements (like HTML tags) first,
	// which then reveals the inner placeholders (like URIs and code) for subsequent replacement.
	for (let i = preservations.length - 1; i >= 0; i--) {
		const item = preservations[i];
		restoredText = restoredText.replace(
			new RegExp(item.placeholder, "g"),
			item.content,
		);
	}

	return restoredText;
}

// Calculate processing time estimate based on real Hermes/vLLM metrics (our default)
export function estimateProcessingTime(tokenCount) {
	// Default: Real vLLM Hermes performance metrics from production
	let promptTokensPerSec = 3144; // Hermes prompt processing speed
	let generationTokensPerSec = 121; // Hermes generation speed
	
	// Adjust for faster Groq endpoints
	const endpoint = getSelectedModelEndpoint();
	if (endpoint.includes('groq.com')) {
		promptTokensPerSec = 15000; // Groq processes prompts very quickly
		generationTokensPerSec = 800; // Groq generation is also much faster
	}

	const promptProcessingTime = tokenCount / promptTokensPerSec; // seconds for input processing
	const estimatedOutputTokens = tokenCount * 1.2; // Translation usually 1.2x input length
	const generationTime = estimatedOutputTokens / generationTokensPerSec; // seconds for generation

	// Add some buffer for network latency and processing overhead
	const totalTime = (promptProcessingTime + generationTime) * 1.3;

	return Math.ceil(totalTime);
}

// Internal function to translate text without preservation (for chunked translations)
async function translateTextRaw(text, targetLanguage) {
	// Ensure models are loaded before translation
	const { fetchModelsFromEndpoints } = await import("./models.js");
	await fetchModelsFromEndpoints();

	// Skip preservation - assume text is already preserved
	const preservedText = text;

	// Build the actual prompt that will be sent to the AI
	const prompt = `${preservedText}

Translate the above HTML content into ${SUPPORTED_LANGUAGES[targetLanguage]}. Preserve structure, syntax, and formatting. Do not translate placeholders like __CODE_0__, __URI_0__, __HTML_0__. Return only the translated HTML with no additional text.`;

	// Get accurate token count and timing estimate using upstream tokenizer
	// Tokenize the actual prompt that will be sent to the AI
	const tokenInfo = await countTokensWithUpstream(prompt, getSelectedModel());
	const estimatedSeconds = estimateProcessingTime(tokenInfo.totalTokens);

	console.log("=== TRANSLATION ANALYSIS ===");
	console.log(`Input tokens: ${tokenInfo.totalTokens}`);
	console.log(`Text length: ${tokenInfo.textLength} characters`);
	console.log(`Estimated processing time: ${estimatedSeconds} seconds`);
	console.log("Token breakdown:", tokenInfo.breakdown);

	// Debug logging
	console.log("=== RAW TRANSLATION DEBUG ===");
	console.log(
		"Input text:",
		text.substring(0, 300) + (text.length > 300 ? "..." : ""),
	);
	console.log(
		"Preserved text:",
		preservedText.substring(0, 300) + (preservedText.length > 300 ? "..." : ""),
	);

	// Use minimal system message for translation - focused purely on the task
	const translationHistory = [
		{
			role: "system",
			content:
				"You are a translation assistant. Translate text accurately while preserving all formatting and placeholders exactly as provided. Return only the translated content without any explanatory text or introductions.",
		},
		{
			role: "user",
			content: prompt,
		},
	];

	let translatedText = "";

	try {
		for await (const chunk of sendMessageWithHistory(translationHistory)) {
			translatedText += chunk;
		}

		// Clean up the response (remove any extra explanations)
		translatedText = translatedText.trim();
		console.log("AI response length:", translatedText.length);
		console.log(
			"AI response:",
			translatedText.substring(0, 300) +
				(translatedText.length > 300 ? "..." : ""),
		);

		// Validate response
		if (!translatedText || translatedText.length === 0) {
			throw new Error(
				"Translation API returned empty response. Please try again.",
			);
		}

		// Return raw translated text without restoration (for chunked translations)
		console.log("=== RAW TRANSLATION RESULT ===");
		console.log(`Raw translated length: ${translatedText.length} characters`);

		return translatedText;
	} catch (error) {
		console.error("Translation error:", error);
		throw new Error("Translation failed. Please try again.");
	}
}

// REMOVED: translateFragmentRaw - no longer using chunking
/* async function translateFragmentRaw(text, targetLanguage) {
	// Ensure models are loaded before translation
	const { fetchModelsFromEndpoints } = await import("./models.js");
	await fetchModelsFromEndpoints();

	// Skip preservation - assume text is already preserved
	const preservedText = text;

	// Build fragment-specific prompt
	const prompt = `Translate the following HTML fragment into ${SUPPORTED_LANGUAGES[targetLanguage]}. This is a partial section of a webpage, not a complete document. Preserve the exact HTML structure and only translate the text content. Do not add any additional HTML elements, headers, navigation, or wrapper tags. Do not translate or modify placeholders like __CODE_0__, __URI_0__, __HTML_0__, etc., as they represent code blocks, URLs, or HTML elements that should remain unchanged. Only translate the text within the provided HTML structure. Here is the HTML fragment to translate:

${preservedText}`;

	// Get accurate token count and timing estimate using upstream tokenizer
	const tokenInfo = await countTokensWithUpstream(prompt, getSelectedModel());
	const estimatedSeconds = estimateProcessingTime(tokenInfo.totalTokens);

	console.log("=== FRAGMENT TRANSLATION ANALYSIS ===");
	console.log(`Input tokens: ${tokenInfo.totalTokens}`);
	console.log(`Text length: ${tokenInfo.textLength} characters`);
	console.log(`Estimated processing time: ${estimatedSeconds} seconds`);

	// Fragment-specific system message
	const translationHistory = [
		{
			role: "system",
			content:
				"You are a translation assistant specializing in HTML fragments. Translate only the text content while preserving the exact HTML structure. Never add wrapper elements or complete partial documents. Return only the translated fragment as provided.",
		},
		{
			role: "user",
			content: prompt,
		},
	];

	let translatedText = "";

	try {
		for await (const chunk of sendMessageWithHistory(translationHistory)) {
			translatedText += chunk;
		}

		// Clean up the response
		translatedText = translatedText.trim();
		console.log("Fragment AI response length:", translatedText.length);

		// Validate response
		if (!translatedText || translatedText.length === 0) {
			throw new Error(
				"Fragment translation API returned empty response. Please try again.",
			);
		}

		console.log("=== FRAGMENT TRANSLATION RESULT ===");
		console.log(`Fragment translated length: ${translatedText.length} characters`);

		return translatedText;
	} catch (error) {
		console.error("Fragment translation error:", error);
		console.error("Error stack:", error.stack);
		throw new Error(`Fragment translation failed: ${error.message}`);
	}
} */

// REMOVED: translateFragment - no longer using chunking

// Translate text using Hermes AI with minimal system context
export async function translateText(text, targetLanguage) {
	const { preservedText, preservations } = preserveSpecialContent(text);
	
	// Use raw translation function
	const translatedText = await translateTextRaw(preservedText, targetLanguage);
	
	// Check if placeholders are still in the response  
	preservations.forEach((item, _index) => {
		const found = translatedText.includes(item.placeholder);
		console.log(
			`  Placeholder ${item.placeholder} found in response: ${found}`,
		);
	});

	// Restore preserved content
	const restored = restoreSpecialContent(translatedText, preservations);
	console.log("Restored text length:", restored.length);
	// Only log full text in debug mode, otherwise just show length
	if (restored.length < 500) {
		console.log("Restored text:", restored);
	} else {
		console.log("Restored text preview:", restored.substring(0, 100) + "...");
	}

	// Final validation
	if (!restored || restored.length === 0) {
		throw new Error("Translation processing failed. Please try again.");
	}

	console.log("=== FINAL RESULT ===");
	console.log(`Final translated length: ${restored.length} characters`);

	return restored;
}

// Translate HTML fragment with preservation (for chunked content)
async function translateFragment(text, targetLanguage) {
	const { preservedText, preservations } = preserveSpecialContent(text);
	
	// Use fragment-specific translation function
	const translatedText = await translateFragmentRaw(preservedText, targetLanguage);
	
	// Check if placeholders are still in the response  
	preservations.forEach((item, _index) => {
		const found = translatedText.includes(item.placeholder);
		console.log(
			`  Fragment placeholder ${item.placeholder} found in response: ${found}`,
		);
	});

	// Restore preserved content
	const restored = restoreSpecialContent(translatedText, preservations);
	console.log("Fragment restored text length:", restored.length);

	// Final validation
	if (!restored || restored.length === 0) {
		throw new Error("Fragment translation processing failed. Please try again.");
	}

	return restored;
}

// Extract page content for translation
export function extractPageContent() {
	// Clone the document to avoid modifying the original
	const documentClone = document.cloneNode(true);

	// Remove any open modals/dialogs that shouldn't be in the translation
	const modalsToRemove = documentClone.querySelectorAll(
		'dialog[open], #uncloseai-embedded-modal, [id*="modal"], [class*="modal"]',
	);
	modalsToRemove.forEach((modal) => modal.remove());

	// Return the entire HTML of the document to preserve head, styles, and scripts
	return documentClone.documentElement.outerHTML;
}

// Convert relative URLs to absolute URLs based on source URL
function convertRelativeToAbsolute(htmlContent, sourceUrl) {
	if (!sourceUrl) {
		return htmlContent;
	}

	try {
		const baseUrl = new URL(sourceUrl);
		const parser = new DOMParser();
		const doc = parser.parseFromString(htmlContent, 'text/html');

		// Convert relative URLs in various attributes
		const elementsWithUrls = [
			{ selector: 'a[href]', attr: 'href' },
			{ selector: 'img[src]', attr: 'src' },
			{ selector: 'link[href]', attr: 'href' },
			{ selector: 'script[src]', attr: 'src' },
			{ selector: 'iframe[src]', attr: 'src' },
			{ selector: 'source[src]', attr: 'src' },
			{ selector: 'audio[src]', attr: 'src' },
			{ selector: 'video[src]', attr: 'src' },
			{ selector: 'embed[src]', attr: 'src' },
			{ selector: 'object[data]', attr: 'data' },
			{ selector: 'form[action]', attr: 'action' }
		];

		elementsWithUrls.forEach(({ selector, attr }) => {
			const elements = doc.querySelectorAll(selector);
			elements.forEach(element => {
				const url = element.getAttribute(attr);
				if (url && !url.startsWith('http') && !url.startsWith('//') && !url.startsWith('#') && !url.startsWith('mailto:') && !url.startsWith('tel:')) {
					try {
						const absoluteUrl = new URL(url, baseUrl).href;
						element.setAttribute(attr, absoluteUrl);
					} catch (e) {
						// If URL conversion fails, keep the original
						console.warn('Failed to convert relative URL:', url, e);
					}
				}
			});
		});

		return doc.documentElement.outerHTML;
	} catch (error) {
		console.warn('Failed to convert relative URLs:', error);
		return htmlContent;
	}
}

// Translate any HTML content with optional URL resolution
export async function translateHTML(htmlContent, targetLanguage, sourceUrl = null) {
	const translationId = Math.random().toString(36).substr(2, 9);
	console.log(`🚀 STARTING TRANSLATION ${translationId} - Length: ${htmlContent?.length || 0} chars`);
	
	if (!htmlContent || htmlContent.length < 10) {
		throw new Error("Unable to extract meaningful content from provided HTML.");
	}

	// Convert relative URLs to absolute if sourceUrl is provided
	let processedContent = htmlContent;
	if (sourceUrl) {
		processedContent = convertRelativeToAbsolute(htmlContent, sourceUrl);
		console.log('Converted relative URLs using base URL:', sourceUrl);
	}

	// Use actual token limits instead of character limits
	const maxTokens = getSelectedModelMaxTokens();
	const reserveTokens = 1000; // Reserve tokens for prompt overhead
	const availableTokens = Math.max(maxTokens - reserveTokens, 2000); // Ensure minimum
	
	// Check if content needs chunking based on token count (on raw HTML, not preserved)
	const contentTokensResult = await countTokens(processedContent, getSelectedModel());
	const contentTokens = typeof contentTokensResult === 'object' ? contentTokensResult.count || contentTokensResult.totalTokens || 0 : contentTokensResult;
	
	// Account for translation expansion (typically 1.5-2x input size)
	const estimatedOutputTokens = Math.floor(contentTokens * 1.8);
	const totalEstimatedTokens = contentTokens + estimatedOutputTokens;
	
	console.log(`=== TOKEN ANALYSIS ===`);
	console.log(`Max tokens: ${maxTokens}`);
	console.log(`Reserve tokens: ${reserveTokens}`);
	console.log(`Available tokens: ${availableTokens}`);
	console.log(`Content tokens: ${contentTokens}`);
	console.log(`Estimated output tokens: ${estimatedOutputTokens}`);
	console.log(`Total estimated tokens: ${totalEstimatedTokens}`);
	console.log(`Fits in single request: ${totalEstimatedTokens <= availableTokens}`);
	
	let translatedContent;
	if (totalEstimatedTokens <= availableTokens) {
		// Content fits in one request - use normal translation with preservation
		console.log(`Content fits in single request: ${contentTokens} tokens`);
		translatedContent = await translateText(processedContent, targetLanguage);
	} else {
		// Content too large for selected model
		console.log(`Content too large: ${totalEstimatedTokens} total tokens > ${availableTokens} limit`);
		
		// Get model info for better error message
		const modelName = getSelectedModel();
		const modelInfo = modelRegistry[modelName] || { modelName };
		
		throw new Error(`Content is too large for the selected model (${modelName}).
		
Content requires ~${Math.ceil(totalEstimatedTokens).toLocaleString()} tokens but model only supports ${maxTokens.toLocaleString()} tokens.

Please try:
• Using a larger model (32K or higher)
• Translating a smaller section of the page
• Using the custom text tab for specific content`);
	}
	
	// Parse the content to add base tag for URL resolution
	const parser = new DOMParser();
	const doc = parser.parseFromString(translatedContent, 'text/html');
	
	// Add base tag for URL resolution
	let baseUrl;
	if (sourceUrl) {
		// Use the source URL as base
		baseUrl = new URL('.', sourceUrl).href; // Get base URL (without filename)
	} else {
		// Use current page URL as base (like current page translation)
		const currentUrl = window.location.href;
		baseUrl = new URL('.', currentUrl).href;
	}
	
	// Remove any existing base tag
	const existingBase = doc.querySelector('base');
	if (existingBase) {
		existingBase.remove();
	}
	
	// Add new base tag to head
	const baseTag = doc.createElement('base');
	baseTag.setAttribute('href', baseUrl);
	doc.head.insertBefore(baseTag, doc.head.firstChild);
	
	console.log('Added base tag with href:', baseUrl);
	console.log(`✅ COMPLETED TRANSLATION ${translationId} - Final length: ${doc.documentElement.outerHTML?.length || 0} chars`);
	
	return doc.documentElement.outerHTML;
}

// Translate current page content
export async function translateCurrentPage(targetLanguage) {
	const pageContent = extractPageContent();
	
	// Use the new translateHTML function for current page translation
	return await translateHTML(pageContent, targetLanguage);
}
