<!-- #region TranslationRunProgress [C:3] [TYPE Component] [SEMANTICS translate, progress, websocket, batch, status] -->
<!-- @ingroup Translate -->
<!-- @BRIEF Component component: lib/components/translate/TranslationRunProgress.svelte -->
<!-- @RELATION DEPENDS_ON -> [TranslateApi] -->
<!-- @RELATION DEPENDS_ON -> [EXT:frontend:api_module] -->
<!-- @UX_STATE idle -> Initial state, no run loaded -->
<!-- @UX_STATE running -> Translation phase in progress -->
<!-- @UX_STATE inserting -> Insert phase in progress -->
<!-- @UX_STATE completed -> All phases completed successfully -->
<!-- @UX_STATE partial -> Translation completed with some failures -->
<!-- @UX_STATE failed -> Translation phase failed -->
<!-- @UX_STATE insert_failed -> Insert phase failed -->
<!-- @UX_STATE cancelled -> Run was cancelled -->
<!-- @UX_FEEDBACK progress bar with percentage; batch counter; per-phase counts; cancel button during running/inserting. -->
<!-- @UX_RECOVERY retry on failure states. -->
<!-- @INVARIANT completionDelivered MUST be plain let, NOT $state.
  The $effect reads isRunning and status (both $derived) and writes completionDelivered.
  A $state variable creates reactive tracking on both read and write — the effect
  re-runs on every write, creating an infinite loop. Plain let breaks the cycle. -->
<!-- @INVARIANT storeState MUST use $effect+subscribe pattern, NOT fromStore+$derived.
  fromStore + multiple $derived(runState.current.xxx) creates render_effect instances
  via createSubscriber on every $derived re-evaluation. Over time these accumulate
  and trigger an infinite Svelte reactive flush loop after ~300 polls. -->
<!-- @REJECTED $state(completionDelivered) for completion guard — the $effect at line 45
  reads `completionDelivered` in the guard check and writes it via
  `completionDelivered = true`. $state triggers reactive tracking on both sides →
  effect re-runs → writes true again → re-runs → infinite loop.
  @RATIONALE plain let is a JavaScript variable, not reactive — no tracking on read
  or write. The effect fires once when status becomes terminal, sets the flag, and
  never re-runs because the flag write has no reactive side effects. -->
<!-- @REJECTED fromStore + $derived(runState.current.xxx) for store subscription —
  creates render_effect instances via createSubscriber on every $derived re-evaluation.
  Over time these accumulate and trigger an infinite Svelte reactive flush loop.
  @RATIONALE $effect(() => store.subscribe(s => state = s)) creates exactly ONE
  subscription per component lifecycle — no accumulating render_effects. -->
<script lang="ts">
	import { getT } from '$lib/i18n/index.svelte.js';
	import { addToast } from '$lib/toasts.svelte.js';
	import { cancelRun, downloadFailedCsv } from '$lib/api/translate.js';
	import { translationRunStore } from '$lib/stores/translationRun.svelte.js';

	/** @type {{ runId: string, onRetry?: () => void, onRetryInsert?: () => void, onComplete?: (statusData: any) => void }} */
	let { runId, onRetry = () => {}, onRetryInsert = () => {}, onComplete = () => {} } = $props();

	let isCancelling = $state(false);
  // @RATIONALE Svelte 5 store detection fails for `$t` in deeply nested templates.
  //             Use getT() + $derived to keep reactivity to locale changes.
  const _t = $derived(getT());
	/** Non-reactive guard — prevents double-firing of onComplete.
	 *  MUST NOT be $state: the $effect reads AND writes it, and a reactive
	 *  write-after-read inside $effect triggers an infinite Svelte flush loop. */
	let completionDelivered = false;

	// Subscribe pattern: single subscription, no render_effect accumulation.
	// Avoids fromStore + $derived creating render_effect instances via createSubscriber
	// on every re-evaluation (documented in TranslationRunGlobalIndicator).
	let storeState = $state<any>(null);
	$effect(() => {
		const unsub = translationRunStore.subscribe((s) => {
			storeState = s?.runId === runId ? s : null;
		});
		return () => unsub();
	});

	let uxState = $derived(storeState?.uxState || 'idle');
	let status = $derived(storeState?.status || null);
	let totalRecords = $derived(storeState?.totalRecords || 0);
	let successfulRecords = $derived(storeState?.successfulRecords || 0);
	let failedRecords = $derived(storeState?.failedRecords || 0);
	let skippedRecords = $derived(storeState?.skippedRecords || 0);
	let cacheHits = $derived(storeState?.cacheHits || 0);
	let progressPct = $derived(storeState?.progressPct || 0);
	let batchCount = $derived(storeState?.batchCount || 0);
	let insertStatus = $derived(storeState?.insertStatus || null);

	let isRunning = $derived(uxState === 'running' || uxState === 'inserting');

	$effect(() => {
		if (!runId) {
			completionDelivered = false;
			return;
		}
		if (isRunning || !status || completionDelivered) return;
		completionDelivered = true;
		onComplete(status);
	});

	/** @returns {Promise<void>} */
	async function handleCancel() {
		isCancelling = true;
		try {
			await cancelRun(runId);
			addToast(getT()?.translate?.run?.run_cancelled, 'info');
		} catch (err) {
			addToast(err?.message || getT()?.translate?.run?.cancel_failed, 'error');
		} finally {
			isCancelling = false;
		}
	}
