/**
 * Cliente API para comunicación con el backend
 */
import { getToken } from './auth';
import { browser } from '$app/environment';
import { toast } from 'svelte-sonner';
import { clearAccessTokenOnDocument, setAccessTokenOnDocument } from '$lib/access-token-cookie-browser';

/** Base URL absoluta para fetch; corrige `http:host` sin `//` y añade `http://` si no hay esquema. */
function normalizeAbsoluteApiBaseUrl(raw: string): string {
	let s = (raw ?? '').trim().replace(/\/+$/, '');
	if (!s) return '';
	if (s.startsWith('http:') && !s.startsWith('http://')) {
		s = 'http://' + s.slice('http:'.length).replace(/^\/+/, '');
	}
	if (s.startsWith('https:') && !s.startsWith('https://')) {
		s = 'https://' + s.slice('https:'.length).replace(/^\/+/, '');
	}
	if (s.startsWith('/')) return s;
	if (/^https?:\/\//i.test(s)) return s;
	return `http://${s.replace(/^\/+/, '')}`;
}

const API_BASE_URL = normalizeAbsoluteApiBaseUrl(String(import.meta.env.VITE_API_URL ?? ''));

export interface ApiResponse<T = any> {
	data?: T;
	error?: string;
	validationErrors?: Array<{
		field: string;
		message: string;
		code?: string;
		solution?: string[];
		value?: any;
	}>;
	status: number;
}

/** Reemplaza referencias técnicas `line[n]` por texto más claro para el usuario. */
export function humanizeLineReferences(text: string): string {
	return text.replace(/\bline\[(\d+)\]/gi, 'partida $1');
}

function humanizeFieldPath(field: string): string {
	const rawField = (field || '').trim();
	if (!rawField) return 'campo';

	const lineMatch = rawField.match(/^line\[(\d+)\]\.(.+)$/i);
	const fieldPath = lineMatch?.[2] || rawField;
	const label = fieldPath
		.replace(/^body\./i, '')
		.replace(/\./g, ' → ')
		.replace(/_/g, ' ');

	if (lineMatch) {
		return `Partida ${lineMatch[1]} - ${label}`;
	}

	return label;
}

function humanizeValidationMessage(message: string): string {
	const rawMessage = (message || '').trim();
	if (!rawMessage) return 'error de validación';

	return rawMessage
		.replace(/\b(field required|is required)\b/gi, 'es obligatorio')
		.replace(/\b(value is not a valid decimal)\b/gi, 'debe ser un número válido')
		.replace(/\b(value is not a valid integer)\b/gi, 'debe ser un número entero válido');
}

function formatValidationHint(field: string, message: string, code?: string): string {
	const fieldLabel = humanizeFieldPath(field);
	const normalizedMessage = humanizeValidationMessage(message);

	if (code === 'REQUIRED' || code === 'REQUIRED_FIELD' || /es obligatorio|es requerido/i.test(normalizedMessage)) {
		return `Completa ${fieldLabel}.`;
	}

	if (code === 'AMERICAN_FRACTION_NOT_FOUND') {
		return 'La fracción americana seleccionada no existe. Elige una opción del catálogo.';
	}

	if (code === 'UNIT_OF_MEASURE_NOT_FOUND') {
		return 'La unidad de medida seleccionada no existe. Elige una opción del catálogo.';
	}

	if (code === 'ORIGIN_COUNTRY_NOT_FOUND') {
		return 'El país de origen seleccionado no existe. Elige una opción del catálogo.';
	}

	if (code === 'CLASS_NOT_FOUND') {
		return 'La clase seleccionada no existe. Elige una opción del catálogo.';
	}

	if (code === 'FRACTION_TYPE_INVALID') {
		return 'Selecciona un tipo de tarifa válido.';
	}

	return normalizedMessage;
}

/**
 * Título y descripción listos para toasts / alertas a partir de ApiResponse.
 * Prioriza los mensajes que ya envía el backend y evita duplicar rutas técnicas.
 */
export function friendlyApiErrorParts(res: ApiResponse): { title: string; description: string } {
	const validationErrors = res.validationErrors;
	if (validationErrors?.length) {
		const blocks = validationErrors.map((e) => {
			const base = formatValidationHint(e.field || '', e.message || '', e.code);
			const hints = e.solution?.filter(Boolean).length
				? '\n' + e.solution!.map((s) => `• ${humanizeLineReferences(s)}`).join('\n')
				: '';
			return base + hints;
		});
		const description = blocks.join('\n\n').trim();
		const rawTitle = (res.error || '').trim();
		const title =
			rawTitle &&
			!rawTitle.startsWith('Error de validación') &&
			rawTitle !== 'Error de validación'
				? rawTitle
				: 'Revisa los datos de la partida';
		return { title, description: description || rawTitle || 'Corrige los datos e intenta de nuevo.' };
	}

	if (res.error) {
		const err = humanizeValidationMessage(humanizeLineReferences(res.error.trim()));
		if (err.startsWith('Error de validación:')) {
			return {
				title: 'Revisa los datos ingresados',
				description: err.replace(/^Error de validación:\s*/i, '').trim() || err
			};
		}
		return { title: 'No se pudo completar la acción', description: err };
	}

	return {
		title: 'Error',
		description: 'Ocurrió un error inesperado. Intenta de nuevo o contacta a soporte si continúa.'
	};
}

let isRefreshing = false;
let refreshSubscribers: ((token: string) => void)[] = [];

/**
 * Agrega una petición a la cola de espera mientras se refresca el token
 */
function subscribeTokenRefresh(callback: (token: string) => void) {
	refreshSubscribers.push(callback);
}

/**
 * Notifica a todas las peticiones en espera que el token se ha refrescado
 */
function onTokenRefreshed(token: string) {
	refreshSubscribers.forEach((callback) => callback(token));
	refreshSubscribers = [];
}

/**
 * Refresca el token silenciosamente usando el endpoint server-side.
 *
 * El servidor lee el refresh_token desde la cookie HttpOnly,
 * llama a Keycloak, actualiza las cookies y devuelve el nuevo access_token.
 * El refresh_token NUNCA es leído por este código JavaScript.
 */
async function refreshToken(): Promise<string | null> {
	if (!browser) return null;

	try {
		const response = await fetch('/api-sveltekit/auth/silent-refresh', {
			method: 'POST',
			credentials: 'include', // Envía cookies HttpOnly automáticamente
			headers: { 'Content-Type': 'application/json' }
		});

		if (!response.ok) {
			console.error('❌ [API] Silent refresh falló, status:', response.status);
			clearAccessTokenOnDocument();
			setTimeout(() => { window.location.href = '/login'; }, 1500);
			return null;
		}

		const data = await response.json() as { access_token?: string };

		if (data.access_token) {
			setAccessTokenOnDocument(data.access_token);

			// Actualizar authStore en memoria
			try {
				const { authStore } = await import('./auth');
				authStore.setToken(data.access_token);
			} catch {}

			return data.access_token;
		}

		return null;
	} catch (error) {
		console.error('❌ [API] Error en silent refresh:', error);
		return null;
	}
}

/**
 * Realiza una petición al API con manejo automático de refresh token
 */
async function fetchApi<T = any>(
	endpoint: string,
	options: RequestInit = {},
	retryCount = 0
): Promise<ApiResponse<T>> {
	// Si ya estamos refrescando el token, esperar
	if (isRefreshing && retryCount === 0) {
		return new Promise((resolve) => {
			subscribeTokenRefresh((newToken) => {
				resolve(fetchApi<T>(endpoint, options, 1));
			});
		});
	}

	const token = getToken();

	if (!token && !endpoint.includes('/auth/login')) {
		console.warn('⚠️ [API] No hay token disponible para', endpoint);
	}

	const headers: Record<string, string> = {
		...((options.headers as Record<string, string>) || {})
	};

	// Only set Content-Type to application/json if not already set and body is not FormData
	if (!headers['Content-Type'] && !(options.body instanceof FormData)) {
		headers['Content-Type'] = 'application/json';
	}

	if (token) {
		headers['Authorization'] = `Bearer ${token}`;
	}

	// Incluir tenant override para flujo SSO multi-tenant.
	// sso_tenant_pub es una cookie no-HttpOnly que el servidor setea junto con sso_tenant_id.
	if (browser) {
		const tenantPub = document.cookie
			.split('; ')
			.find((c) => c.startsWith('sso_tenant_pub='))
			?.split('=')[1];
		if (tenantPub) {
			headers['X-Tenant-Override'] = tenantPub;
		}
	}

	try {
		const response = await fetch(`${API_BASE_URL}${endpoint}`, {
			...options,
			headers,
			credentials: 'include' // Importante: envía cookies con cada request
		});

		// 403 = permisos, no autenticación: nunca intentar refresh.
		if (response.status === 403 && !endpoint.includes('/auth/refresh') && retryCount === 0) {
			if (browser) {
				toast.error('No tienes permisos para realizar esta acción', {
					duration: 4000,
					description: 'Contacta a tu administrador si crees que esto es un error'
				});
			}
			const data = await response.json();
			return {
				error: data.detail || 'No tienes permisos para realizar esta acción',
				status: 403
			};
		}

		// 402 = licencia inválida/expirada: no intentar refresh.
		if (response.status === 402 && !endpoint.includes('/auth/refresh') && retryCount === 0) {
			const data = await response.json().catch(() => ({}));
			return {
				error: data.message || data.detail || 'Licencia inválida o expirada',
				status: 402
			};
		}

		// Solo 401 dispara silent refresh.
		if (response.status === 401 && !endpoint.includes('/auth/refresh') && retryCount === 0) {
			// Si es 401, intentar refrescar el token
			isRefreshing = true;

			try {
				const newToken = await refreshToken();

				if (newToken) {
					// Token refrescado exitosamente					
					onTokenRefreshed(newToken);
					isRefreshing = false;
					// Reintentar la petición original con el nuevo token
					return await fetchApi<T>(endpoint, options, 1);
				} else {
					console.error('❌ [API] No se pudo refrescar el token');
					isRefreshing = false;
					// Retornar error 401 para que la capa superior lo maneje
					return {
						error: 'Sesión expirada. Por favor, inicia sesión nuevamente.',
						status: 401
					};
				}
			} catch (refreshError) {
				console.error('❌ [API] Error al refrescar:', refreshError);
				isRefreshing = false;
				return {
					error: 'Error al refrescar la sesión',
					status: 401
				};
			}
		}

		// Manejar respuestas sin contenido (204 No Content)
		if (response.status === 204) {
			return {
				data: null as T,
				status: response.status
			};
		}

		const data = await response.json();

		if (!response.ok) {
			// Manejo especial para errores 422 (validation error)
			if (response.status === 422) {
				// HTTPException(detail={ message, errors }) — catálogo / CSV parity
				const det = data.detail;
					const validationErrors = (errors: unknown[]) => errors as NonNullable<ApiResponse['validationErrors']>;
				if (
					det &&
					typeof det === 'object' &&
					!Array.isArray(det) &&
					Array.isArray((det as { errors?: unknown }).errors)
				) {
					const d = det as { message?: string; errors: unknown[] };
					return {
						error: d.message || 'Error de validación',
							validationErrors: validationErrors(d.errors),
						status: response.status
					};
				}
				// Errores de validación personalizados (con array errors en raíz)
				if (data.errors && Array.isArray(data.errors)) {
					return {
						error: data.message || 'Error de validación',
							validationErrors: validationErrors(data.errors),
						status: response.status
					};
				}
				// Errores de validación de FastAPI (con detail)
				else if (data.detail) {
					let errorMessage = 'Error de validación: ';

					// FastAPI devuelve errores de validación en data.detail como array
					if (Array.isArray(data.detail)) {
						const errors = data.detail.map((err: any) => {
							const field = err.loc ? err.loc.join('.') : 'campo desconocido';
							return `${field}: ${err.msg}`;
						}).join(', ');
						errorMessage += errors;
					} else if (typeof data.detail === 'string') {
						errorMessage = data.detail;
					} else {
						errorMessage += JSON.stringify(data.detail);
					}

					return {
						error: errorMessage,
						status: response.status
					};
				}
			}

			return {
				error: data.message || (typeof data.detail === 'string' ? data.detail : JSON.stringify(data.detail)) || 'Error en la petición',
				status: response.status
			};
		}

		return {
			data,
			status: response.status
		};
	} catch (error) {
		console.error(`❌ [API] Error de conexión en ${endpoint}:`, error);
		return {
			error: 'Error de conexión con el servidor',
			status: 0
		};
	}
}

/** Opciones para subidas CSV (FormData) con progreso de red. */
export type CsvFormDataUploadOptions = {
	onUploadProgress?: (e: { loaded: number; total: number }) => void;
};

/**
 * POST multipart/form-data con XMLHttpRequest para exponer progreso de subida.
 * Misma semántica de auth/401/403/422 que fetchApi.
 */
async function fetchApiFormDataPost<T = any>(
	endpoint: string,
	formData: FormData,
	opts: CsvFormDataUploadOptions & { retryCount?: number } = {}
): Promise<ApiResponse<T>> {
	const retryCount = opts.retryCount ?? 0;

	if (isRefreshing && retryCount === 0) {
		return new Promise((resolve) => {
			subscribeTokenRefresh(() => {
				resolve(fetchApiFormDataPost<T>(endpoint, formData, { ...opts, retryCount: 1 }));
			});
		});
	}

	return new Promise((resolve) => {
		const token = getToken();
		const xhr = new XMLHttpRequest();
		xhr.open('POST', `${API_BASE_URL}${endpoint}`);
		xhr.withCredentials = true;
		if (token) {
			xhr.setRequestHeader('Authorization', `Bearer ${token}`);
		}

		xhr.upload.onprogress = (ev) => {
			if (!opts.onUploadProgress) return;
			if (ev.lengthComputable) {
				opts.onUploadProgress({ loaded: ev.loaded, total: ev.total });
			} else {
				opts.onUploadProgress({ loaded: ev.loaded, total: 0 });
			}
		};

		xhr.onload = () => {
			void (async () => {
				const status = xhr.status;
				let data: any = null;
				if (xhr.responseText) {
					try {
						data = JSON.parse(xhr.responseText) as any;
					} catch {
						data = null;
					}
				}

				if ((status === 401 || status === 403) && !endpoint.includes('/auth/refresh') && retryCount === 0) {
					if (status === 403) {
						if (browser) {
							toast.error('No tienes permisos para realizar esta acción', {
								duration: 4000,
								description: 'Contacta a tu administrador si crees que esto es un error'
							});
						}
						resolve({
							error: data?.detail || 'No tienes permisos para realizar esta acción',
							status: 403
						});
						return;
					}

					isRefreshing = true;
					try {
						const newToken = await refreshToken();
						if (newToken) {
							onTokenRefreshed(newToken);
							isRefreshing = false;
							resolve(await fetchApiFormDataPost<T>(endpoint, formData, { ...opts, retryCount: 1 }));
						} else {
							console.error('❌ [API] No se pudo refrescar el token');
							isRefreshing = false;
							resolve({
								error: 'Sesión expirada. Por favor, inicia sesión nuevamente.',
								status: 401
							});
						}
					} catch (refreshError) {
						console.error('❌ [API] Error al refrescar:', refreshError);
						isRefreshing = false;
						resolve({
							error: 'Error al refrescar la sesión',
							status: 401
						});
					}
					return;
				}

				if (status === 204) {
					resolve({
						data: null as T,
						status
					});
					return;
				}

				if (status === 0) {
					resolve({
						error: 'Error de conexión con el servidor',
						status: 0
					});
					return;
				}

				if (status < 200 || status >= 300) {
					if (status === 422 && data) {
						if (data.errors && Array.isArray(data.errors)) {
							resolve({
								error: data.message || 'Error de validación',
								validationErrors: data.errors as NonNullable<ApiResponse['validationErrors']>,
								status: 422
							});
							return;
						}
						if (data.detail) {
							let errorMessage = 'Error de validación: ';
							if (Array.isArray(data.detail)) {
								const errors = data.detail
									.map((err: any) => {
										const field = err.loc ? err.loc.filter((loc: string) => loc !== 'body').join('.') : 'campo desconocido';
										return `${humanizeFieldPath(field)}: ${humanizeValidationMessage(err.msg || 'error de validación')}`;
									})
									.join(', ');
								errorMessage += errors;
							} else if (typeof data.detail === 'string') {
								errorMessage = data.detail;
							} else {
								errorMessage += JSON.stringify(data.detail);
							}
							resolve({
								error: errorMessage,
								status: 422
							});
							return;
						}
					}
					resolve({
						error:
							data?.message ||
							(typeof data?.detail === 'string' ? data.detail : JSON.stringify(data?.detail)) ||
							'Error en la petición',
						status
					});
					return;
				}

				if (data === null && xhr.responseText) {
					resolve({
						error: 'Respuesta inválida del servidor',
						status
					});
					return;
				}

				resolve({
					data,
					status
				});
			})();
		};

		xhr.onerror = () => {
			resolve({
				error: 'Error de conexión con el servidor',
				status: 0
			});
		};

		try {
			xhr.send(formData);
		} catch (error) {
			console.error(`❌ [API] Error al enviar ${endpoint}:`, error);
			resolve({
				error: 'Error de conexión con el servidor',
				status: 0
			});
		}
	});
}

/**
 * Convierte cuerpos de error (JSON o texto) en un mensaje legible para toasts/UX.
 * Evita mostrar JSON crudo p. ej. `{"error":"HTTP_ERROR","message":"..."}`.
 */
function messageFromBlobErrorResponse(text: string, status: number): string {
	const raw = (text || '').trim();
	if (!raw) {
		return status === 404
			? 'No se encontró el recurso. Prueba otro rango o vuelve a intentar.'
			: `Error ${status} al descargar el archivo.`;
	}
	try {
		const data = JSON.parse(raw) as Record<string, unknown>;
		if (typeof data.message === 'string' && data.message.trim()) {
			return data.message.trim();
		}
		const d = data.detail;
		if (typeof d === 'string' && d.trim()) {
			return d.trim();
		}
		if (Array.isArray(d) && d[0] && typeof (d[0] as { msg?: string }).msg === 'string') {
			return String((d[0] as { msg: string }).msg).trim();
		}
	} catch {
		// no es JSON: usar texto plano si es corto y legible
	}
	if (raw.length < 500 && !raw.startsWith('{')) {
		return raw;
	}
	if (raw.startsWith('{')) {
		return status === 404
			? 'No se encontró información para exportar. Prueba otras fechas o amplía el rango.'
			: `Error ${status} al descargar el archivo.`;
	}
	return raw;
}

async function fetchBlob(endpoint: string, options: RequestInit = {}): Promise<Blob> {
	const token = getToken();
	const headers: Record<string, string> = {
		...((options.headers as Record<string, string>) || {})
	};
	if (token) {
		headers['Authorization'] = `Bearer ${token}`;
	}

	const response = await fetch(`${API_BASE_URL}${endpoint}`, {
		...options,
		headers,
		credentials: 'include'
	});

	if (!response.ok) {
		const text = await response.text().catch(() => '');
		throw new Error(messageFromBlobErrorResponse(text, response.status));
	}
	return await response.blob();
}

// Métodos HTTP
export const api = {
	get: <T = any>(endpoint: string) => fetchApi<T>(endpoint, { method: 'GET' }),
	getBlob: (endpoint: string) => fetchBlob(endpoint, { method: 'GET' }),
	postBlob: (endpoint: string, body: any) =>
		fetchBlob(endpoint, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify(body)
		}),

	post: <T = any>(endpoint: string, body: any, options: RequestInit = {}) =>
		fetchApi<T>(endpoint, {
			method: 'POST',
			body: JSON.stringify(body),
			...options
		}),

	put: <T = any>(endpoint: string, body: any, options: RequestInit = {}) =>
		fetchApi<T>(endpoint, {
			method: 'PUT',
			body: JSON.stringify(body),
			...options
		}),

	patch: <T = any>(endpoint: string, body: any, options: RequestInit = {}) =>
		fetchApi<T>(endpoint, {
			method: 'PATCH',
			body: JSON.stringify(body),
			...options
		}),

	delete: <T = any>(endpoint: string, options: RequestInit = {}) => fetchApi<T>(endpoint, { method: 'DELETE', ...options }),

	/**
	 * Download CSV template by template_id (generated from code, no static file).
	 * Returns blob and suggested filename for the browser download.
	 */
	async getCsvTemplateDownload(
		templateId: string,
		locale?: string
	): Promise<{ blob: Blob; filename: string }> {
		const token = getToken();
		const headers: Record<string, string> = {};
		if (token) headers['Authorization'] = `Bearer ${token}`;
		const loc = locale === 'en' ? 'en' : 'es';
		const qs = new URLSearchParams({ locale: loc });
		const response = await fetch(
			`${API_BASE_URL}/v1/a76/csv-templates/${templateId}?${qs.toString()}`,
			{
				method: 'GET',
				headers,
				credentials: 'include',
				cache: 'no-store'
			}
		);
		if (!response.ok) {
			const msg = response.status === 404 ? 'Plantilla no encontrada' : `Error ${response.status}`;
			throw new Error(msg);
		}
		const blob = await response.blob();
		let filename = `plantilla_${templateId}.csv`;
		const disposition = response.headers.get('Content-Disposition');
		if (disposition) {
			const match = /filename="?([^";\n]+)"?/.exec(disposition);
			if (match) filename = match[1].trim();
		}
		return { blob, filename };
	},

	// Endpoints específicos
	auth: {
		login: (credentials: { username: string; password: string; tenant_slug: string }) =>
			api.post('/v1/auth/login/', credentials),
		refresh: (refreshToken: string) =>
			api.post('/v1/auth/refresh/', { refresh_token: refreshToken }),
		logout: (data: { refresh_token: string, username?: string }) => api.post('/v1/auth/logout', data, { keepalive: true }),
		me: () => api.get('/v1/auth/me/'),
		health: () => api.get('/health'),
		register: (data: {
			username: string;
			email: string;
			password: string;
			first_name: string;
			last_name: string;
			tenant_slug: string;
			invite_token?: string;
		}) => api.post('/v1/auth/register', data),
	},

	tenants: {
		list: (page = 1, pageSize = 50) =>
			api.get(`/v1/tenants/?page=${page}&page_size=${pageSize}`),
		get: (id: number) => api.get(`/v1/tenants/${id}/`),
		create: (data: any) => api.post('/v1/tenants/', data),
		update: (id: number, data: any) => api.put(`/v1/tenants/${id}/`, data)
	},

	licenses: {
		get: (tenantId: number) => api.get(`/v1/licenses/tenant/${tenantId}/`),
		myLicense: () => api.get('/v1/licenses/my-license/'),
		usage: (tenantId: number) => api.get(`/v1/licenses/usage/${tenantId}/`),
		validate: (tenantId: number) => api.get(`/v1/licenses/validate/${tenantId}/`)
	},

	// CSV import for Operaciones de Importación/Exportación (facturas: encabezados y partidas).
	// Backend: api/v1/modules/a76/layouts_csv/facturas (rutas bajo /v1/a76/imports/).
	imports: {
		upload: (
			file: File,
			modelTarget: string,
			footerConfig: any,
			companyId: number,
			operationType: string,
			templateId?: string,
			uploadOptions?: CsvFormDataUploadOptions
		) => {
			const formData = new FormData();
			formData.append('file', file);
			if (footerConfig) {
				formData.append('footer_config', JSON.stringify(footerConfig));
			}
			if (templateId) {
				formData.append('template_id', templateId);
			}

			const queryParams = new URLSearchParams({
				company_id: String(companyId),
				operation_type: operationType || 'imp'
			}).toString();

			return fetchApiFormDataPost(
				`/v1/a76/imports/upload/${modelTarget}?${queryParams}`,
				formData,
				uploadOptions
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/imports/${jobId}/status`),
		commit: (jobId: string, modelTarget: string) =>
			api.post(`/v1/a76/imports/${jobId}/commit`, { model_target: modelTarget }),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/imports/${jobId}/errors/scan-csv`)
	},

	// CSV import for Operaciones de Exportación (encabezado y partidas).
	// Backend: layouts_csv/exportacion — rutas /v1/a76/imports/exportacion/
	exportacionImports: {
		upload: (
			file: File,
			modelTarget: string,
			footerConfig: any,
			companyId: number,
			templateId?: string,
			uploadOptions?: CsvFormDataUploadOptions
		) => {
			const formData = new FormData();
			formData.append('file', file);
			if (footerConfig) {
				formData.append('footer_config', JSON.stringify(footerConfig));
			}
			if (templateId) {
				formData.append('template_id', templateId);
			}

			const queryParams = new URLSearchParams({
				company_id: String(companyId),
				operation_type: 'exp'
			}).toString();

			return fetchApiFormDataPost(
				`/v1/a76/imports/exportacion/upload/${modelTarget}?${queryParams}`,
				formData,
				uploadOptions
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/imports/exportacion/${jobId}/status`),
		commit: (jobId: string, modelTarget: string) =>
			api.post(`/v1/a76/imports/exportacion/${jobId}/commit`, { model_target: modelTarget }),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/imports/exportacion/${jobId}/errors/scan-csv`)
	},

	// CSV import for Agentes Aduanales (flujo propio en customs_brokers/imports)
	customsBrokerImports: {
		upload: (file: File, companyId: number, uploadOptions?: CsvFormDataUploadOptions) => {
			const formData = new FormData();
			formData.append('file', file);
			return fetchApiFormDataPost(
				`/v1/a76/customs-brokers/imports/upload?company_id=${companyId}`,
				formData,
				uploadOptions
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/customs-brokers/imports/${jobId}/status`),
		commit: (jobId: string) =>
			api.post(`/v1/a76/customs-brokers/imports/${jobId}/commit`, {}),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/customs-brokers/imports/${jobId}/errors/scan-csv`)
	},

	// CSV import for Clientes y Proveedores (flujo propio en clients_and_providers/imports)
	clientProviderImports: {
		upload: (file: File, companyId: number, uploadOptions?: CsvFormDataUploadOptions) => {
			const formData = new FormData();
			formData.append('file', file);
			return fetchApiFormDataPost(
				`/v1/a76/clients-providers/imports/upload?company_id=${companyId}`,
				formData,
				uploadOptions
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/clients-providers/imports/${jobId}/status`),
		commit: (jobId: string) =>
			api.post(`/v1/a76/clients-providers/imports/${jobId}/commit`, {}),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/clients-providers/imports/${jobId}/errors/scan-csv`)
	},

	// CSV import for Tipos de Cambio (flujo en general_catalogs/exchange_rate/imports)
	exchangeRateImports: {
		upload: (
			file: File,
			companyId: number,
			params?: { reemplazar_sin_preguntar?: boolean; date_format?: string },
			uploadOptions?: CsvFormDataUploadOptions
		) => {
			const formData = new FormData();
			formData.append('file', file);
			const search = new URLSearchParams({ company_id: String(companyId) });
			if (params?.reemplazar_sin_preguntar !== undefined)
				search.set('reemplazar_sin_preguntar', String(!!params.reemplazar_sin_preguntar));
			if (params?.date_format != null && params.date_format !== '')
				search.set('date_format', params.date_format);
			return fetchApiFormDataPost(
				`/v1/a76/exchange-rate/imports/upload?${search.toString()}`,
				formData,
				uploadOptions
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/exchange-rate/imports/${jobId}/status`),
		commit: (jobId: string) =>
			api.post(`/v1/a76/exchange-rate/imports/${jobId}/commit`, {}),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/exchange-rate/imports/${jobId}/errors/scan-csv`)
	},

	// CSV import for Pedimentos (pedimentos/imports)
	pedimentosImports: {
		upload: (
			file: File,
			companyId: number,
			params?: { actualizar?: boolean; dateFormat?: string },
			uploadOptions?: CsvFormDataUploadOptions
		) => {
			const formData = new FormData();
			formData.append('file', file);
			const search = new URLSearchParams({ company_id: String(companyId) });
			if (params?.actualizar !== undefined) search.set('actualizar', String(!!params.actualizar));
			if (params?.dateFormat != null) search.set('dateFormat', params.dateFormat);
			return fetchApiFormDataPost(
				`/v1/a76/pedimentos/imports/upload?${search.toString()}`,
				formData,
				uploadOptions
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/pedimentos/imports/${jobId}/status`),
		commit: (jobId: string) =>
			api.post(`/v1/a76/pedimentos/imports/${jobId}/commit`, {}),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/pedimentos/imports/${jobId}/errors/scan-csv`)
	},

	// CSV import for Clases de Materiales (classes/imports)
	materialClassImports: {
		upload: (
			file: File,
			companyId: number,
			params?: { actualizar?: boolean; siempre_toda?: boolean },
			uploadOptions?: CsvFormDataUploadOptions
		) => {
			const formData = new FormData();
			formData.append('file', file);
			const search = new URLSearchParams({ company_id: String(companyId) });
			if (params?.actualizar !== undefined) search.set('actualizar', String(!!params.actualizar));
			if (params?.siempre_toda !== undefined) search.set('siempre_toda', String(!!params.siempre_toda));
			return fetchApiFormDataPost(
				`/v1/a76/classes/imports/upload?${search.toString()}`,
				formData,
				uploadOptions
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/classes/imports/${jobId}/status`),
		commit: (jobId: string) =>
			api.post(`/v1/a76/classes/imports/${jobId}/commit`, {}),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/classes/imports/${jobId}/errors/scan-csv`)
	},

	// CSV import for Vehículos / Transportes (transportation/vehicles/imports)
	vehicleImports: {
		upload: (
			file: File,
			companyId: number,
			options?: { actualizar?: boolean; onUploadProgress?: CsvFormDataUploadOptions['onUploadProgress'] }
		) => {
			const formData = new FormData();
			formData.append('file', file);
			const params = new URLSearchParams({ company_id: String(companyId) });
			if (options?.actualizar !== undefined) params.set('actualizar', String(options.actualizar));
			const uploadOpts =
				options?.onUploadProgress != null ? { onUploadProgress: options.onUploadProgress } : undefined;
			return fetchApiFormDataPost(
				`/v1/a76/transportation/vehicles/imports/upload?${params.toString()}`,
				formData,
				uploadOpts
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/transportation/vehicles/imports/${jobId}/status`),
		commit: (jobId: string) =>
			api.post(`/v1/a76/transportation/vehicles/imports/${jobId}/commit`, {}),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/transportation/vehicles/imports/${jobId}/errors/scan-csv`)
	},

	// CSV import for Conductores (drivers/imports)
	driverImports: {
		upload: (file: File, companyId: number, uploadOptions?: CsvFormDataUploadOptions) => {
			const formData = new FormData();
			formData.append('file', file);
			return fetchApiFormDataPost(
				`/v1/a76/drivers/imports/upload?company_id=${companyId}`,
				formData,
				uploadOptions
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/drivers/imports/${jobId}/status`),
		commit: (jobId: string) => api.post(`/v1/a76/drivers/imports/${jobId}/commit`, {}),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/drivers/imports/${jobId}/errors/scan-csv`)
	},

	// CSV import for Trailers y Cajas (transportation/trailers/imports)
	trailerImports: {
		upload: (
			file: File,
			companyId: number,
			params?: { actualizar?: boolean; onUploadProgress?: CsvFormDataUploadOptions['onUploadProgress'] }
		) => {
			const formData = new FormData();
			formData.append('file', file);
			const actualizar = params?.actualizar ?? false;
			const uploadOpts =
				params?.onUploadProgress != null ? { onUploadProgress: params.onUploadProgress } : undefined;
			return fetchApiFormDataPost(
				`/v1/a76/transportation/trailers/imports/upload?company_id=${companyId}&actualizar=${actualizar}`,
				formData,
				uploadOpts
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/transportation/trailers/imports/${jobId}/status`),
		commit: (jobId: string) =>
			api.post(`/v1/a76/transportation/trailers/imports/${jobId}/commit`, {}),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/transportation/trailers/imports/${jobId}/errors/scan-csv`)
	},

	// CSV import for Transportistas (transporters/imports)
	transporterImports: {
		upload: (
			file: File,
			companyId: number,
			params?: { actualizar?: boolean; onUploadProgress?: CsvFormDataUploadOptions['onUploadProgress'] }
		) => {
			const formData = new FormData();
			formData.append('file', file);
			const actualizar = params?.actualizar ?? false;
			const uploadOpts =
				params?.onUploadProgress != null ? { onUploadProgress: params.onUploadProgress } : undefined;
			return fetchApiFormDataPost(
				`/v1/a76/transporters/imports/upload?company_id=${companyId}&actualizar=${actualizar}`,
				formData,
				uploadOpts
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/transporters/imports/${jobId}/status`),
		commit: (jobId: string) => api.post(`/v1/a76/transporters/imports/${jobId}/commit`, {}),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/transporters/imports/${jobId}/errors/scan-csv`)
	},

	// CSV import for Números de parte (parts/imports)
	partNumberImports: {
		upload: (
			file: File,
			companyId: number,
			options?: {
				actualizar?: boolean;
				reemplazar_sin_preguntar?: boolean;
				onUploadProgress?: CsvFormDataUploadOptions['onUploadProgress'];
			}
		) => {
			const formData = new FormData();
			formData.append('file', file);
			const params = new URLSearchParams({ company_id: String(companyId) });
			if (options?.actualizar !== undefined) params.set('actualizar', String(options.actualizar));
			if (options?.reemplazar_sin_preguntar !== undefined)
				params.set('reemplazar_sin_preguntar', String(options.reemplazar_sin_preguntar));
			const uploadOpts =
				options?.onUploadProgress != null ? { onUploadProgress: options.onUploadProgress } : undefined;
			return fetchApiFormDataPost(
				`/v1/a76/parts/imports/upload?${params.toString()}`,
				formData,
				uploadOpts
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/parts/imports/${jobId}/status`),
		commit: (jobId: string) => api.post(`/v1/a76/parts/imports/${jobId}/commit`, {}),
		downloadScanErrorsCsv: (jobId: string) =>
			fetchBlob(`/v1/a76/parts/imports/${jobId}/errors/scan-csv`)
	},

	// CSV import for BOMs (boms/imports)
	bomImports: {
		upload: (file: File, companyId: number, uploadOptions?: CsvFormDataUploadOptions) => {
			const formData = new FormData();
			formData.append('file', file);
			return fetchApiFormDataPost(
				`/v1/a76/boms/imports/upload?company_id=${companyId}`,
				formData,
				uploadOptions
			);
		},
		status: (jobId: string) => api.get(`/v1/a76/boms/imports/${jobId}/status`),
		commit: (jobId: string) => api.post(`/v1/a76/boms/imports/${jobId}/commit`, {}),
		downloadScanErrorsCsv: (jobId: string) => fetchBlob(`/v1/a76/boms/imports/${jobId}/errors/scan-csv`)
	},

	// Generic request for custom needs (like file uploads)
	request: <T = any>(endpoint: string, options: RequestInit = {}) => fetchApi<T>(endpoint, options)
};
