// 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

// TTS Modal functionality
import { initializeChunkFiveFont } from "./ui-themes.js";
import { getUIText } from "./ui-translations.js";
import { handleTTS, downloadAudio, generateTitleForTTS, getTTSMode, setTTSMode } from "./tts.js";
import { VOICES_API_URL } from "./config.js";

// Function to fetch and populate voice dropdown
async function populateVoiceDropdown(voiceSelection) {
	// Check cache first (1 minute expiration)
	const cacheKey = 'uncloseai_voices_cache';
	const cacheTimeKey = 'uncloseai_voices_cache_time';
	const cacheVersionKey = 'uncloseai_voices_cache_version';
	const cacheExpiration = 60000; // 1 minute in milliseconds
	const currentVersion = '4'; // Increment this when voice models change (v4: 42+ built-in cloned voices)

	try {
		const cachedTime = localStorage.getItem(cacheTimeKey);
		const cachedVoices = localStorage.getItem(cacheKey);
		const cachedVersion = localStorage.getItem(cacheVersionKey);

		// Check if cache is valid (not expired AND correct version)
		if (cachedTime && cachedVoices && cachedVersion === currentVersion) {
			const age = Date.now() - parseInt(cachedTime, 10);
			if (age < cacheExpiration) {
				console.log('Using cached voices data (v' + currentVersion + ')');
				const voices = JSON.parse(cachedVoices);
				renderVoiceOptions(voiceSelection, voices);
				return voices;
			}
		} else if (cachedVersion !== currentVersion) {
			console.log('Cache version mismatch - invalidating old cache');
			// Clear old cache
			localStorage.removeItem(cacheKey);
			localStorage.removeItem(cacheTimeKey);
			localStorage.removeItem(cacheVersionKey);
		}
	} catch (error) {
		console.warn('Cache read failed:', error);
	}

	// Fetch fresh data
	try {
		console.log('Fetching voices from API...');
		const response = await fetch(VOICES_API_URL);

		if (!response.ok) {
			throw new Error(`HTTP error! status: ${response.status}`);
		}

		const data = await response.json();

		// Transform API response format to frontend format
		// API returns: {data: [{id: "tts-1-qwen", voices: ["alloy", ...]}]}
		// Frontend expects: [{model: "tts-1-qwen", voice: "alloy"}, ...]
		let voices = [];
		if (data.data && Array.isArray(data.data)) {
			data.data.forEach(modelInfo => {
				if (modelInfo.voices && Array.isArray(modelInfo.voices)) {
					modelInfo.voices.forEach(voice => {
						voices.push({ model: modelInfo.id, voice: voice });
					});
				}
			});
		} else if (data.voices && Array.isArray(data.voices)) {
			// Legacy format support
			voices = data.voices;
		}

		console.log(`Loaded ${voices.length} voices from API`);

		// Cache the response with version
		try {
			localStorage.setItem(cacheKey, JSON.stringify(voices));
			localStorage.setItem(cacheTimeKey, Date.now().toString());
			localStorage.setItem(cacheVersionKey, currentVersion);
		} catch (error) {
			console.warn('Cache write failed:', error);
		}

		renderVoiceOptions(voiceSelection, voices);
		return voices;
	} catch (error) {
		console.error('Failed to fetch voices:', error);
		// Fallback to default voices (20 voices from Qwen3-TTS)
		const defaultVoices = [
			// Standard OpenAI-compatible voices
			{ model: 'tts-1-qwen', voice: 'alloy' },
			{ model: 'tts-1-qwen', voice: 'echo' },
			{ model: 'tts-1-qwen', voice: 'fable' },
			{ model: 'tts-1-qwen', voice: 'onyx' },
			{ model: 'tts-1-qwen', voice: 'nova' },
			{ model: 'tts-1-qwen', voice: 'shimmer' },
			// Extended voices
			{ model: 'tts-1-qwen', voice: 'amber' },
			{ model: 'tts-1-qwen', voice: 'breeze' },
			{ model: 'tts-1-qwen', voice: 'coral' },
			{ model: 'tts-1-qwen', voice: 'dawn' },
			{ model: 'tts-1-qwen', voice: 'ember' },
			{ model: 'tts-1-qwen', voice: 'frost' },
			{ model: 'tts-1-qwen', voice: 'glow' },
			{ model: 'tts-1-qwen', voice: 'haze' },
			{ model: 'tts-1-qwen', voice: 'ivy' },
			{ model: 'tts-1-qwen', voice: 'jade' },
			{ model: 'tts-1-qwen', voice: 'kite' },
			{ model: 'tts-1-qwen', voice: 'lark' },
			{ model: 'tts-1-qwen', voice: 'mist' },
			{ model: 'tts-1-qwen', voice: 'nectar' }
		];
		renderVoiceOptions(voiceSelection, defaultVoices);
		return defaultVoices;
	}
}

