<!-- #region TranslationPreview [C:3] [TYPE Component] [SEMANTICS translate, preview, multi-language, approve, cost] -->
<!-- @BRIEF Component component: lib/components/translate/TranslationPreview.svelte -->
<!-- @RELATION DEPENDS_ON -> [TranslateApi] -->
<!-- @RELATION DEPENDS_ON -> [EXT:frontend:api_module] -->
<!-- @UX_STATE idle -> Initial state, no preview loaded -->
<!-- @UX_STATE loading -> Preview API call in progress -->
<!-- @UX_STATE preview_loaded -> Preview data displayed with dynamic language columns -->
<!-- @UX_STATE preview_error -> Preview API call failed -->
<!-- @UX_STATE accepted -> Preview session accepted -->
<!-- @UX_STATE stale_config -> Configuration changed since preview was generated -->
<!-- @UX_FEEDBACK spinner during preview; visual distinction for LLM vs edited translations; cost warning for large previews. -->
<!-- @UX_RECOVERY retry preview; re-fetch with updated config. -->
<script lang="ts">
	import { t } from '$lib/i18n/index.svelte.js';
	import { addToast } from '$lib/toasts.svelte.js';
	import {
		fetchPreview,
		approveRow,
		editRow,
		rejectRow,
		acceptPreview,
		fetchPreviewRecords
	} from '$lib/api/translate.js';

	/** @type {{ jobId: string, environmentId?: string, onAccept?: () => void }} */
	let { jobId, environmentId = '', onAccept = () => {} } = $props();

	/** @type {'idle'|'loading'|'preview_loaded'|'preview_error'|'accepted'|'stale_config'} */
	let uxState = $state('idle');
	let session = $state(null);
	let records = $state([]);
	let targetLanguages = $state([]);
	let costEstimate = $state(null);
	let errorMessage = $state('');
	let sampleSize = $state(10);
	let isAccepting = $state(false);

	// Per-language editing state: { `${rowId}_${langCode}`: true }
	let editingCells = $state({});
	let editValues = $state({});

	// Derived
	let approvedCount = $derived(records.filter(r => r.status === 'APPROVED').length);
	let rejectedCount = $derived(records.filter(r => r.status === 'REJECTED').length);
	let pendingCount = $derived(records.filter(r => r.status === 'PENDING').length);
	let isAllApproved = $derived(pendingCount === 0 && records.length > 0);

	function getRowStatusLabel(status) {
		const labels = {
			APPROVED: $t.translate?.preview?.status_approved || 'Approved',
			REJECTED: $t.translate?.preview?.status_rejected || 'Rejected',
			PENDING: $t.translate?.preview?.status_pending || 'Pending',
		};
		return labels[status] || status;
	}

	/** Get language status for a record and language code */
	function getLangStatus(record, langCode) {
		const lang = (record.languages || []).find(l => l.language_code === langCode);
		return lang ? lang.status : 'pending';
	}

	/** Get translated value for a record and language code */
	function getLangValue(record, langCode) {
		const lang = (record.languages || []).find(l => l.language_code === langCode);
		return lang ? (lang.user_edit || lang.final_value || lang.translated_value || '') : '';
	}

	/** Get detected language for a record */
	function getDetectedLang(record) {
		if (record.source_language_detected && record.source_language_detected !== 'und') {
			return record.source_language_detected;
		}
		const langs = record.languages || [];
		for (const l of langs) {
			if (l.source_language_detected && l.source_language_detected !== 'und') {
				return l.source_language_detected;
			}
		}
		return 'und';
	}

	/** @returns {Promise<void>} */
	async function handlePreview() {
		uxState = 'loading';
		errorMessage = '';
		try {
			const result = await fetchPreview(jobId, sampleSize, environmentId);
			session = result;
			records = (result.records || []).map(r => ({ ...r, _isEdited: false }));
			targetLanguages = result.target_languages || [];
			costEstimate = result.cost_estimate || null;
			uxState = 'preview_loaded';
			addToast($t.translate?.preview?.preview_loaded?.replace('{count}', records.length), 'success');
		} catch (err) {
			errorMessage = err?.message || $t.translate?.preview?.preview_failed;
			uxState = 'preview_error';
			addToast(errorMessage, 'error');
		}
	}

	/** @param {string} rowId */
	async function handleApprove(rowId) {
		try {
			const updated = await approveRow(jobId, rowId);
			records = records.map(r => r.id === rowId ? {
				...r,
				status: updated.status || 'APPROVED',
				target_sql: updated.target_sql || r.target_sql,
				languages: updated.languages || r.languages,
				_isEdited: false
			} : r);
		} catch (err) {
			addToast(err?.message || $t.translate?.preview?.approve_failed, 'error');
		}
	}

	/** @param {string} rowId */
	async function handleReject(rowId) {
		try {
			const updated = await rejectRow(jobId, rowId);
			records = records.map(r => r.id === rowId ? {
				...r,
				status: updated.status || 'REJECTED',
				languages: updated.languages || r.languages,
				_isEdited: false
			} : r);
		} catch (err) {
			addToast(err?.message || $t.translate?.preview?.reject_failed, 'error');
		}
	}

	/** @param {string} rowId @param {string} langCode */
	async function handleApproveLanguage(rowId, langCode) {
		try {
			const updated = await approveRow(jobId, rowId, langCode);
			records = records.map(r => r.id === rowId ? {
				...r,
				status: updated.status || r.status,
				target_sql: updated.target_sql || r.target_sql,
				languages: updated.languages || r.languages,
				_isEdited: false
			} : r);
		} catch (err) {
			addToast(err?.message || $t.translate?.preview?.approve_failed, 'error');
		}
	}

	/** @param {string} rowId @param {string} langCode */
	async function handleRejectLanguage(rowId, langCode) {
		try {
			const updated = await rejectRow(jobId, rowId, langCode);
			records = records.map(r => r.id === rowId ? {
				...r,
				status: updated.status || r.status,
				languages: updated.languages || r.languages,
				_isEdited: false
			} : r);
		} catch (err) {
			addToast(err?.message || $t.translate?.preview?.reject_failed, 'error');
		}
	}

	/** @param {string} rowId @param {string} langCode */
	function startLangEdit(rowId, langCode) {
		const key = `${rowId}_${langCode}`;
		editingCells = { ...editingCells, [key]: true };
		editValues = { ...editValues, [key]: getLangValue(records.find(r => r.id === rowId), langCode) };
	}

	/** @param {string} rowId @param {string} langCode */
	async function saveLangEdit(rowId, langCode) {
		const key = `${rowId}_${langCode}`;
		const value = (editValues[key] || '').trim();
		if (!value) return;
		try {
			const updated = await editRow(jobId, rowId, value, langCode);
			records = records.map(r =>
				r.id === rowId
					? {
						...r,
						status: updated.status || r.status,
						target_sql: updated.target_sql || r.target_sql,
						languages: updated.languages || r.languages,
						_isEdited: true
					}
					: r
			);
			const newEditing = { ...editingCells };
			delete newEditing[key];
			editingCells = newEditing;
			const newValues = { ...editValues };
			delete newValues[key];
			editValues = newValues;
			addToast($t.translate?.preview?.row_updated, 'success');
		} catch (err) {
			addToast(err?.message || $t.translate?.preview?.edit_failed, 'error');
		}
	}

	/** @param {string} rowId @param {string} langCode */
	function cancelLangEdit(rowId, langCode) {
		const key = `${rowId}_${langCode}`;
		const newEditing = { ...editingCells };
		delete newEditing[key];
		editingCells = newEditing;
		const newValues = { ...editValues };
		delete newValues[key];
		editValues = newValues;
	}

	async function handleBulkApprove() {
		for (const row of records) {
			if (row.status === 'PENDING') {
				await handleApprove(row.id);
			}
		}
	}

	async function handleAccept() {
		isAccepting = true;
		try {
			const result = await acceptPreview(jobId);
			uxState = 'accepted';
			onAccept();
			addToast($t.translate?.preview?.accepted_body, 'success');
		} catch (err) {
			addToast(err?.message || $t.translate?.preview?.accept_failed, 'error');
		} finally {
			isAccepting = false;
		}
	}
