
// Helper function to convert to number or undefined
const toNumberOrUndefined = (value: any): number | undefined => {
    if (value === undefined || value === null || value === '') {
        return undefined;
    }
    const numValue = Number(value);
    return !isNaN(numValue) && isFinite(numValue) ? numValue : undefined;
};

// Fields that should be converted to numbers if they exist
const NUMERIC_FIELDS = [
    // Top-level
    'invoice_id', 'line_number', 'part_number_id', 'component_part_number_id', 'class_id',
    'unit_of_measure', 'alternate_unit', 'consecutive_destination', 'consecutive_aphis',
    'bom_version', 'bill_version', 'tlcan_value', 'validation_zero', 'validation_one',
    'take_component_pt', 'pallet2', 'rectification',

    // Financial
    'unit_cost_capture', 'unit_cost_usd', 'unit_cost_commercial_usd', 'unit_cost_current_usd',
    'unit_cost_depreciated_usd', 'unit_cost_subitem_usd', 'unit_cost_auxiliary_usd',
    'sales_cost_usd', 'commercial_unit_cost', 'unit_cost_mxn', 'unit_cost_commercial_mxn',
    'unit_cost_current_mxn', 'unit_cost_depreciated_mxn', 'unit_cost_subitem_mxn',
    'sales_cost_mxn', 'unit_cost_mc', 'value_mxn', 'value_commercial_mxn', 'value_updated_mxn',
    'value_subitem_mxn', 'sub_import_value_mxn', 'value_returned_mxn', 'value_depreciated_mxn',
    'customs_value_mxn', 'value_total_mxn', 'value_temp_material_mxn', 'value_def_material_mxn',
    'value_added_mxn', 'value_national_packing_mxn', 'vat_mxn', 'vat_used_mxn',
    'advalorem_line_mxn', 'value_usd', 'value_commercial_usd', 'value_updated_usd',
    'value_subitem_usd', 'sub_import_value_usd', 'value_returned_usd', 'value_depreciated_usd',
    'customs_value_usd', 'value_auxiliary_usd', 'value_total_usd', 'value_temp_material_usd',
    'value_def_material_usd', 'value_added_usd', 'value_national_packing_usd',
    'value_us_packing_usd', 'vat_usd', 'vat_used_usd', 'value_non_originating_usd',
    'value_originating_usd', 'igi_amount_usd', 'exempt_amount_usd', 'total_commercial_value',
    'advalorem_line_usd', 'value_mc', 'sub_import_value_mc', 'vat_mc', 'value_added_mc',
    'value_national_packing_mc', 'value_total_mc', 'value_temp_material_mc', 'value_def_material_mc',

    // Quantity
    'quantity', 'alternate_quantity', 'quantity_uma', 'auxiliary_quantity',
    'quantity_temp_export', 'serial_count', 'net_weight', 'gross_weight',
    'package_id', 'package_quantity', 'container_quantity',

    // Customs
    'advalorem_numeric', 'advalorem_american', 'advalorem_tlcan', 'depreciation_rate',

    // Description
    'eighth_rule_line',

    // Series
    'row', 'serie_row', 'import_line',

    // Identifiers
    'invoice_consecutive', 'part_line',

    // FA Data
    'return_import_date', 'subitem_number', 'search_line'
];

// UI/Display only fields that should be removed before sending to API
const UI_BLACKLIST = [
    'class_code', 'class_unit_of_measure', 'class_description', 'part_description_es',
    'part_description_en', 'part_number_display', 'unit_code', 'unit_description',
    'includes_subitems', 'payment_method_description', 'class_unit_of_measure_description',
    'origin_country_name', 'fraction_description', 'id' // Remove ID for creates, but update handles it via URL
];