// Function to render voice options in the dropdown
function renderVoiceOptions(voiceSelection, voices) {
	voiceSelection.innerHTML = ''; // Clear existing options

	// Get saved voice preference (vault first, fallback to default)
	let savedVoice = 'tts-1:onyx'; // Default
	try {
		if (typeof window !== 'undefined' && window.UncloseVault && window.UncloseVault.isUnlocked()) {
			savedVoice = window.UncloseVault.get('uncloseai_selected_voice', 'tts-1:onyx');
		}
	} catch (error) {
		console.warn('Failed to read voice preference:', error);
	}

	// Group voices by model
	const groupedVoices = {};
	voices.forEach(v => {
		if (!groupedVoices[v.model]) {
			groupedVoices[v.model] = [];
		}
		groupedVoices[v.model].push(v.voice);
	});

	// Create radio buttons grouped by model
	Object.keys(groupedVoices).sort().forEach(model => {
		// Add model label
		const modelLabel = document.createElement('div');
		modelLabel.className = 'tts-model-label';
		modelLabel.textContent = model;
		voiceSelection.appendChild(modelLabel);

		// Add voices for this model
		groupedVoices[model].forEach(voice => {
			const label = document.createElement('label');
			label.className = 'tts-voice-label';
			const radio = document.createElement('input');
			radio.type = 'radio';
			radio.name = 'tts-voice';
			radio.value = `${model}:${voice}`;

			// Check if this is the saved voice
			if (`${model}:${voice}` === savedVoice) {
				radio.checked = true;
			}

			// Save selection on change (through vault)
			radio.addEventListener('change', () => {
				try {
					if (typeof window !== 'undefined' && window.UncloseVault && window.UncloseVault.isUnlocked()) {
						window.UncloseVault.set('uncloseai_selected_voice', radio.value);
					} else {
						console.warn('Vault locked - voice preference not persisted');
					}
				} catch (error) {
					console.warn('Failed to save voice preference:', error);
				}
			});

			label.appendChild(radio);
			label.appendChild(document.createTextNode(voice));
			voiceSelection.appendChild(label);
		});
	});

	// If no voice is checked, check the first one
	if (!voiceSelection.querySelector('input[type="radio"]:checked')) {
		const firstRadio = voiceSelection.querySelector('input[type="radio"]');
		if (firstRadio) {
			firstRadio.checked = true;
		}
	}
}