</script>

<div class="translation-preview">
	<!-- Header -->
	<div class="flex items-center justify-between mb-4">
		<h3 class="text-lg font-semibold text-gray-900">{$t.translate?.preview?.title}</h3>
		{#if uxState === 'preview_loaded'}
			<span class="px-2.5 py-0.5 rounded-full text-xs font-medium {session?.status === 'APPLIED' ? 'bg-green-100 text-green-700' : 'bg-blue-100 text-blue-700'}">
				{session?.status === 'APPLIED' ? $t.translate?.preview?.accepted_badge : $t.translate?.preview?.active_badge}
			</span>
		{/if}
	</div>

	<!-- Idle State -->
	{#if uxState === 'idle'}
		<div class="bg-gray-50 border border-gray-200 rounded-lg p-6 text-center">
			<p class="text-sm text-gray-500 mb-4">
				{$t.translate?.preview?.title} &mdash; {$t.translate?.preview?.sample_size} {sampleSize}
			</p>
			<div class="flex items-center justify-center gap-3 mb-4">
				<label class="text-sm text-gray-600">{$t.translate?.preview?.sample_size}</label>
				<input
					type="range"
					bind:value={sampleSize}
					min="1"
					max="100"
					class="w-40 accent-blue-600"
				/>
				<span class="text-sm font-mono text-gray-700 w-8 text-left">{sampleSize}</span>
			</div>
			{#if sampleSize > 30}
				<div class="flex items-center justify-center gap-2 mb-4 text-amber-700 bg-amber-50 border border-amber-200 rounded-lg px-4 py-2 mx-auto max-w-md">
					<svg class="w-4 h-4 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
						<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
					</svg>
					<span class="text-xs font-medium">{($t.translate?.preview?.cost_warning) || 'Large sample size may incur higher costs'}</span>
				</div>
			{/if}
			<button
				onclick={handlePreview}
				class="px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary-hover transition-colors text-sm"
			>
				{$t.translate?.preview?.run_preview}
			</button>
		</div>

	<!-- Loading State -->
	{:else if uxState === 'loading'}
		<div class="bg-white border border-gray-200 rounded-lg p-6">
			<div class="flex items-center gap-3 mb-4">
				<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-600" />
				<span class="text-sm text-gray-500">{$t.translate?.preview?.running_preview}</span>
			</div>
			<div class="space-y-3">
				{#each Array(3) as _}
					<div class="h-12 bg-gray-100 rounded animate-pulse" />
				{/each}
			</div>
		</div>

	<!-- Error State -->
	{:else if uxState === 'preview_error'}
		<div class="bg-red-50 border border-red-200 rounded-lg p-4">
			<p class="text-sm text-red-700 mb-3">{errorMessage}</p>
			<div class="flex items-center gap-3">
				<button
					onclick={handlePreview}
					class="px-4 py-1.5 bg-destructive text-white rounded-lg hover:bg-destructive-hover transition-colors text-sm"
				>
					{$t.translate?.preview?.retry_preview}
				</button>
				<label class="text-sm text-gray-500 flex items-center gap-2">
					{$t.translate?.preview?.sample_size}
					<input
						type="range"
						bind:value={sampleSize}
						min="1"
						max="100"
						class="w-24 accent-blue-600"
					/>
					<span class="text-xs font-mono text-gray-500 w-6">{sampleSize}</span>
				</label>
			</div>
		</div>

	<!-- Preview Loaded State -->
	{:else if uxState === 'preview_loaded'}
		<!-- Cost Estimate Card -->
		{#if costEstimate}
			<div class="bg-gradient-to-r from-blue-50 to-indigo-50 border border-primary-ring rounded-lg p-4 mb-4">
				<h4 class="text-sm font-medium text-blue-800 mb-2">{$t.translate?.preview?.cost_estimate}</h4>
				<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-sm">
					<div>
						<span class="text-primary">{$t.translate?.preview?.sample_rows}</span>
						<span class="ml-1 font-semibold">{costEstimate.sample_size}</span>
					</div>
					<div>
						<span class="text-primary">{$t.translate?.preview?.sample_tokens}</span>
						<span class="ml-1 font-semibold">{costEstimate.sample_total_tokens?.toLocaleString()}</span>
					</div>
					<div>
						<span class="text-primary">{$t.translate?.preview?.sample_cost}</span>
						<span class="ml-1 font-semibold">${costEstimate.sample_cost?.toFixed(6)}</span>
					</div>
					<div>
						<span class="text-primary">{$t.translate?.preview?.est_total_cost}</span>
						<span class="ml-1 font-semibold">${costEstimate.estimated_cost?.toFixed(6)}</span>
					</div>
				</div>
				<!-- Language count -->
				<p class="text-xs text-blue-500 mt-1">
					{$t.translate?.preview?.across_languages?.replace('{count}', costEstimate.num_languages || targetLanguages.length || 1)}
				</p>
				<!-- Cost warning for large previews -->
				{#if costEstimate.warning}
					<div class="mt-2 flex items-center gap-2 text-amber-700 bg-amber-50 border border-amber-200 rounded px-3 py-2">
						<svg class="w-4 h-4 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
							<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
						</svg>
						<span class="text-xs font-medium">{costEstimate.warning}</span>
					</div>
				{/if}
			</div>
		{/if}

		<!-- Stats Bar -->
		<div class="flex items-center justify-between mb-3 text-sm">
			<div class="flex gap-4">
				<span class="text-gray-500">{$t.translate?.preview?.rows_count?.replace('{count}', records.length)}</span>
				<span class="text-green-600">{$t.translate?.preview?.approved_count?.replace('{count}', approvedCount)}</span>
				<span class="text-red-600">{$t.translate?.preview?.rejected_count?.replace('{count}', rejectedCount)}</span>
				{#if pendingCount > 0}
					<span class="text-yellow-600">{$t.translate?.preview?.pending_count?.replace('{count}', pendingCount)}</span>
				{/if}
				{#if targetLanguages.length > 0}
					<span class="text-primary">{$t.translate?.preview?.languages_count?.replace('{count}', targetLanguages.length)}</span>
				{/if}
			</div>
			<div class="flex gap-2">
				{#if pendingCount > 0}
					<button
						onclick={handleBulkApprove}
						class="px-3 py-1 text-xs bg-green-100 text-green-700 rounded hover:bg-green-200 transition-colors"
					>
						{$t.translate?.preview?.approve_all}
					</button>
				{/if}
				<button
					onclick={handlePreview}
					class="px-3 py-1 text-xs border border-gray-300 text-gray-600 rounded hover:bg-gray-50 transition-colors"
				>
					{$t.translate?.preview?.re_run}
				</button>
			</div>
		</div>

		<!-- Dynamic Multi-Language Table -->
		<div class="overflow-x-auto border border-gray-200 rounded-lg">
			<table class="w-full text-sm">
				<thead class="bg-gray-50">
					<tr>
						<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase w-12">#</th>
						<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">{$t.translate?.preview?.table_source}</th>
						<th class="px-3 py-2 text-center text-xs font-medium text-gray-500 uppercase w-28">{$t.translate?.preview?.detected_language}</th>
						{#each targetLanguages as lang}
							<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase min-w-[250px]">
								<div class="flex items-center gap-1">
									<span class="font-mono text-primary">{lang}</span>
								</div>
							</th>
						{/each}
						<th class="px-3 py-2 text-center text-xs font-medium text-gray-500 uppercase w-24">{$t.translate?.preview?.table_status}</th>
					</tr>
				</thead>
				<tbody class="divide-y divide-gray-200">
					{#each records as row, idx}
						<tr class="{row.status === 'REJECTED' ? 'bg-red-50' : row._isEdited ? 'bg-yellow-50' : idx % 2 === 0 ? 'bg-white' : 'bg-gray-50/50'}">
							<td class="px-3 py-2 text-xs text-gray-400 align-top">{idx + 1}</td>
							<td class="px-3 py-2 align-top min-w-[200px]">
								<div class="max-h-32 overflow-y-auto">
									<code class="text-xs text-gray-800 whitespace-pre-wrap break-all">{row.source_sql || $t.translate?.preview?.empty_placeholder}</code>
								</div>
							</td>
							<td class="px-3 py-2 text-center align-top">
							<span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded-full font-mono
								{getDetectedLang(row) !== 'und' ? 'bg-purple-100 text-purple-700' : 'bg-amber-100 text-amber-700'}">
								{getDetectedLang(row)}
								{#if getDetectedLang(row) === 'und'}
									<span class="text-[10px] font-bold" title="Undetermined source language">⚠</span>
								{/if}
							</span>
							</td>
							{#each targetLanguages as lang}
								{@const langKey = `${row.id}_${lang}`}
								{@const langVal = getLangValue(row, lang)}
								{@const langStatus = getLangStatus(row, lang)}
								{@const isEditing = editingCells[langKey]}
								<td class="px-3 py-2 align-top">
									
									{#if isEditing}
										<textarea
											bind:value={editValues[langKey]}
											class="w-full px-2 py-1 border border-primary-ring rounded text-xs focus-visible:ring-2 focus-visible:ring-primary-ring min-h-[50px]"
										/>
										<div class="flex gap-1 mt-1">
											<button
												onclick={() => saveLangEdit(row.id, lang)}
												class="px-2 py-0.5 text-xs bg-primary text-white rounded hover:bg-primary-hover"
											>
												{$t.translate?.preview?.save_edit}
											</button>
											<button
												onclick={() => cancelLangEdit(row.id, lang)}
												class="px-2 py-0.5 text-xs border border-gray-300 text-gray-600 rounded hover:bg-gray-50"
											>
												{$t.translate?.preview?.cancel_edit}
											</button>
										</div>
									{:else}
										<div class="max-h-32 overflow-y-auto">
											<code class="text-xs whitespace-pre-wrap break-all {langStatus === 'edited' ? 'text-yellow-800' : langStatus === 'approved' ? 'text-green-700' : 'text-gray-700'}">
												{langVal || $t.translate?.preview?.pending_placeholder}
											</code>
										</div>
										<div class="flex gap-1 mt-1 flex-wrap">
											{#if langStatus !== 'approved' && langStatus !== 'edited'}
												<button
													onclick={() => handleApproveLanguage(row.id, lang)}
													class="px-1.5 py-0.5 text-xs bg-green-100 text-green-700 rounded hover:bg-green-200 transition-colors"
													title={$t.translate?.preview?.approve}
												>
													{$t.translate?.preview?.approve}
												</button>
											{/if}
											<button
												onclick={() => startLangEdit(row.id, lang)}
												class="px-1.5 py-0.5 text-xs bg-blue-100 text-blue-700 rounded hover:bg-blue-200 transition-colors"
												title={$t.translate?.preview?.edit}
											>
												{$t.translate?.preview?.edit}
											</button>
											{#if langStatus !== 'rejected'}
												<button
													onclick={() => handleRejectLanguage(row.id, lang)}
													class="px-1.5 py-0.5 text-xs bg-red-100 text-red-700 rounded hover:bg-red-200 transition-colors"
													title={$t.translate?.preview?.reject}
												>
													{$t.translate?.preview?.reject}
												</button>
											{/if}
										</div>
									{/if}
								</td>
							{/each}
							<td class="px-3 py-2 text-center align-top">
								<div class="flex flex-col items-center gap-1">
									<span class="inline-flex px-2 py-0.5 text-xs rounded-full font-medium
										{row.status === 'APPROVED' ? 'bg-green-100 text-green-700' : ''}
										{row.status === 'REJECTED' ? 'bg-red-100 text-red-700' : ''}
										{row.status === 'PENDING' ? 'bg-yellow-100 text-yellow-700' : ''}
									">
										{getRowStatusLabel(row.status)}
									</span>
									<div class="flex gap-1">
										{#if row.status !== 'APPROVED'}
											<button
												onclick={() => handleApprove(row.id)}
												class="px-1.5 py-0.5 text-xs bg-green-100 text-green-700 rounded hover:bg-green-200 transition-colors"
												title={$t.translate?.preview?.approve_all}
											>
												{$t.translate?.preview?.approve}
											</button>
										{/if}
										{#if row.status !== 'REJECTED'}
											<button
												onclick={() => handleReject(row.id)}
												class="px-1.5 py-0.5 text-xs bg-red-100 text-red-700 rounded hover:bg-red-200 transition-colors"
												title={$t.translate?.preview?.reject}
											>
												{$t.translate?.preview?.reject}
											</button>
										{/if}
									</div>
								</div>
							</td>
						</tr>
					{/each}
				</tbody>
			</table>
		</div>

		<!-- Accept Gate -->
		<div class="mt-6 flex items-center justify-between p-4 bg-gray-50 border border-gray-200 rounded-lg">
			<div>
				<p class="text-sm font-medium text-gray-900">{$t.translate?.preview?.quality_gate}</p>
				<p class="text-xs text-gray-500">
					{isAllApproved ? $t.translate?.preview?.quality_gate_ready : $t.translate?.preview?.quality_gate_pending}
				</p>
			</div>
			<button
				onclick={handleAccept}
				disabled={!isAllApproved || isAccepting}
				class="px-6 py-2 rounded-lg text-sm font-medium transition-colors
					{isAllApproved && !isAccepting
						? 'bg-green-600 text-white hover:bg-green-700'
						: 'bg-gray-200 text-gray-400 cursor-not-allowed'}"
			>
				{isAccepting ? $t.translate?.preview?.accepting : $t.translate?.preview?.accept_preview}
			</button>
		</div>

	<!-- Accepted State -->
	{:else if uxState === 'accepted'}
		<div class="bg-green-50 border border-green-200 rounded-lg p-6 text-center">
			<div class="text-3xl mb-2">&#10003;</div>
			<h4 class="text-lg font-semibold text-green-800 mb-1">{$t.translate?.preview?.accepted_title}</h4>
			<p class="text-sm text-green-600">
				{$t.translate?.preview?.accepted_body}
			</p>
			<button
				onclick={handlePreview}
				class="mt-4 px-4 py-1.5 text-sm border border-green-300 text-green-700 rounded-lg hover:bg-green-100 transition-colors"
			>
				{$t.translate?.preview?.run_preview_again}
			</button>
		</div>
	{/if}
</div>
<!-- #endregion TranslationPreview -->