// Clean nested data before sending to API
export function cleanLineData(line: any) {
    // 1. Deep clone to unwrap proxies and avoid mutations
    const cleaned = JSON.parse(JSON.stringify(line));

    // 2. Helper to clean an object recursively
    const processObject = (obj: any) => {
        if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return;

        Object.keys(obj).forEach(key => {
            // Remove blacklisted fields
            if (UI_BLACKLIST.includes(key)) {
                delete obj[key];
                return;
            }

            // Convert numeric fields - ONLY if it's not one of our known containers
            const containers = ['financial', 'quantity', 'customs', 'description', 'fa_data', 'reference'];
            const isContainer = containers.includes(key);

            if (NUMERIC_FIELDS.includes(key) && !isContainer) {
                obj[key] = toNumberOrUndefined(obj[key]);
            }

            // Recurse into nested objects if it's one of our known containers
            if (isContainer && obj[key]) {
                processObject(obj[key]);
            }
        });
    };

    // 3. Process the top level and nested objects
    processObject(cleaned);

    // 4. Special handling for Series and Identifiers (arrays)
    if (cleaned.series && Array.isArray(cleaned.series)) {
        cleaned.series = cleaned.series.map((s: any) => {
            if (typeof s !== 'object') return s;
            const sClean = { ...s };
            // Remove UI IDs from series rows if they are local temporary IDs
            if (typeof sClean.id === 'string' && sClean.id.startsWith('temp-')) {
                delete sClean.id;
            }
            processObject(sClean);
            return sClean;
        });
    }

    if (cleaned.identifiers && Array.isArray(cleaned.identifiers)) {
        cleaned.identifiers = cleaned.identifiers.map((i: any) => {
            if (typeof i !== 'object') return i;
            processObject(i);
            return i;
        });
    }

    return cleaned;
}

// Normalize numeric values from strings to numbers (for editing)
export function normalizeItemData(item: Partial<Item>): Partial<Item> {
    if (!item) return {};
    
    // Deep clone to avoid side effects
    const normalized = JSON.parse(JSON.stringify(item));

    const processNormalization = (obj: any) => {
        if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return;

        Object.keys(obj).forEach(key => {
            const containers = ['financial', 'quantity', 'customs', 'description', 'fa_data', 'reference'];
            const isContainer = containers.includes(key);

            if (NUMERIC_FIELDS.includes(key) && !isContainer) {
                const val = obj[key];
                if (val !== undefined && val !== null && val !== '') {
                    const num = Number(val);
                    if (!isNaN(num)) {
                        obj[key] = num;
                    }
                }
            }

            if (isContainer && obj[key]) {
                processNormalization(obj[key]);
            }
        });
    };

    // Process top level and nested objects
    processNormalization(normalized);

    // Ensure mandatory containers exist to allow binding and partial updates
    const mandatoryContainers = ['financial', 'quantity', 'customs', 'description', 'fa_data', 'reference'];
    mandatoryContainers.forEach(container => {
        if (!normalized[container]) {
            normalized[container] = {};
        }
    });

    // Process arrays
    if (normalized.series && Array.isArray(normalized.series)) {
        normalized.series.forEach((s: any) => processNormalization(s));
    } else {
        normalized.series = [];
    }
    
    if (normalized.identifiers && Array.isArray(normalized.identifiers)) {
        normalized.identifiers.forEach((i: any) => processNormalization(i));
    } else {
        normalized.identifiers = [];
    }

    // 5. Popular campos de visualización para que el formulario los muestre al editar
    if (normalized.part_number && !normalized.part_number_display) {
        normalized.part_number_display = normalized.part_number;
    }

    if (normalized.unit_of_measure_code && !normalized.unit_code) {
        normalized.unit_code = normalized.unit_of_measure_code;
    }

    if (normalized.customs?.origin_country && !normalized.origin_country_name) {
        normalized.origin_country_name = normalized.customs.origin_country;
    }

    // 5. Popular campos de visualización para que el formulario los muestre al editar
    if (normalized.part_number && !normalized.part_number_display) {
        normalized.part_number_display = normalized.part_number;
    }

    if (normalized.unit_of_measure_code && !normalized.unit_code) {
        normalized.unit_code = normalized.unit_of_measure_code;
    }

    if (normalized.customs && normalized.customs.origin_country && !normalized.origin_country_name) {
        normalized.origin_country_name = normalized.customs.origin_country;
    }

    // Solo asegurar que el objeto description exista para que sea reactivo al editar
    if (!normalized.description) {
        normalized.description = {
            description_spanish: normalized.part_description_es || '',
            description_english: normalized.part_description_en || ''
        };
    } else {
        // Sincronización bidireccional entre raíz (redundancia) y objeto anidado
        const descES = normalized.description.description_spanish || normalized.part_description_es || '';
        const descEN = normalized.description.description_english || normalized.part_description_en || '';
        
        normalized.description.description_spanish = descES;
        normalized.description.description_english = descEN;
        normalized.part_description_es = descES;
        normalized.part_description_en = descEN;
    }

    return normalized;
}