</script>

<div class="translation-run-progress">
	{#if uxState === 'idle'}
		<div class="bg-surface-muted border border-border rounded-lg p-4 text-center">
			<p class="text-sm text-text-muted">{_t.translate?.run?.loading}</p>
		</div>

	{:else if isRunning}
		<div class="bg-surface-card border border-border rounded-lg p-4 space-y-3">
			<!-- Phase indicator -->
			<div class="flex items-center justify-between">
				<div class="flex items-center gap-2">
					<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-primary-ring"></div>
					<span class="text-sm font-medium text-text">
						{uxState === 'inserting' ? _t.translate?.run?.insert_phase : _t.translate?.run?.translate_phase}
					</span>
				</div>
				<button
					onclick={handleCancel}
					disabled={isCancelling}
					class="px-3 py-1 text-xs border border-destructive-ring text-destructive rounded hover:bg-destructive-light disabled:opacity-50 transition-colors"
				>
					{isCancelling ? _t.translate?.run?.cancelling : _t.translate?.run?.cancel}
				</button>
			</div>

			<!-- Progress bar -->
			<div class="w-full bg-surface-muted rounded-full h-2.5">
				<div
					class="bg-primary h-2.5 rounded-full transition-all duration-500"
					style="width: {Math.min(progressPct, 100)}%"
				></div>
			</div>

			<!-- Statistics -->
			<div class="grid grid-cols-5 gap-3 text-sm">
				<div>
					<span class="text-text-muted">{_t.translate?.run?.total}</span>
					<p class="font-semibold">{totalRecords}</p>
				</div>
				<div>
					<span class="text-success">{_t.translate?.run?.success}</span>
					<p class="font-semibold text-success">{successfulRecords}</p>
				</div>
				<div>
					<span class="text-destructive">{_t.translate?.run?.failed}</span>
					<p class="font-semibold text-destructive">{failedRecords}</p>
				</div>
				<div>
					<span class="text-warning">{_t.translate?.run?.skipped}</span>
					<p class="font-semibold text-warning">{skippedRecords}</p>
				</div>
				<div>
					<span class="text-info">Cache</span>
					<p class="font-semibold text-info">{cacheHits}</p>
				</div>
			</div>

			<!-- Batch counter -->
			<p class="text-xs text-text-subtle">
				{_t.translate?.run?.batches_progress?.replace('{count}', batchCount).replace('{pct}', progressPct)}
			</p>
		</div>

	<!-- Completed -->
	{:else if uxState === 'completed'}
		<div class="bg-success-light border border-success rounded-lg p-4">
			<div class="flex items-center gap-2 mb-2">
				<span class="text-success text-lg">&#10003;</span>
				<span class="text-sm font-semibold text-success">{_t.translate?.run?.completed}</span>
			</div>
			<div class="grid grid-cols-3 gap-3 text-sm">
				<div>
					<span class="text-text-muted">{_t.translate?.run?.total}</span>
					<p class="font-semibold text-success">{successfulRecords} / {totalRecords}</p>
				</div>
				<div>
					<span class="text-text-muted">{_t.translate?.run?.skipped}</span>
					<p class="font-semibold">{skippedRecords}</p>
				</div>
				<div>
					<span class="text-text-muted">{_t.translate?.run?.insert_status?.replace(':', '')}</span>
					<p class="font-semibold text-success">{insertStatus || _t.translate?.run?.insert_completed}</p>
				</div>
			</div>
		</div>

	<!-- Partial (completed with errors) -->
	{:else if uxState === 'partial'}
		<div class="bg-warning-light border border-warning rounded-lg p-4">
			<div class="flex items-center justify-between mb-2">
				<div class="flex items-center gap-2">
					<span class="text-warning text-lg">&#9888;</span>
					<span class="text-sm font-semibold text-warning">{_t.translate?.run?.completed_with_errors}</span>
				</div>
				<button
					onclick={onRetry}
					class="px-3 py-1 text-xs bg-warning-light text-warning rounded hover:bg-yellow-200 transition-colors"
				>
					{_t.translate?.run?.retry_failed}
				</button>
			</div>
			<div class="grid grid-cols-3 gap-3 text-sm">
				<div>
					<span class="text-text-muted">{_t.translate?.run?.success}</span>
					<p class="font-semibold text-success">{successfulRecords}</p>
				</div>
				<div>
					<span class="text-text-muted">{_t.translate?.run?.failed}</span>
					<p class="font-semibold text-destructive">{failedRecords}</p>
				</div>
				<div>
					<span class="text-text-muted">{_t.translate?.run?.skipped}</span>
					<p class="font-semibold">{skippedRecords}</p>
				</div>
			</div>
		</div>

	<!-- Failed -->
	{:else if uxState === 'failed'}
		<div class="bg-destructive-light border-destructive-light rounded-lg p-4">
			<div class="flex items-center justify-between mb-2">
				<div class="flex items-center gap-2">
					<span class="text-destructive text-lg">&#10007;</span>
					<span class="text-sm font-semibold text-destructive">{_t.translate?.run?.translation_failed}</span>
				</div>
				<button
					onclick={onRetry}
					class="px-3 py-1 text-xs bg-destructive-light text-destructive rounded hover:bg-destructive-light transition-colors"
				>
					{_t.translate?.common?.retry}
				</button>
			</div>
			<p class="text-xs text-destructive">{status?.error_message || _t.translate?.common?.unknown}</p>
		</div>

	<!-- Insert Failed -->
	{:else if uxState === 'insert_failed'}
		<div class="bg-warning-light border border-orange-200 rounded-lg p-4">
			<div class="flex items-center justify-between mb-2">
				<div class="flex items-center gap-2">
					<span class="text-warning text-lg">&#9888;</span>
					<span class="text-sm font-semibold text-warning">{_t.translate?.run?.insert_failed}</span>
				</div>
				<div class="flex items-center gap-2">
					<button
						onclick={async () => {
							try { await downloadFailedCsv(runId); }
							catch (e) { addToast(e?.message || 'Download failed', 'error'); }
						}}
						class="px-3 py-1 text-xs bg-primary-light text-primary rounded hover:bg-primary-light transition-colors"
					>
						{_t.translate?.run?.download_csv || 'Download CSV'}
					</button>
					<button
						onclick={onRetryInsert}
						class="px-3 py-1 text-xs bg-warning-light text-warning rounded hover:bg-warning-light transition-colors"
					>
						{_t.translate?.run?.retry_insert}
					</button>
				</div>
			</div>
			<p class="text-xs text-text-muted mt-1">
				{_t.translate?.run?.completed} ({successfulRecords}) &mdash; {_t.translate?.run?.insert_failed}
			</p>
		</div>

	<!-- Cancelled -->
	{:else if uxState === 'cancelled'}
		<div class="bg-surface-muted border border-border rounded-lg p-4">
			<div class="flex items-center gap-2">
				<span class="text-text-muted text-lg">&#9632;</span>
				<span class="text-sm font-semibold text-text">{_t.translate?.run?.cancelled}</span>
			</div>
		</div>
	{/if}
</div>
<!-- #endregion TranslationRunProgress -->
