<script lang="ts">
	import { onMount } from 'svelte';
	import { browser } from '$app/environment';
	import * as Tabs from '$lib/components/ui/tabs/index.js';
	import UploadLauncherGrid from '$lib/components/dashboard/csv-upload/UploadLauncherGrid.svelte';
	import CsvParamsBar from '$lib/components/dashboard/csv-upload/CsvParamsBar.svelte';
	import ProcessingResultModal from '$lib/components/dashboard/csv-upload/ProcessingResultModal.svelte';
	import {
		catalogosConfig,
		transportesConfig,
		importacionConfig,
		exportacionConfig,
		tabSettings,
		globalCsvParams,
		type CsvUploadItem
	} from '$lib/config/csv-upload';
	import { api } from '$lib/api';
	import { Progress } from '$lib/components/ui/progress/index.js';
	import { toast } from 'svelte-sonner';
	import { companyStore } from '$lib/stores/company.svelte';
	import { currentUser, userHasPermission } from '$lib/auth';
	import ErrorState from '$lib/components/dashboard/common/error-state.svelte';

	const canProcessCsv = $derived(userHasPermission($currentUser, 'csv_upload.process'));
	import {
		CSV_IMPORT_SESSION_KEY,
		clearCsvImportSession,
		readCsvImportSession,
		saveCsvImportSession,
		type CsvImportProfile,
		type CsvImportSessionV1
	} from '$lib/csv-import-session';
	import {
		removeCsvImportPending,
		upsertCsvImportPending,
		type CsvImportPendingEntry
	} from '$lib/csv-import-pending';
	import { fetchCsvImportStatus } from '$lib/csv-import-status-api';
	import { totalSkippedFromCommit } from '$lib/csv-import-commit-metrics';
	import { countCsvDataRows } from '$lib/csv-upload-row-count';
	import CsvPendingImportsSheet from '$lib/components/dashboard/csv-upload/CsvPendingImportsSheet.svelte';
	import { csvFmt, csvMsg } from '$lib/i18n/csv-msg';

	/** Intenta extraer un objeto tipo scan desde string tipo repr de Python. */
	function parsePythonReprScan(s: string): Record<string, unknown> | null {
		const jobIdMatch = s.match(/'job_id':\s*'([^']*)'/);
		const totalRowsMatch = s.match(/'total_rows':\s*(\d+)/);
		if (!jobIdMatch || !totalRowsMatch) return null;
		const job_id = jobIdMatch[1];
		const total_rows = parseInt(totalRowsMatch[1], 10);
		const errorCountMatch = s.match(/'error_count':\s*(\d+)/);
		const validRowsMatch = s.match(/'valid_rows':\s*(\d+)/);
		const error_count = errorCountMatch ? parseInt(errorCountMatch[1], 10) : 0;
		const valid_rows = validRowsMatch ? parseInt(validRowsMatch[1], 10) : 0;
		const errors: { line: number; col: string; msg: string; solution?: string; warning?: boolean }[] = [];
		// Buscar cada diccionario de error en la cadena.
		// Nota: el backend suele serializar dicts con claves extra (p.ej. 'solution', 'warning'),
		// así que el regex no puede asumir que el dict termina justo después de 'msg'.
		const errRegex =
			/\{'line':\s*(\d+),\s*'col':\s*'([^']*)',\s*'msg':\s*'((?:[^'\\]|\\.)*)'(?:,\s*'solution':\s*'((?:[^'\\]|\\.)*)')?(?:,\s*'warning':\s*(True|False))?[^}]*\}/g;
		let m: RegExpExecArray | null;
		while ((m = errRegex.exec(s)) !== null) {
			errors.push({
				line: parseInt(m[1], 10),
				col: m[2],
				msg: m[3].replace(/\\'/g, "'"),
				solution: m[4]?.replace(/\\'/g, "'") ?? '',
				warning: m[5] ? m[5] === 'True' : false
			});
		}
		return {
			status: 'waiting_confirmation',
			job_id,
			total_rows,
			error_count,
			valid_rows,
			errors
		};
	}

	/** Intenta extraer un resultado tipo commit (finished/warning) desde repr Python. */
	function parsePythonReprCommit(s: string): Record<string, unknown> | null {
		if (!s || typeof s !== 'string') return null;

		const unescapePy = (v: string) =>
			v.replace(/\\'/g, "'").replace(/\\\\n/g, '\n').replace(/\\\\t/g, '\t').replace(/\\\\r/g, '\r');

		const safeInt = (value: unknown): number => {
			if (value === null || value === undefined) return 0;
			const raw = String(value).replace(/['"]/g, '').trim();
			const n = parseInt(raw, 10);
			return Number.isFinite(n) ? n : 0;
		};

		// Acepta tanto repr Python como JSON (comillas simples/dobles).
		const statusMatch =
			s.match(/['"]status['"]:\s*['"]([^'"]+)['"]/) || s.match(/['"]status['"]:\s*([^,}]+)/);
		if (!statusMatch) return null;
		const status = String(statusMatch[1] ?? '').replace(/['"]/g, '').trim();
		if (status !== 'finished' && status !== 'warning') return null;

		const insertedMatch = s.match(/['"]inserted['"]:\s*['"]?(-?\d+)['"]?/) || s.match(/['"]inserted['"]:\s*([^,}]+)/);
		const inserted = insertedMatch ? safeInt(insertedMatch[1]) : 0;

		const updatedMatch = s.match(/['"]updated['"]:\s*['"]?(-?\d+)['"]?/) || s.match(/['"]updated['"]:\s*([^,}]+)/);
		const updated = updatedMatch ? safeInt(updatedMatch[1]) : 0;

		const skippedInvalidMatch =
			s.match(/['"]skipped_invalid['"]:\s*['"]?(\d+)['"]?/) || s.match(/['"]skipped_invalid['"]:\s*([^,}]+)/);
		const skippedMissingFkMatch =
			s.match(/['"]skipped_missing_fk['"]:\s*['"]?(\d+)['"]?/) || s.match(/['"]skipped_missing_fk['"]:\s*([^,}]+)/);
		const skippedDuplicateMatch =
			s.match(/['"]skipped_duplicate['"]:\s*['"]?(\d+)['"]?/) || s.match(/['"]skipped_duplicate['"]:\s*([^,}]+)/);

		const skipped_invalid = skippedInvalidMatch ? safeInt(skippedInvalidMatch[1]) : 0;
		const skipped_missing_fk = skippedMissingFkMatch
			? safeInt(skippedMissingFkMatch[1])
			: 0;
		const skipped_duplicate = skippedDuplicateMatch
			? safeInt(skippedDuplicateMatch[1])
			: 0;

		const skippedMissingInvMatch =
			s.match(/['"]skipped_missing_invoice['"]:\s*['"]?(\d+)['"]?/) ||
			s.match(/['"]skipped_missing_invoice['"]:\s*([^,}]+)/);
		const skipped_missing_invoice = skippedMissingInvMatch
			? safeInt(skippedMissingInvMatch[1])
			: 0;

		const gapsMatch =
			s.match(/['"]critical_reference_gaps['"]:\s*['"]?(\d+)['"]?/) ||
			s.match(/['"]critical_reference_gaps['"]:\s*([^,}]+)/);
		const critical_reference_gaps = gapsMatch ? safeInt(gapsMatch[1]) : 0;

		const refReadyMatch = s.match(/['"]reference_state_ready['"]:\s*(True|False|true|false)/);
		let reference_state_ready = true;
		if (refReadyMatch) {
			reference_state_ready = refReadyMatch[1].toLowerCase() === 'true';
		}

		// Detalle opcional: lista de dicts con {line, reason, solution?}
		const skipped_details: Array<{ line: number; reason: string; solution?: string }> = [];
		const detailRegex =
			/\{'line':\s*(\d+),\s*'reason':\s*'((?:[^'\\]|\\.)*)'(?:,\s*'solution':\s*'((?:[^'\\]|\\.)*)')?[^}]*\}/g;
		let m: RegExpExecArray | null;
		while ((m = detailRegex.exec(s)) !== null) {
			skipped_details.push({
				line: parseInt(m[1], 10),
				reason: unescapePy(m[2]),
				solution: m[3] ? unescapePy(m[3]) : undefined
			});
		}

		const messageMatch =
			s.match(/'message':\s*'((?:[^'\\]|\\.)*)'/) ||
			s.match(/"message":\s*"((?:[^"\\]|\\.)*)"/);
		const message = messageMatch ? unescapePy(messageMatch[1]) : undefined;

		// Si no hay contadores en el texto, no es un commit.
		if (
			inserted === 0 &&
			updated === 0 &&
			skipped_invalid === 0 &&
			skipped_missing_fk === 0 &&
			skipped_duplicate === 0 &&
			skipped_missing_invoice === 0 &&
			critical_reference_gaps === 0
		) {
			return null;
		}

		// Mínimo requerido por el modal (contrato tipo pedimentos)
		return {
			status,
			inserted,
			updated,
			skipped_invalid,
			skipped_missing_fk,
			skipped_duplicate,
			skipped_missing_invoice,
			critical_reference_gaps,
			reference_state_ready,
			skipped_details,
			...(message ? { message } : {})
		};
	}

	// We no longer need modal state
	let activeTab = $state('catalogos');

	let isUploading = $state(false);
	let currentJobId = $state<string | null>(null);
	let activeModelTarget = $state<string | null>(null);
	let scanResults = $state<any>(null);
	let commitResults = $state<any>(null);
	let showResultModal = $state(false);
	// Cuando es true, usamos API de importación de Agentes Aduanales (customs_brokers/imports)
	let useCustomsBrokerImport = $state(false);
	// Cuando es true, usamos API de importación de Clientes y Proveedores (clients_and_providers/imports)
	let useClientProviderImport = $state(false);
	// Cuando es true, usamos API de importación de Tipos de Cambio (exchange_rate/imports)
	let useExchangeRateImport = $state(false);
	// Cuando es true, usamos API de importación de Pedimentos (pedimentos/imports)
	let usePedimentosImport = $state(false);
	// Cuando es true, usamos API de importación de Clases de Materiales (classes/imports)
	let useMaterialClassesImport = $state(false);
	// Cuando es true, usamos API de importación de Vehículos / Transportes (vehicles/imports)
	let useVehicleImport = $state(false);
	// Cuando es true, usamos API de importación de Conductores (drivers/imports)
	let useDriverImport = $state(false);
	// Cuando es true, usamos API de importación de Trailers y Cajas (trailers/imports)
	let useTrailerImport = $state(false);
	// Cuando es true, usamos API de importación de Transportistas (transporters/imports)
	let useTransporterImport = $state(false);
	// Cuando es true, usamos API de importación de Números de parte (parts/imports)
	let usePartNumbersImport = $state(false);
	// Cuando es true, usamos API de importación de BOMs (boms/imports)
	let useBomImport = $state(false);
	// Cuando es true, usamos API de importación de Exportación (layouts_csv/exportacion)
	let useExportacionImport = $state(false);

	type CsvProgressPhase = 'idle' | 'upload' | 'scan' | 'commit';
	let csvProgressPhase = $state<CsvProgressPhase>('idle');
	let uploadProgressPct = $state(0);
	/** true cuando el navegador reporta tamaño total en XHR (lengthComputable) */
	let uploadLengthComputable = $state(false);
	let scanProgressCurrent = $state(0);
	let scanProgressTotal = $state(0);
	let currentImportLabel = $state<string | null>(null);
	let csvResumeOverlayHint = $state(false);
	let skipScanCompleteToastOnce = $state(false);
	/** Job id del escaneo listo para confirmar (para quitar de pendientes al hacer commit). */
	let scanPhaseJobId = $state<string | null>(null);

	/**
	 * Total de filas de datos conocido para la barra (máx. entre conteo local, total del poll Celery y total_rows del escaneo).
	 * No se reinicia mientras el modal de resultados siga abierto, para poder usar el mismo total en el commit.
	 */
	let csvImportRowTotal = $state(0);

	const csvProgressStepTitle = $derived.by(() => {
		switch (csvProgressPhase) {
			case 'upload':
				return csvMsg('progress.upload');
			case 'scan':
				return csvMsg('progress.scan');
			case 'commit':
				return csvMsg('progress.commit');
			default:
				return '';
		}
	});

	/** Denominador único: filas de la tarea (registros del CSV / total reportado por el worker). */
	const csvImportProgressDenom = $derived.by(() => {
		const d = Math.max(csvImportRowTotal || 0, scanProgressTotal || 0);
		return d > 0 ? d : 1;
	});

	const csvProgressDetailLine = $derived.by(() => {
		const denom = csvImportProgressDenom;
		switch (csvProgressPhase) {
			case 'upload':
				if (csvImportRowTotal > 0) {
					return csvFmt('progress.rows_file', { n: csvImportRowTotal });
				}
				return uploadLengthComputable
					? csvMsg('progress.upload_known')
					: csvMsg('progress.upload_unknown');
			case 'scan':
				return csvFmt('progress.rows_scan', {
					current: Math.min(scanProgressCurrent, denom),
					total: denom
				});
			case 'commit':
				return scanProgressTotal > 0
					? csvFmt('progress.rows_commit', {
							current: Math.min(scanProgressCurrent, denom),
							total: denom
						})
					: csvFmt('progress.rows_commit_fallback', {
							current: Math.min(scanProgressCurrent, denom),
							total: denom
						});
			default:
				return '';
		}
	});

	/** Porcentaje según filas procesadas / total de filas (sin tramos artificiales por etapa). */
	const csvCombinedProgressPct = $derived.by(() => {
		if (!isUploading || csvProgressPhase === 'idle') return 0;
		if (csvProgressPhase === 'upload') {
			return 0;
		}
		const denom = csvImportProgressDenom;
		const numer = Math.min(Math.max(0, scanProgressCurrent), denom);
		return Math.min(100, Math.round((100 * numer) / denom));
	});

	/**
	 * Commit: muchas tareas no publican PROGRESS por fila; si current sigue en 0, barra indeterminada.
	 * Cuando Celery envía current &gt; 0, la barra pasa a reflejar registros reales.
	 */
	const csvFooterProgressIndeterminate = $derived(
		isUploading && !showResultModal && csvProgressPhase === 'commit' && scanProgressCurrent <= 0
	);

	const csvFooterPercentLabel = $derived(
		csvFooterProgressIndeterminate
			? null
			: csvProgressPhase === 'upload'
				? '0%'
				: `${csvCombinedProgressPct}%`
	);

	const csvFooterAriaValueText = $derived(
		csvFooterProgressIndeterminate
			? `${csvProgressStepTitle}. ${csvProgressDetailLine || csvMsg('progress.in_progress')}`
			: csvProgressDetailLine
				? `${csvProgressStepTitle}, ${csvFooterPercentLabel}. ${csvProgressDetailLine}`
				: `${csvProgressStepTitle}, ${csvFooterPercentLabel}`
	);

	$effect(() => {
		if (!isUploading) {
			csvProgressPhase = 'idle';
			uploadProgressPct = 0;
			uploadLengthComputable = false;
			// Con modal abierto (escaneo listo o resultado de commit) conservar totales para la siguiente fase o la UI.
			if (!showResultModal) {
				scanProgressCurrent = 0;
				scanProgressTotal = 0;
				csvImportRowTotal = 0;
			}
			currentImportLabel = null;
			csvResumeOverlayHint = false;
			scanPhaseJobId = null;
		}
	});

	function resetAllImportFlags() {
		useCustomsBrokerImport = false;
		useClientProviderImport = false;
		useExchangeRateImport = false;
		usePedimentosImport = false;
		useMaterialClassesImport = false;
		useVehicleImport = false;
		useDriverImport = false;
		useTrailerImport = false;
		useTransporterImport = false;
		usePartNumbersImport = false;
		useBomImport = false;
		useExportacionImport = false;
	}

	function applyImportProfileFromSession(session: CsvImportSessionV1) {
		resetAllImportFlags();
		switch (session.profile) {
			case 'customs_brokers':
				useCustomsBrokerImport = true;
				break;
			case 'clients_providers':
				useClientProviderImport = true;
				break;
			case 'exchange_rates':
				useExchangeRateImport = true;
				break;
			case 'pedimentos':
				usePedimentosImport = true;
				break;
			case 'material_classes':
				useMaterialClassesImport = true;
				break;
			case 'vehicles':
				useVehicleImport = true;
				break;
			case 'drivers':
				useDriverImport = true;
				break;
			case 'trailers':
				useTrailerImport = true;
				break;
			case 'transporters':
				useTransporterImport = true;
				break;
			case 'part_numbers':
				usePartNumbersImport = true;
				break;
			case 'boms':
				useBomImport = true;
				break;
			case 'exportacion':
				useExportacionImport = true;
				break;
			default:
				break;
		}
		activeTab = session.activeTab;
		currentImportLabel = session.label ?? null;
	}

	function resolveProfileFromFlags(): CsvImportProfile | null {
		if (useCustomsBrokerImport) return 'customs_brokers';
		if (useClientProviderImport) return 'clients_providers';
		if (useExchangeRateImport) return 'exchange_rates';
		if (usePedimentosImport) return 'pedimentos';
		if (useMaterialClassesImport) return 'material_classes';
		if (useVehicleImport) return 'vehicles';
		if (useDriverImport) return 'drivers';
		if (useTrailerImport) return 'trailers';
		if (useTransporterImport) return 'transporters';
		if (usePartNumbersImport) return 'part_numbers';
		if (useBomImport) return 'boms';
		if (useExportacionImport) return 'exportacion';
		return 'imports';
	}

	function pushScanToPendingLocal(scanData: Record<string, unknown>) {
		const cid = companyStore.activeCompany?.id;
		if (cid === undefined) return;
		const jid =
			typeof scanData.job_id === 'string' && scanData.job_id
				? scanData.job_id
				: currentJobId
					? currentJobId
					: null;
		if (!jid) return;
		scanPhaseJobId = jid;
		upsertCsvImportPending({
			companyId: cid,
			jobId: jid,
			profile: resolveProfileFromFlags() ?? 'imports',
			activeModelTarget,
			activeTab: activeTab as CsvImportSessionV1['activeTab'],
			label: currentImportLabel ?? undefined,
			totalRows: typeof scanData.total_rows === 'number' ? scanData.total_rows : undefined,
			validRows: typeof scanData.valid_rows === 'number' ? scanData.valid_rows : undefined
		});
	}

	function finalizeCommitAndClearPending() {
		if (scanPhaseJobId) {
			removeCsvImportPending(scanPhaseJobId);
			scanPhaseJobId = null;
		}
	}

	/** Quita pendientes ligados al flujo actual (escaneo y/o commit pueden tener distinto job id). */
	function removePendingLinkedToCurrentFlow() {
		const ids = new Set<string>();
		if (currentJobId) ids.add(currentJobId);
		if (scanPhaseJobId) ids.add(scanPhaseJobId);
		for (const id of ids) removeCsvImportPending(id);
	}

	function resumePendingImport(entry: CsvImportPendingEntry, scanPayload: Record<string, unknown>) {
		const session: CsvImportSessionV1 = {
			v: 1,
			companyId: entry.companyId,
			jobId: entry.jobId,
			profile: entry.profile,
			activeModelTarget: entry.activeModelTarget,
			activeTab: entry.activeTab,
			...(entry.label ? { label: entry.label } : {})
		};
		applyImportProfileFromSession(session);
		activeModelTarget = entry.activeModelTarget;
		activeTab = entry.activeTab;
		currentJobId = entry.jobId;
		currentImportLabel = entry.label ?? null;
		scanPhaseJobId = entry.jobId;
		scanResults = scanPayload;
		commitResults = null;
		showResultModal = true;
		isUploading = false;
		saveCsvImportSession(session);
		skipScanCompleteToastOnce = true;
	}

	function persistCsvJobFromState() {
		if (!browser || !currentJobId) return;
		const profile = resolveProfileFromFlags() ?? 'imports';
		const companyId = companyStore.activeCompany?.id;
		if (companyId === undefined) return;
		const payload: CsvImportSessionV1 = {
			v: 1,
			companyId,
			jobId: currentJobId,
			profile,
			activeModelTarget,
			activeTab: activeTab as CsvImportSessionV1['activeTab'],
			...(currentImportLabel ? { label: currentImportLabel } : {})
		};
		saveCsvImportSession(payload);
	}

	function beginCsvScanAfterUpload() {
		persistCsvJobFromState();
		csvProgressPhase = 'scan';
		uploadProgressPct = 100;
		pollStatus();
	}

	let csvSessionRestoreAttempted = $state(false);

	$effect(() => {
		if (!browser) return;
		const cid = companyStore.activeCompany?.id;
		if (cid === undefined) return;
		try {
			const raw = sessionStorage.getItem(CSV_IMPORT_SESSION_KEY);
			if (!raw) return;
			const p = JSON.parse(raw) as { companyId?: number };
			if (p.companyId != null && p.companyId !== cid) clearCsvImportSession();
		} catch {
			//
		}
	});

	$effect(() => {
		if (!browser || csvSessionRestoreAttempted) return;
		const cid = companyStore.activeCompany?.id;
		if (cid === undefined) return;
		csvSessionRestoreAttempted = true;
		const session = readCsvImportSession(cid);
		if (!session) return;
		applyImportProfileFromSession(session);
		activeModelTarget = session.activeModelTarget;
		activeTab = session.activeTab;
		currentJobId = session.jobId;
		currentImportLabel = session.label ?? null;
		isUploading = true;
		csvProgressPhase = 'scan';
		uploadProgressPct = 100;
		uploadLengthComputable = true;
		csvResumeOverlayHint = true;
		skipScanCompleteToastOnce = true;
		queueMicrotask(() => void pollStatus());
	});

	onMount(() => {
		const onVis = () => {
			if (!browser || document.visibilityState !== 'visible') return;
			if (currentJobId) void pollStatus();
		};
		document.addEventListener('visibilitychange', onVis);
		return () => document.removeEventListener('visibilitychange', onVis);
	});

	// Initialize settings for all tabs upfront to avoid reactivity loops (sync init so child never receives undefined)
	const _initialSettings: Record<string, any> = {};
	for (const tab in tabSettings) {
		_initialSettings[tab] = {};
		tabSettings[tab].forEach((f) => {
			_initialSettings[tab][f.name] = f.defaultValue;
		});
	}
	let allSettings = $state<Record<string, any>>(_initialSettings);

	// Global parameters for all CSV loads (merged with tab settings when sending footer_config)
	const _initialGlobal: Record<string, string> = {};
	globalCsvParams.forEach((p) => {
		_initialGlobal[p.name] = String(p.defaultValue);
	});
	let globalSettings = $state<Record<string, string>>(_initialGlobal);

	/** Coerce string 'true'/'false' to boolean for API payload. */
	function coerceFooterConfig(obj: Record<string, any>): Record<string, any> {
		const booleanKeys = [
			'autonumber_series',
			'load_subpartidas',
			'recalculate_pedimento_date',
			'autonumber_remesas',
			'recalculate_dates',
			'is_regime_change'
		];
		const out = { ...obj };
		for (const k of booleanKeys) {
			if (k in out && typeof out[k] === 'string') {
				out[k] = out[k] === 'true';
			}
		}
		return out;
	}

	async function handleUpload(file: File, config: CsvUploadItem) {
		console.log('handleUpload started', { file, config });
		isUploading = true;
		csvProgressPhase = 'upload';
		uploadProgressPct = 0;
		uploadLengthComputable = false;
		scanProgressCurrent = 0;
		scanProgressTotal = 0;
		csvImportRowTotal = 0;
		try {
			csvImportRowTotal = await countCsvDataRows(file);
		} catch (e) {
			console.warn('csv-upload: no se pudo contar filas del archivo', e);
		}
		csvResumeOverlayHint = false;
		skipScanCompleteToastOnce = false;
		scanPhaseJobId = null;
		currentImportLabel = csvMsg(`items.${config.id}`);

		const onCsvFileUploadProgress = (e: { loaded: number; total: number }) => {
			if (e.total > 0) {
				uploadLengthComputable = true;
				uploadProgressPct = Math.min(100, Math.round((e.loaded / e.total) * 100));
			}
		};

		activeModelTarget = config.modelTarget || null;
		scanResults = null;
		useCustomsBrokerImport = config.id === 'customs_brokers';
		useClientProviderImport = config.id === 'clients_providers';
		useExchangeRateImport = config.id === 'exchange_rates';
		usePedimentosImport = config.id === 'pedimentos';
		useMaterialClassesImport = config.id === 'material_classes';
		useVehicleImport = config.id === 'transports';
		useDriverImport = config.id === 'drivers';
		useTrailerImport = config.id === 'trailers';
		useTransporterImport = config.id === 'transporters';
		usePartNumbersImport = config.id === 'part_numbers';
		useBomImport = config.id === 'boms';
		useExportacionImport = activeTab === 'exportacion';

		const companyId = companyStore.activeCompany?.id || 1;

		if (useCustomsBrokerImport) {
			try {
				const res = await api.customsBrokerImports.upload(file, companyId, {
					onUploadProgress: onCsvFileUploadProgress
				});
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		if (useClientProviderImport) {
			try {
				const res = await api.clientProviderImports.upload(file, companyId, {
					onUploadProgress: onCsvFileUploadProgress
				});
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		if (useExchangeRateImport) {
			try {
				const catalogosSettings = allSettings['catalogos'] || {};
				const globalMode = globalSettings['mode'] ?? catalogosSettings['mode'] ?? 'update';
				const reemplazar_sin_preguntar = globalMode === 'replace';
				// Formato activo del selector global (Formato de Fecha); mismo default que la barra
				const dateFormat =
					globalSettings['dateFormat'] ?? catalogosSettings['dateFormat'] ?? globalCsvParams.find((p) => p.name === 'dateFormat')?.defaultValue ?? 'dd/mm/yyyy';
				const res = await api.exchangeRateImports.upload(file, companyId, {
					reemplazar_sin_preguntar,
					date_format: dateFormat
				}, { onUploadProgress: onCsvFileUploadProgress });
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		if (usePedimentosImport) {
			try {
				const catalogosSettings = allSettings['catalogos'] || {};
				const actualizar = catalogosSettings['mode'] === 'update';
				const dateFormat =
					globalSettings['dateFormat'] ?? catalogosSettings['dateFormat'] ?? globalCsvParams.find((p) => p.name === 'dateFormat')?.defaultValue ?? 'dd/mm/yyyy';
				const res = await api.pedimentosImports.upload(file, companyId, {
					actualizar,
					dateFormat
				}, { onUploadProgress: onCsvFileUploadProgress });
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		if (useMaterialClassesImport) {
			try {
				const catalogosSettings = allSettings['catalogos'] || {};
				const actualizar = catalogosSettings['mode'] === 'update';
				const res = await api.materialClassImports.upload(file, companyId, {
					actualizar,
					siempre_toda: false
				}, { onUploadProgress: onCsvFileUploadProgress });
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		if (useVehicleImport) {
			try {
				const catalogosSettings = allSettings['catalogos'] || {};
				const actualizar = catalogosSettings['mode'] === 'update';
				const res = await api.vehicleImports.upload(file, companyId, {
					actualizar,
					onUploadProgress: onCsvFileUploadProgress
				});
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		if (useDriverImport) {
			try {
				const res = await api.driverImports.upload(file, companyId, {
					onUploadProgress: onCsvFileUploadProgress
				});
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		if (useTrailerImport) {
			try {
				const transportesSettings = allSettings['transportes'] || {};
				const actualizar = transportesSettings['mode'] === 'update';
				const res = await api.trailerImports.upload(file, companyId, {
					actualizar,
					onUploadProgress: onCsvFileUploadProgress
				});
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		if (useTransporterImport) {
			try {
				const transportesSettings = allSettings['transportes'] || {};
				const actualizar = transportesSettings['mode'] === 'update';
				const res = await api.transporterImports.upload(file, companyId, {
					actualizar,
					onUploadProgress: onCsvFileUploadProgress
				});
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		if (usePartNumbersImport) {
			try {
				const catalogosSettings = allSettings['catalogos'] || {};
				const actualizar = catalogosSettings['mode'] === 'update';
				const res = await api.partNumberImports.upload(file, companyId, {
					actualizar,
					reemplazar_sin_preguntar: true,
					onUploadProgress: onCsvFileUploadProgress
				});
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		if (useBomImport) {
			try {
				const res = await api.bomImports.upload(file, companyId, {
					onUploadProgress: onCsvFileUploadProgress
				});
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		const currentTabSettings = allSettings[activeTab] || {};
		const footerConfig = coerceFooterConfig({
			...globalSettings,
			...currentTabSettings
		});
		if (activeTab === 'importacion') {
			footerConfig.invoice_type = config.id?.startsWith('imp_def_') ? 'DEF' : 'TEM';
		}

		if (useExportacionImport) {
			try {
				const res = await api.exportacionImports.upload(
					file,
					config.modelTarget || '',
					footerConfig,
					companyId,
					config.templateId || config.id,
					{ onUploadProgress: onCsvFileUploadProgress }
				);
				if (res.data?.job_id) {
					currentJobId = res.data.job_id;
					beginCsvScanAfterUpload();
				} else {
					toast.error(res.error || csvMsg('toast.upload_err'));
					isUploading = false;
				}
			} catch (e) {
				console.error('Upload exception', e);
				toast.error(csvMsg('toast.upload_err_generic'));
				isUploading = false;
			}
			return;
		}

		const opType = activeTab === 'exportacion' ? 'exp' : 'imp';

		try {
			const res = await api.imports.upload(
				file,
				config.modelTarget || '',
				footerConfig,
				companyId,
				opType,
				config.templateId || config.id,
				{ onUploadProgress: onCsvFileUploadProgress }
			);
			if (res.data?.job_id) {
				currentJobId = res.data.job_id;
				beginCsvScanAfterUpload();
			} else {
				toast.error(res.error || csvMsg('toast.upload_err'));
				isUploading = false;
			}
		} catch (e) {
			console.error('Upload exception', e);
			toast.error(csvMsg('toast.upload_err_generic'));
			isUploading = false;
		}
	}

	function isStaleImportJobError(status: number, err: string | undefined): boolean {
		if (status === 404) return true;
		const m = (err || '').toLowerCase();
		return /not found|no encontrado|404|expir|no disponible|invalid|inexistente/i.test(m);
	}

	async function pollStatus() {
		if (!currentJobId) return;

		try {
			const profile = resolveProfileFromFlags() ?? 'imports';
			const res = await fetchCsvImportStatus(currentJobId, profile);
			console.log('Poll response', res);
			csvResumeOverlayHint = false;
			if (res.error && !res.data) {
				const errMsg = res.error || '';
				if (isStaleImportJobError(res.status, errMsg)) {
					removePendingLinkedToCurrentFlow();
					toast.info(csvMsg('toast.stale_job'));
				} else {
					toast.error(errMsg || csvMsg('toast.poll_err'));
				}
				isUploading = false;
				clearCsvImportSession();
				currentJobId = null;
				return;
			}
			if (res.data?.status === 'processing') {
				const p = (res.data as { progress?: unknown }).progress;
				const t = (res.data as { total?: unknown }).total;
				if (typeof p === 'number') scanProgressCurrent = p;
				if (typeof t === 'number') {
					scanProgressTotal = t;
					csvImportRowTotal = Math.max(csvImportRowTotal, t);
				}
			}
			// Tratar como resultado de escaneo si viene status waiting_confirmation O si el payload tiene forma de scan (job_id + total_rows)
			const looksLikeScanResult =
				res.data?.status === 'waiting_confirmation' ||
				(res.data?.job_id && typeof res.data?.total_rows === 'number');
			if (looksLikeScanResult) {
				const tr = (res.data as { total_rows?: unknown }).total_rows;
				if (typeof tr === 'number' && tr > 0) {
					csvImportRowTotal = Math.max(csvImportRowTotal, tr);
				}
				scanResults = res.data;
				pushScanToPendingLocal(res.data as Record<string, unknown>);
				showResultModal = true;
				if (skipScanCompleteToastOnce) {
					skipScanCompleteToastOnce = false;
				} else {
					toast.success(csvMsg('toast.scan_done'));
				}
				isUploading = false;
			} else if (res.data?.status === 'failed' || res.data?.status === 'FAILURE') {
				const errRaw = res.data.error;
				// Si el backend devolvió el resultado del scan dentro de error (p. ej. string JSON), usarlo para mostrar el modal
					const errStringCandidate =
						typeof errRaw === 'string'
							? errRaw
							: errRaw && typeof errRaw === 'object' && typeof (errRaw as any).message === 'string'
								? (errRaw as any).message
								: null;

					const parsedCommit = errStringCandidate ? parsePythonReprCommit(errStringCandidate) : null;
					if (parsedCommit) {
						commitResults = parsedCommit;
						showResultModal = true;
						finalizeCommitAndClearPending();
						toast.success(csvMsg('toast.import_done'));
						isUploading = false;
						clearCsvImportSession();
						currentJobId = null;
						return;
					}

				let parsedScan: Record<string, unknown> | null = null;
				if (typeof errRaw === 'string' && (errRaw.includes('waiting_confirmation') || (errRaw.includes('total_rows') && errRaw.includes('job_id')))) {
					try {
						const parsed = JSON.parse(errRaw) as Record<string, unknown>;
						if (parsed && typeof parsed.job_id === 'string' && typeof parsed.total_rows === 'number') {
							parsedScan = parsed;
						}
					} catch {
						// No es JSON; intentar parsear como repr de Python
						parsedScan = parsePythonReprScan(errRaw);
					}
				}
				if (parsedScan) {
					const tr = parsedScan.total_rows;
					if (typeof tr === 'number' && tr > 0) {
						csvImportRowTotal = Math.max(csvImportRowTotal, tr);
					}
					scanResults = parsedScan;
					pushScanToPendingLocal(parsedScan);
					showResultModal = true;
					if (skipScanCompleteToastOnce) {
						skipScanCompleteToastOnce = false;
					} else {
						toast.success(csvMsg('toast.scan_done'));
					}
					isUploading = false;
				} else {
					// Mensaje parece resultado de escaneo pero no se pudo parsear (p. ej. repr Python) → no asustar con error
					const looksLikeScanInError =
						typeof errRaw === 'string' &&
						(errRaw.includes('waiting_confirmation') || (errRaw.includes('total_rows') && errRaw.includes('job_id')));
					if (looksLikeScanInError) {
						removePendingLinkedToCurrentFlow();
						toast.info(csvMsg('toast.scan_alt'));
						isUploading = false;
						clearCsvImportSession();
						currentJobId = null;
						return;
					}
					let errText: string;
					const maybeDoneMsg = csvMsg('toast.import_maybe_done');
					if (typeof errRaw === 'string') {
						errText =
							errRaw.includes('finished') && errRaw.includes('inserted')
								? maybeDoneMsg
								: errRaw.includes("'status'") && errRaw.includes('waiting_confirmation')
									? csvMsg('toast.err_fetch_scan_result')
									: errRaw;
					} else if (typeof errRaw === 'object' && errRaw !== null) {
						errText =
							(errRaw as { message?: string })?.message || csvMsg('toast.err_processing_fallback');
					} else {
						errText = csvMsg('toast.err_unknown');
					}
					// No mostrar como error si el mensaje indica éxito
					if (errText === maybeDoneMsg || errText.includes(maybeDoneMsg)) {
						toast.success(errText);
					} else {
						toast.error(csvFmt('toast.error_processing', { msg: errText }));
					}
					isUploading = false;
					removePendingLinkedToCurrentFlow();
					clearCsvImportSession();
					currentJobId = null;
					scanResults = null;
					commitResults = null;
					showResultModal = false;
				}
			} else if (res.data?.status === 'warning') {
				// Caso cuando no se insertaron registros pero hay información de rechazo
				commitResults = res.data;
				showResultModal = true;
				const inserted = res.data?.inserted || 0;
				const updated = res.data?.updated || 0;
				const totalSkipped = totalSkippedFromCommit(res.data as Record<string, unknown>);
				const backendMessage = res.data?.message;
				const totalOk = inserted + updated;

				if (totalOk === 0) {
					toast.error(
						backendMessage ||
							csvFmt('toast.commit_warning_none', { skipped: totalSkipped })
					);
				} else {
					toast.warning(
						backendMessage ||
							csvFmt('toast.commit_warning_ok', {
								inserted,
								updated,
								skipped: totalSkipped
							})
					);
				}
				finalizeCommitAndClearPending();
				isUploading = false;
			} else if (res.data?.status === 'finished') {
				commitResults = res.data;
				showResultModal = true;
				const inserted = res.data?.inserted || 0;
				const updated = res.data?.updated || 0;
				const totalSkipped = totalSkippedFromCommit(res.data as Record<string, unknown>);

				if (inserted > 0 || updated > 0) {
					const parts: string[] = [];
					if (inserted > 0) parts.push(csvFmt('toast.n_inserted', { n: inserted }));
					if (updated > 0) parts.push(csvFmt('toast.n_updated', { n: updated }));
					toast.success(csvFmt('toast.success_counts', { msg: parts.join(', ') }));
					if (totalSkipped > 0) {
						toast.warning(csvFmt('toast.warn_skipped', { n: totalSkipped }));
					}
				} else {
					toast.error(csvMsg('toast.finished_none'));
				}
				finalizeCommitAndClearPending();
				isUploading = false;
			} else {
				// Continue polling
				console.log('Status not final, polling again in 2s...', res.data?.status);
				setTimeout(pollStatus, 800);
			}
		} catch (e) {
			console.error('Poll exception', e);
			// Retry on network error? Or fail?
			// For now, let's keep retrying a few times or hard fail.
			// Let's just log and retry.
			setTimeout(pollStatus, 800);
		}
	}
</script>

{#if !canProcessCsv}
	<div class="flex h-[calc(100vh-4rem)] items-center justify-center">
		<ErrorState status={403} />
	</div>
{:else}
	<div class="flex flex-col flex-1 min-h-0 -m-4 overflow-hidden">
	<!-- Single scroll: content scrolls here; padding at bottom reserves space for fixed params bar -->
	<div class="flex-1 min-h-0 overflow-y-auto p-4 md:p-8 space-y-4">
		<div class="flex flex-wrap items-center justify-between gap-3">
			<h1 class="text-lg font-semibold md:text-2xl">{csvMsg('page_title')}</h1>
			<CsvPendingImportsSheet companyId={companyStore.activeCompany?.id} onResume={resumePendingImport} />
		</div>
		<p class="text-sm text-muted-foreground">
			{csvMsg('intro_help')}
		</p>

		<Tabs.Root bind:value={activeTab} class="w-full">
			<Tabs.List class="grid w-full grid-cols-2 md:grid-cols-4 lg:w-auto">
				<Tabs.Trigger value="catalogos">{csvMsg('tab_catalogos')}</Tabs.Trigger>
				<Tabs.Trigger value="transportes">{csvMsg('tab_transportes')}</Tabs.Trigger>
				<Tabs.Trigger value="importacion">{csvMsg('tab_importacion')}</Tabs.Trigger>
				<Tabs.Trigger value="exportacion">{csvMsg('tab_exportacion')}</Tabs.Trigger>
			</Tabs.List>

			<div class="mt-6">
				<Tabs.Content value="catalogos" class="space-y-4">
					<!-- Catálogos: backend layouts_csv (customs_brokers, clients_and_providers, parts, boms, etc.) -->
					<div class="flex items-center justify-between">
						<h2 class="text-lg font-medium tracking-tight">{csvMsg('section_catalogs')}</h2>
					</div>
					<UploadLauncherGrid
						items={catalogosConfig}
						onUpload={handleUpload}
						busy={isUploading}
					/>
				</Tabs.Content>

				<Tabs.Content value="transportes" class="space-y-4">
					<!-- Logística: backend layouts_csv (vehicles, drivers, trailers); transportistas sin layouts_csv -->
					<div class="flex items-center justify-between">
						<h2 class="text-lg font-medium tracking-tight">{csvMsg('section_transport')}</h2>
					</div>
					<UploadLauncherGrid
						items={transportesConfig}
						onUpload={handleUpload}
						busy={isUploading}
					/>
				</Tabs.Content>

				<Tabs.Content value="importacion" class="space-y-4">
					<!-- Operaciones de Importación: backend layouts_csv/facturas (api.imports) -->
					<div class="flex items-center justify-between">
						<h2 class="text-lg font-medium tracking-tight">{csvMsg('section_import')}</h2>
					</div>
					<UploadLauncherGrid
						items={importacionConfig}
						onUpload={handleUpload}
						busy={isUploading}
					/>
				</Tabs.Content>

				<Tabs.Content value="exportacion" class="space-y-4">
					<div class="flex items-center justify-between">
						<h2 class="text-lg font-medium tracking-tight">{csvMsg('section_export')}</h2>
					</div>
					<UploadLauncherGrid
						items={exportacionConfig}
						onUpload={handleUpload}
						busy={isUploading}
					/>
				</Tabs.Content>
			</div>
		</Tabs.Root>

		<!-- Spacer: barra de parámetros + franja de progreso cuando aplica -->
		<div
			class="shrink-0"
			style="height: calc(var(--csv-params-bar-height, 6rem) + {(isUploading && !showResultModal)
				? '4.25rem'
				: '0rem'});"
			aria-hidden="true"
		></div>
	</div>

	<!-- Pie fijo: progreso compacto + parámetros (sin overlay a pantalla completa) -->
	<div
		class="fixed right-0 bottom-0 left-0 z-40 ml-[calc(var(--sidebar-width))] flex flex-col border-t border-border bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
	>
		{#if isUploading && !showResultModal}
			<div class="border-b border-border/70">
				<div
					class="mx-auto w-full max-w-[1400px] space-y-1.5 px-4 py-2"
					role="status"
					aria-live="polite"
					aria-busy="true"
				>
					{#if csvResumeOverlayHint}
						<p class="text-xs text-muted-foreground">{csvMsg('progress.resume_hint')}</p>
					{/if}
					{#if currentImportLabel}
						<p class="text-xs font-medium text-foreground">{currentImportLabel}</p>
					{/if}
					<div class="flex items-start justify-between gap-3">
						<p class="min-w-0 flex-1 text-left text-xs font-medium leading-snug text-foreground md:text-sm">
							{csvProgressStepTitle}
						</p>
						{#if csvFooterPercentLabel}
							<span class="shrink-0 tabular-nums text-xs font-semibold text-foreground md:text-sm" aria-hidden="true">
								{csvFooterPercentLabel}
							</span>
						{/if}
					</div>
					{#if csvProgressDetailLine}
						<p class="text-[11px] leading-snug text-muted-foreground md:text-xs">{csvProgressDetailLine}</p>
					{/if}
					<div class="w-full">
						{#if csvFooterProgressIndeterminate}
							<div class="relative h-1.5 w-full overflow-hidden rounded-full bg-muted/70">
								<div
									class="csv-upload-indeterminate-bar absolute top-0 h-full w-2/5 rounded-full bg-primary"
								></div>
							</div>
						{:else}
							<Progress
								value={csvCombinedProgressPct}
								max={100}
								class="h-1.5 bg-muted/70"
								aria-valuetext={csvFooterAriaValueText}
							/>
						{/if}
					</div>
				</div>
			</div>
		{/if}
		<CsvParamsBar
			embedded
			bind:globalSettings
			{activeTab}
			bind:tabSettingsValues={allSettings[activeTab]}
		/>
	</div>
</div>

{#if scanResults || commitResults}
	<ProcessingResultModal
		bind:open={showResultModal}
		{scanResults}
		{commitResults}
		{isUploading}
		scanErrorsDownloadType={
			useCustomsBrokerImport
				? 'customs_brokers'
				: useClientProviderImport
					? 'clients_providers'
					: useExchangeRateImport
						? 'exchange_rate'
						: usePedimentosImport
								? 'pedimentos'
								: useMaterialClassesImport
									? 'classes'
									: useVehicleImport
										? 'vehicles'
										: useDriverImport
											? 'drivers'
											: useTrailerImport
												? 'trailers'
												: useTransporterImport
													? 'transporters'
													: usePartNumbersImport
														? 'parts'
														: useBomImport
															? 'boms'
															: useExportacionImport
																? 'exportacion'
																: 'imports'
		}
		onConfirm={async () => {
			if (!currentJobId) return;
			try {
				isUploading = true;
				csvProgressPhase = 'commit';
				scanProgressCurrent = 0;
				const res = useCustomsBrokerImport
					? await api.customsBrokerImports.commit(currentJobId)
					: useClientProviderImport
						? await api.clientProviderImports.commit(currentJobId)
						: useExchangeRateImport
							? await api.exchangeRateImports.commit(currentJobId)
							: usePedimentosImport
									? await api.pedimentosImports.commit(currentJobId)
									: useMaterialClassesImport
										? await api.materialClassImports.commit(currentJobId)
										: useVehicleImport
											? await api.vehicleImports.commit(currentJobId)
											: useDriverImport
												? await api.driverImports.commit(currentJobId)
												: useTrailerImport
													? await api.trailerImports.commit(currentJobId)
													: useTransporterImport
														? await api.transporterImports.commit(currentJobId)
														: usePartNumbersImport
															? await api.partNumberImports.commit(currentJobId)
															: useBomImport
																? await api.bomImports.commit(currentJobId)
																: useExportacionImport
																	? await api.exportacionImports.commit(currentJobId, activeModelTarget || '')
																	: await api.imports.commit(currentJobId, activeModelTarget || '');
				if (res.data?.commit_job_id) {
					currentJobId = res.data.commit_job_id;
					persistCsvJobFromState();
					pollStatus();
				}
			} catch (err) {
				toast.error(csvMsg('toast.commit_err'));
				isUploading = false;
			}
		}}
		onCancel={() => {
			clearCsvImportSession();
			currentJobId = null;
			scanResults = null;
			commitResults = null;
			showResultModal = false;
		}}
		onClose={() => {
			clearCsvImportSession();
			currentJobId = null;
			scanResults = null;
			commitResults = null;
			showResultModal = false;
		}}
	/>
{/if}

{/if}

<style>
	@keyframes csv-upload-indeterminate {
		0% {
			transform: translateX(-100%);
		}
		100% {
			transform: translateX(400%);
		}
	}
	.csv-upload-indeterminate-bar {
		animation: csv-upload-indeterminate 1.2s ease-in-out infinite;
	}
</style>