export interface Item {
    id?: number;
    [key: string]: any;
}

// Helper function to check if an object has any meaningful values
export function hasValues(obj: any): boolean {
    if (!obj || typeof obj !== 'object') return false;
    
    // Si el objeto está intencionalmente vacío o tiene campos que serán usados,
    // es mejor dejar que el backend valide si es requerido.
    const values = Object.values(obj);
    if (values.length === 0) return false;
    
    return values.some(
        (val) =>
            val !== undefined &&
            val !== null &&
            val !== '' &&
            !(typeof val === 'object' && !hasValues(val))
    );
}

// Human-readable mapping for field errors
const FIELD_MAP: Record<string, string> = {
    'class_id': 'Clase',
    'invoice_id': 'ID de Factura',
    'line_number': 'Número de Línea',
    'unit_of_measure': 'Unidad de Medida',
    'description.description_spanish': 'Descripción en Español',
    'description.description_english': 'Descripción en Inglés',
    'quantity.quantity': 'Cantidad',
    'quantity.net_weight': 'Peso Neto',
    'quantity.gross_weight': 'Peso Bruto',
    'financial.unit_cost_capture': 'Costo Unitario',
    'customs.fraction': 'Fracción Arancelaria',
    'customs.origin_country': 'País de Origen',
    'customs.american_fraction': 'Fracción Americana',
    'fa_data.search_invoice': 'Factura de Referencia',
    'fa_data.search_line': 'Línea de Referencia',
    'fa_data.search_type': 'Tipo de Búsqueda',
    'fa_data.movement_type_import': 'Tipo de Importación',
    'fa_data.is_subitem': 'Es Subpartida',
    'fa_data.subitem_number': 'Número de Partida Principal'
};

const FIELD_GUIDANCE: Record<string, string> = {
    class_id: 'Selecciona una clase.',
    unit_of_measure: 'Selecciona una unidad de medida.',
    'quantity.quantity': 'Captura una cantidad válida mayor a cero.',
    'quantity.net_weight': 'Captura un peso neto válido mayor a cero.',
    'customs.fraction': 'Selecciona una fracción arancelaria válida.',
    'customs.origin_country': 'Selecciona un país de origen válido.',
    'customs.fraction_type': 'Selecciona un tipo de tarifa.',
    'customs.american_fraction': 'Selecciona una fracción americana válida.',
    'description.description_spanish': 'Captura la descripción en español.',
    'description.description_english': 'Captura la descripción en inglés.',
    'financial.unit_cost_capture': 'Captura un costo unitario válido.',
    'fa_data.search_invoice': 'Selecciona una factura de referencia.',
    'fa_data.search_line': 'Selecciona una línea de referencia.',
    'fa_data.search_type': 'Selecciona un tipo de búsqueda.',
    'fa_data.movement_type_import': 'Selecciona TEM o DEF.',
    'fa_data.subitem_number': 'Captura el número de la partida principal.'
};

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 mappedPath = fieldPath.replace(/^body\./i, '');
    const fieldLabel = FIELD_MAP[mappedPath] || mappedPath.replace(/\./g, ' → ');

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

    return fieldLabel;
}

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

    return rawMessage
        .replace(/line\[(\d+)\]\.(\w+(?:\.\w+)*)/gi, (_match, lineNumber, fieldPath) => {
            return `Partida ${lineNumber} - ${humanizeFieldPath(fieldPath)}`;
        })
        .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 formatFriendlyFieldMessage(fieldName: string, message: string, code?: string, fieldPath?: string): string {
    const cleanFieldName = fieldName.replace(/^Partida \d+ - /, '');
    // Try to find guidance using the technical fieldPath first, then the humanized name
    const guidance = (fieldPath ? FIELD_GUIDANCE[fieldPath] : null) || FIELD_GUIDANCE[cleanFieldName] || FIELD_GUIDANCE[fieldName];
    const normalizedMessage = humanizeValidationMessage(message);

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

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

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

    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 === 'PACKAGE_NOT_FOUND' || code === 'PACKAGE_ID_REQUIRED') {
        return 'El paquete seleccionado no es válido. Elige una opción del catálogo.';
    }

    if (code === 'MOVEMENT_TYPE_IMPORT_INVALID') {
        return 'Selecciona TEM o DEF para el tipo de importación.';
    }

    return normalizedMessage;
}