export function openTTSModal() {
	// Load appropriate CSS based on whether we're using PicoCSS or built-in styles
	// Detect if PicoCSS is actually present on the page
	const hasPicoCSS = document.querySelector('link[href*="pico"]') !== null;
	
	// Load appropriate CSS based on actual PicoCSS presence
	const cssFile = hasPicoCSS ? 'uncloseai-modal-pico.css' : 'uncloseai-modal-builtin.css';
	
	if (!document.querySelector(`link[href*="${cssFile}"]`)) {
		const link = document.createElement('link');
		link.rel = 'stylesheet';
		link.href = `https://uncloseai.com/src/${cssFile}`;
		document.head.appendChild(link);
	}

	// Ensure ChunkFive font is loaded
	initializeChunkFiveFont();

	// Check if Hermes modal is open to set appropriate z-index
	const hermesModal = document.getElementById("uncloseai-embedded-modal");
	const zIndex = hermesModal ? "2001" : "1001";

	const modal = document.createElement("dialog");
	modal.id = "tts-modal";
	modal.style.zIndex = zIndex;

	document.body.appendChild(modal);

	const article = document.createElement("article");
	article.className = "tts-modal-article";

	const header = document.createElement("header");
	header.className = "tts-modal-header";

	const h1 = document.createElement("h1");
	h1.innerHTML = `<span style="font-family: 'ChunkFiveRegular', monospace;">uncloseai.</span> ${getUIText("ttsModalTitle")}`;
	header.appendChild(h1);

	const closeButton = document.createElement("button");
	closeButton.textContent = "X";
	closeButton.className = "close-btn";
	closeButton.onclick = () => {
		modal.close();
		document.body.removeChild(modal);
	};
	header.appendChild(closeButton);

	const textArea = document.createElement("textarea");
	textArea.className = "tts-modal-textarea";
	textArea.placeholder = getUIText("ttsPlaceholder");

	// Voice selection - dynamically populated from API
	const voiceSelection = document.createElement("div");
	voiceSelection.className = "tts-voice-container";

	// Populate voice options dynamically
	populateVoiceDropdown(voiceSelection);

	// Speed control
	const speedControl = document.createElement("div");
	speedControl.className = "tts-speed-control";

	const speedLabel = document.createElement("label");
	const speedValue = document.createElement("span");
	speedValue.textContent = "0.9";

	speedLabel.textContent = `${getUIText("speedLabel")} ${speedValue.textContent}`;
	speedLabel.setAttribute("data-i18n", "speedLabel");
	const speedSlider = document.createElement("input");
	speedSlider.type = "range";
	speedSlider.min = "0.25";
	speedSlider.max = "2.0";
	speedSlider.step = "0.05";
	speedSlider.value = "0.9";
	speedSlider.oninput = () => {
		speedValue.textContent = speedSlider.value;
		speedLabel.textContent = `${getUIText("speedLabel")} ${speedSlider.value}`;
	};

	speedControl.appendChild(speedLabel);
	speedControl.appendChild(speedSlider);

	// Streaming mode toggle
	const modeControl = document.createElement("div");
	modeControl.className = "tts-mode-control";
	modeControl.style.cssText = "display:flex;align-items:center;gap:8px;margin:8px 0;font-size:0.9em;";

	const modeLabel = document.createElement("span");
	modeLabel.textContent = "Playback:";
	modeLabel.style.fontWeight = "bold";

	const modeBuffered = document.createElement("label");
	modeBuffered.style.cssText = "display:flex;align-items:center;gap:4px;cursor:pointer;";
	const radioBuffered = document.createElement("input");
	radioBuffered.type = "radio";
	radioBuffered.name = "tts-mode";
	radioBuffered.value = "buffered";
	radioBuffered.checked = getTTSMode() === "buffered";
	modeBuffered.appendChild(radioBuffered);
	modeBuffered.appendChild(document.createTextNode("Buffered"));

	const modeStreaming = document.createElement("label");
	modeStreaming.style.cssText = "display:flex;align-items:center;gap:4px;cursor:pointer;";
	const radioStreaming = document.createElement("input");
	radioStreaming.type = "radio";
	radioStreaming.name = "tts-mode";
	radioStreaming.value = "streaming";
	radioStreaming.checked = getTTSMode() === "streaming";
	modeStreaming.appendChild(radioStreaming);
	modeStreaming.appendChild(document.createTextNode("Streaming"));

	radioBuffered.addEventListener("change", () => { if (radioBuffered.checked) setTTSMode("buffered"); });
	radioStreaming.addEventListener("change", () => { if (radioStreaming.checked) setTTSMode("streaming"); });

	modeControl.appendChild(modeLabel);
	modeControl.appendChild(modeBuffered);
	modeControl.appendChild(modeStreaming);

	const playButton = document.createElement("button");
	playButton.textContent = getUIText("playText");
	playButton.className = "tts-play-btn";

	const resultDiv = document.createElement("div");
	resultDiv.id = "tts-result";
	resultDiv.className = "tts-result";

	// Content container with padding
	const contentContainer = document.createElement("div");
	contentContainer.className = "tts-modal-content";
	contentContainer.appendChild(textArea);
	contentContainer.appendChild(voiceSelection);
	contentContainer.appendChild(speedControl);
	contentContainer.appendChild(modeControl);
	contentContainer.appendChild(playButton);
	contentContainer.appendChild(resultDiv);

	article.appendChild(header);
	article.appendChild(contentContainer);
	modal.appendChild(article);

	modal.showModal();

	// Play button handler
	playButton.onclick = async () => {
		const text = textArea.value.trim();
		if (!text) {
			alert(getUIText("pleaseEnterText"));
			return;
		}

		const selectedValue = document.querySelector('input[name="tts-voice"]:checked').value;
		const speed = parseFloat(speedSlider.value);

		// Parse model:voice format (e.g., "tts-1-qwen:alloy" or legacy "alloy")
		let model = 'tts-1-qwen';
		let voice = selectedValue;

		if (selectedValue.includes(':')) {
			const parts = selectedValue.split(':');
			model = parts[0];
			voice = parts[1];
		}

		try {
			const currentMode = getTTSMode();
			playButton.textContent = currentMode === "streaming" ? "Streaming..." : getUIText("processingText");
			playButton.disabled = true;

			const result = await handleTTS(text, voice, speed, model);
			if (!result.audio) return;

			const isStreaming = result.streamed === true;
			console.log(`TTS result: streamed=${isStreaming}`);

			resultDiv.classList.add("show");
			resultDiv.innerHTML = "";

			// Use the audio element from the result directly (critical for streaming -
			// it's already playing via MediaSource, creating a new Audio would lose the stream)
			const audio = result.audio;

			// For buffered mode, the audio isn't playing yet, so set it up
			if (!isStreaming) {
				audio.preload = "auto";
			}

			// Create custom control container
			const controlsContainer = document.createElement("div");
			controlsContainer.className = "tts-audio-controls";

			// Create play/pause button
			const playPauseBtn = document.createElement("button");
			playPauseBtn.textContent = isStreaming ? "⏸️ Streaming..." : "⏸️ Pause";

			// Create progress bar
			const progressContainer = document.createElement("div");
			progressContainer.className = "tts-progress-container";

			const progressBar = document.createElement("div");
			progressBar.className = "tts-progress-bar";
			progressContainer.appendChild(progressBar);

			// Create time display
			const timeDisplay = document.createElement("span");
			timeDisplay.textContent = "0:00 / 0:00";
			timeDisplay.className = "tts-time-display";

			// Create download button (disabled until stream completes)
			const downloadBtn = document.createElement("button");
			downloadBtn.className = "download-btn";
			let downloadBlobUrl = result.blobUrl;

			if (isStreaming && !result.blob) {
				downloadBtn.textContent = "💾 Loading...";
				downloadBtn.disabled = true;
			} else {
				downloadBtn.textContent = "💾 Download";
			}

			downloadBtn.onclick = async () => {
				if (!downloadBlobUrl) return;
				try {
					const filename = await generateTitleForTTS(text);
					downloadAudio(downloadBlobUrl, `${filename}.mp3`);
				} catch (error) {
					console.error("Error generating filename:", error);
					downloadAudio(downloadBlobUrl, "tts-audio.mp3");
				}
			};

			// Assemble controls
			controlsContainer.appendChild(playPauseBtn);
			controlsContainer.appendChild(progressContainer);
			controlsContainer.appendChild(timeDisplay);
			controlsContainer.appendChild(downloadBtn);
			resultDiv.appendChild(controlsContainer);

			// Track state
			let isPlaying = !audio.paused;
			let streamDone = !isStreaming; // buffered mode is already "done"

			const formatTime = (seconds) => {
				if (!isFinite(seconds)) return "?:??";
				const mins = Math.floor(seconds / 60);
				const secs = Math.floor(seconds % 60);
				return `${mins}:${secs.toString().padStart(2, '0')}`;
			};

			const updateProgress = () => {
				if (audio.duration && isFinite(audio.duration)) {
					const progress = (audio.currentTime / audio.duration) * 100;
					progressBar.style.width = `${progress}%`;
					timeDisplay.textContent = `${formatTime(audio.currentTime)} / ${formatTime(audio.duration)}`;
				} else if (isStreaming) {
					// During streaming, duration may not be known yet
					timeDisplay.textContent = `${formatTime(audio.currentTime)} / streaming...`;
				}
			};

			const updatePlayPauseBtn = () => {
				if (isPlaying) {
					playPauseBtn.textContent = streamDone ? "⏸️ Pause" : "⏸️ Streaming...";
				} else {
					playPauseBtn.textContent = "▶️ Play";
				}
			};

			playPauseBtn.onclick = () => {
				if (isPlaying) {
					audio.pause();
					isPlaying = false;
				} else {
					audio.play();
					isPlaying = true;
				}
				updatePlayPauseBtn();
			};

			// Progress bar click handler
			progressContainer.onclick = (e) => {
				if (audio.duration && isFinite(audio.duration)) {
					const rect = progressContainer.getBoundingClientRect();
					const clickX = e.clientX - rect.left;
					const clickRatio = clickX / rect.width;
					audio.currentTime = clickRatio * audio.duration;
				}
			};

			// Audio event listeners
			audio.addEventListener('loadedmetadata', () => {
				if (isFinite(audio.duration)) {
					timeDisplay.textContent = `0:00 / ${formatTime(audio.duration)}`;
				}
			});

			audio.addEventListener('timeupdate', updateProgress);

			audio.addEventListener('ended', () => {
				playPauseBtn.textContent = "▶️ Replay";
				isPlaying = false;
				progressBar.style.width = '0%';
				audio.currentTime = 0;
			});

			audio.addEventListener('error', (e) => {
				console.error('Audio playback error:', e);
				playPauseBtn.textContent = "❌ Error";
				playPauseBtn.disabled = true;
				const errorMsg = document.createElement('div');
				errorMsg.className = "tts-error-message";
				errorMsg.textContent = 'Audio failed to load. Please try again or check your connection.';
				controlsContainer.appendChild(errorMsg);
			});

			audio.addEventListener('abort', (e) => {
				console.warn('Audio loading was aborted:', e);
				if (isPlaying) {
					playPauseBtn.textContent = "▶️ Retry";
					isPlaying = false;
				}
			});

			audio.addEventListener('stalled', (e) => {
				console.warn('Audio loading stalled:', e);
				if (!streamDone) {
					playPauseBtn.textContent = "⏳ Buffering...";
				}
			});

			// Wait for stream to complete in background (doesn't block UI)
			result.done.then(({ blob, blobUrl }) => {
				streamDone = true;
				downloadBlobUrl = blobUrl;
				downloadBtn.textContent = "💾 Download";
				downloadBtn.disabled = false;
				console.log("Stream done, download ready");
				// Update button text now that stream is complete
				if (isPlaying) {
					playPauseBtn.textContent = "⏸️ Pause";
				}
			}).catch((err) => {
				console.error("Stream failed:", err);
				downloadBtn.textContent = "💾 Failed";
			});

			// For buffered mode, auto-play; for streaming, it's already playing
			if (!isStreaming) {
				audio.play().then(() => {
					isPlaying = true;
					updatePlayPauseBtn();
				}).catch((e) => {
					console.warn('Auto-play failed:', e.name, e.message);
					isPlaying = false;
					playPauseBtn.textContent = "▶️ Play";
					if (e.name === 'NotSupportedError') {
						playPauseBtn.textContent = "❌ Unsupported";
						playPauseBtn.disabled = true;
					}
				});
			} else {
				// Streaming mode - audio is already playing
				updatePlayPauseBtn();
			}

			// Re-enable the generate button now that controls are shown
			playButton.textContent = getUIText("playText");
			playButton.disabled = false;
		} catch (error) {
			alert(getUIText("ttsFailed", { error: error.message }));
			playButton.textContent = getUIText("playText");
			playButton.disabled = false;
		}
	};

	// Focus the textarea
	setTimeout(() => {
		textArea.focus();
	}, 100);

	// Expose globally
	window.closeTTSModal = () => {
		modal.close();
		document.body.removeChild(modal);
	};

	// Clean up when closed
	modal.addEventListener("close", () => {
		if (modal.parentNode) {
			document.body.removeChild(modal);
		}
	});
}