/**
 * Formats a backend error into a human-readable Spanish message.
 * Handles 422 (Validation), 403 (Forbidden), 404 (Not Found), and 500 (Server Error).
 * Supports both legacy Axios error objects and new ApiResponse objects.
 */
export function formatItemError(error: any): string {
    if (!error) return 'Error desconocido';

    // 1. Detect if it's a new ApiResponse structure or a legacy Axios error
    const isApiResponse = 'status' in error && ('error' in error || 'validationErrors' in error);
    
    const status = isApiResponse ? error.status : (error?.response?.status);
    const validationErrors = isApiResponse ? error.validationErrors : null;
    const data = !isApiResponse ? error?.response?.data : null;
    const detail = isApiResponse ? error.error : data?.detail;

    // 2. Handle Validation Errors (422)
    // New structure (ApiResponse.validationErrors)
    if (status === 422 && Array.isArray(validationErrors)) {
        const errors = validationErrors.map((err: any) => {
            const path = err.field || '';
            const fieldName = humanizeFieldPath(path);
            const msg = formatFriendlyFieldMessage(fieldName, err.message || 'error de validación', err.code, path.replace(/^line\[\d+\]\./, ''));
            
            return `• ${fieldName}: ${msg}`;
        });
        return `Por favor revisa los siguientes campos:\n${errors.join('\n')}`;
    }
    
    // Legacy structure (FastAPI detail array)
    const legacyDetailArray = !isApiResponse && Array.isArray(detail) ? detail : null;
    if (status === 422 && legacyDetailArray) {
        const errors = legacyDetailArray.map((err: any) => {
            const locPath = (err.loc || [])
                .filter((l: string) => l !== 'body')
                .join('.');
            
            const fieldName = humanizeFieldPath(locPath);
            const msg = formatFriendlyFieldMessage(fieldName, err.msg || 'error de validación', err.type, locPath.replace(/^line\[\d+\]\./, ''));
            
            return `• ${fieldName}: ${msg}`;
        });
        return `Por favor revisa los siguientes campos:\n${errors.join('\n')}`;
    }

    // 3. Handle known detail strings
    if (typeof detail === 'string') {
        const d = detail.trim();
        if (d.includes('Access denied')) return 'No tienes permisos para realizar esta acción.';
        if (d.includes('not found')) return 'El registro no existe o fue eliminado.';
        if (d.includes('Class mismatch')) return 'Error de validación: ' + d;
        return humanizeValidationMessage(d);
    }

    // 4. Fallbacks by status code
    if (status === 403) return 'No tienes permisos para realizar esta operación.';
    if (status === 404) return 'No se encontró el recurso solicitado.';
    if (status >= 500) return 'Hubo un problema en el servidor. Reintenta en unos momentos.';

    // 5. Default messages
    return detail || data?.message || error.message || 'Error al procesar la solicitud.';
}

export type ItemErrorPanelRow = { field: string; message: string; fieldPath?: string };

const INVOICE_ITEM_FIELD_LABEL_SEP = ' - ';

/**
 * For the partidas error panel "Campo" column: hide the standard "Partida N - …" prefix.
 * Only affects display via {@link formatItemErrorRows}.
 */
function shortenItemErrorFieldLabel(field: string): string {
	const f = (field || '').trim();
	if (!f) return f;
	const i = f.indexOf(INVOICE_ITEM_FIELD_LABEL_SEP);
	if (i === -1) return f;
	const rest = f.slice(i + INVOICE_ITEM_FIELD_LABEL_SEP.length).trim();
	return rest || f;
}

/**
 * Parses the multiline string returned by {@link formatItemError} into structured rows
 * for UI tables (field label + user-facing message).
 */
export function parseFormattedItemErrorText(text: string): ItemErrorPanelRow[] {
    const raw = (text || '').trim();
    if (!raw) return [];

    const lines = raw
        .split(/\r?\n/)
        .map((l) => l.trim())
        .filter(Boolean);

    const rows: ItemErrorPanelRow[] = [];

    for (const line of lines) {
        if (/^por favor revisa/i.test(line)) continue;
        if (/^please review/i.test(line)) continue;

        const withoutBullet = line.replace(/^•\s*/, '').trim();
        const sep = ': ';
        const idx = withoutBullet.indexOf(sep);

        if (idx === -1) {
            rows.push({ field: '', message: withoutBullet });
            continue;
        }

        rows.push({
            field: withoutBullet.slice(0, idx).trim(),
            message: withoutBullet.slice(idx + sep.length).trim()
        });
    }

    if (rows.length === 0 && raw) {
        rows.push({ field: '', message: raw });
    }

    return rows;
}

/** Structured rows for error panels, preserving the technical `fieldPath` when available. */
export function formatItemErrorRows(error: any): ItemErrorPanelRow[] {
	if (!error) return [];

	const isApiResponse = 'status' in error && ('error' in error || 'validationErrors' in error);
	const status = isApiResponse ? error.status : error?.response?.status;
	const validationErrors: any[] | null = isApiResponse ? error.validationErrors : null;
	const legacyDetailArray: any[] | null =
		!isApiResponse && Array.isArray(error?.response?.data?.detail)
			? error.response.data.detail
			: null;

	if (status === 422 && Array.isArray(validationErrors) && validationErrors.length > 0) {
		return validationErrors.map((err: any) => {
			const rawPath: string = err.field || '';
			const lineMatch = rawPath.match(/^line\[(\d+)\]\.(.+)$/i);
			const suffix = (lineMatch?.[2] ?? rawPath).replace(/^body\./i, '');
			const fieldLabel = FIELD_MAP[suffix] ?? suffix.replace(/\./g, ' → ');
			const humanField = lineMatch ? `Partida ${lineMatch[1]} - ${fieldLabel}` : fieldLabel;
			const msg = formatFriendlyFieldMessage(humanField, err.message || 'error de validación', err.code, suffix);
			return {
				field: shortenItemErrorFieldLabel(humanField),
				message: msg,
				fieldPath: suffix || undefined
			};
		});
	}

	if (status === 422 && Array.isArray(legacyDetailArray) && legacyDetailArray.length > 0) {
		return legacyDetailArray.map((err: any) => {
			const locPath: string = (err.loc as string[] || []).filter((l) => l !== 'body').join('.');
			const suffix = locPath.replace(/^line\[\d+\]\./i, '').replace(/^body\./i, '');
			const fieldName = humanizeFieldPath(locPath);
			const msg = formatFriendlyFieldMessage(fieldName, err.msg || 'error de validación', err.type, suffix);
			return {
				field: shortenItemErrorFieldLabel(fieldName),
				message: msg,
				fieldPath: suffix || undefined
			};
		});
	}

	return parseFormattedItemErrorText(formatItemError(error)).map((r) => ({
		field: shortenItemErrorFieldLabel(r.field),
		message: r.message
	}));
}
