import { COLOR_TYPES, OUTPUT_OPTIONS, FIT_OPTIONS, TO_FORMAT_OPTIONS, CONVERSION_MATRIX, type ColorType, type OutputOption, type FitOption } from '../../../constants/convertConstants'; import { getEndpointName as getEndpointNameUtil, getEndpointUrl, isImageFormat, isWebFormat, getAvailableToExtensions as getAvailableToExtensionsUtil } from '../../../utils/convertUtils'; import { detectFileExtension as detectFileExtensionUtil } from '../../../utils/fileUtils'; import { BaseParameters } from '../../../types/parameters'; import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters'; import { useCallback, useMemo } from 'react'; export interface ConvertParameters extends BaseParameters { fromExtension: string; toExtension: string; imageOptions: { colorType: ColorType; dpi: number; singleOrMultiple: OutputOption; fitOption: FitOption; autoRotate: boolean; combineImages: boolean; }; htmlOptions: { zoomLevel: number; }; emailOptions: { includeAttachments: boolean; maxAttachmentSizeMB: number; downloadHtml: boolean; includeAllRecipients: boolean; }; pdfaOptions: { outputFormat: string; }; isSmartDetection: boolean; smartDetectionType: 'mixed' | 'images' | 'web' | 'none'; } export interface ConvertParametersHook extends BaseParametersHook { getEndpoint: () => string; getAvailableToExtensions: (fromExtension: string) => Array<{value: string, label: string, group: string}>; analyzeFileTypes: (files: Array<{name: string}>) => void; } export const defaultParameters: ConvertParameters = { fromExtension: '', toExtension: '', imageOptions: { colorType: COLOR_TYPES.COLOR, dpi: 300, singleOrMultiple: OUTPUT_OPTIONS.MULTIPLE, fitOption: FIT_OPTIONS.MAINTAIN_ASPECT, autoRotate: true, combineImages: true, }, htmlOptions: { zoomLevel: 1.0, }, emailOptions: { includeAttachments: true, maxAttachmentSizeMB: 10, downloadHtml: false, includeAllRecipients: false, }, pdfaOptions: { outputFormat: 'pdfa-1', }, isSmartDetection: false, smartDetectionType: 'none', }; const validateParameters = (params: ConvertParameters): boolean => { const { fromExtension, toExtension } = params; if (!fromExtension || !toExtension) return false; // Handle dynamic format identifiers (file-) let supportedToExtensions: string[] = []; if (fromExtension.startsWith('file-')) { // Dynamic format - use 'any' conversion options supportedToExtensions = CONVERSION_MATRIX['any'] || []; } else { // Regular format - check conversion matrix supportedToExtensions = CONVERSION_MATRIX[fromExtension] || []; } if (!supportedToExtensions.includes(toExtension)) { return false; } return true; }; const getEndpointName = (params: ConvertParameters): string => { const { fromExtension, toExtension, isSmartDetection, smartDetectionType } = params; if (isSmartDetection) { if (smartDetectionType === 'mixed') { // Mixed file types -> PDF using file-to-pdf endpoint return 'file-to-pdf'; } else if (smartDetectionType === 'images') { // All images -> PDF using img-to-pdf endpoint return 'img-to-pdf'; } else if (smartDetectionType === 'web') { // All web files -> PDF using html-to-pdf endpoint return 'html-to-pdf'; } } // Handle dynamic format identifiers (file-) if (fromExtension.startsWith('file-')) { // Dynamic format - use file-to-pdf endpoint return 'file-to-pdf'; } return getEndpointNameUtil(fromExtension, toExtension); }; export const useConvertParameters = (): ConvertParametersHook => { const config = useMemo(() => ({ defaultParameters, endpointName: getEndpointName, validateFn: validateParameters, }), []); const baseHook = useBaseParameters(config); const getEndpoint = () => { const { fromExtension, toExtension, isSmartDetection, smartDetectionType } = baseHook.parameters; if (isSmartDetection) { if (smartDetectionType === 'mixed') { // Mixed file types -> PDF using file-to-pdf endpoint return '/api/v1/convert/file/pdf'; } else if (smartDetectionType === 'images') { // All images -> PDF using img-to-pdf endpoint return '/api/v1/convert/img/pdf'; } else if (smartDetectionType === 'web') { // All web files -> PDF using html-to-pdf endpoint return '/api/v1/convert/html/pdf'; } } // Handle dynamic format identifiers (file-) if (fromExtension.startsWith('file-')) { // Dynamic format - use file-to-pdf endpoint return '/api/v1/convert/file/pdf'; } return getEndpointUrl(fromExtension, toExtension); }; const getAvailableToExtensions = getAvailableToExtensionsUtil; const analyzeFileTypes = useCallback((files: Array<{name: string}>) => { if (files.length === 0) { // No files - only reset smart detection, keep user's format choices baseHook.setParameters(prev => { // Only update if something actually changed if (prev.isSmartDetection === false && prev.smartDetectionType === 'none') { return prev; // No change needed } return { ...prev, isSmartDetection: false, smartDetectionType: 'none' // Don't reset fromExtension and toExtension - let user keep their choices }; }); return; } if (files.length === 1) { // Single file - use regular detection with smart target selection const detectedExt = detectFileExtensionUtil(files[0].name); let fromExt = detectedExt; let availableTargets = detectedExt ? CONVERSION_MATRIX[detectedExt] || [] : []; // If no explicit conversion exists for this file type, create a dynamic format entry // and fall back to 'any' conversion logic for the actual endpoint if (availableTargets.length === 0 && detectedExt) { fromExt = `file-${detectedExt}`; // Create dynamic format identifier availableTargets = CONVERSION_MATRIX['any'] || []; } else if (availableTargets.length === 0) { // No extension detected - fall back to 'any' fromExt = 'any'; availableTargets = CONVERSION_MATRIX['any'] || []; } baseHook.setParameters(prev => { // Check if current toExtension is still valid for the new fromExtension const currentToExt = prev.toExtension; const isCurrentToExtValid = availableTargets.includes(currentToExt); // Auto-select target only if: // 1. No current target is set, OR // 2. Current target is invalid for new source type, OR // 3. There's only one possible target (forced conversion) let newToExtension = currentToExt; if (!currentToExt || !isCurrentToExtValid) { newToExtension = availableTargets.length === 1 ? availableTargets[0] : ''; } const newState = { ...prev, isSmartDetection: false, smartDetectionType: 'none' as const, fromExtension: fromExt, toExtension: newToExtension }; // Only update if something actually changed if ( prev.isSmartDetection === newState.isSmartDetection && prev.smartDetectionType === newState.smartDetectionType && prev.fromExtension === newState.fromExtension && prev.toExtension === newState.toExtension ) { return prev; // Return the same object to prevent re-render } return newState; }); return; } // Multiple files - analyze file types const extensions = files.map(file => detectFileExtensionUtil(file.name)); const uniqueExtensions = [...new Set(extensions)]; if (uniqueExtensions.length === 1) { // All files are the same type - use regular detection with smart target selection const detectedExt = uniqueExtensions[0]; let fromExt = detectedExt; let availableTargets = CONVERSION_MATRIX[detectedExt] || []; // If no explicit conversion exists for this file type, fall back to 'any' if (availableTargets.length === 0) { fromExt = 'any'; availableTargets = CONVERSION_MATRIX['any'] || []; } baseHook.setParameters(prev => { // Check if current toExtension is still valid for the new fromExtension const currentToExt = prev.toExtension; const isCurrentToExtValid = availableTargets.includes(currentToExt); // Auto-select target only if: // 1. No current target is set, OR // 2. Current target is invalid for new source type, OR // 3. There's only one possible target (forced conversion) let newToExtension = currentToExt; if (!currentToExt || !isCurrentToExtValid) { newToExtension = availableTargets.length === 1 ? availableTargets[0] : ''; } const newState = { ...prev, isSmartDetection: false, smartDetectionType: 'none' as const, fromExtension: fromExt, toExtension: newToExtension }; // Only update if something actually changed if ( prev.isSmartDetection === newState.isSmartDetection && prev.smartDetectionType === newState.smartDetectionType && prev.fromExtension === newState.fromExtension && prev.toExtension === newState.toExtension ) { return prev; // Return the same object to prevent re-render } return newState; }); } else { // Mixed file types const allImages = uniqueExtensions.every(ext => isImageFormat(ext)); const allWeb = uniqueExtensions.every(ext => isWebFormat(ext)); if (allImages) { // All files are images - use image-to-pdf conversion baseHook.setParameters(prev => { // Only update if something actually changed if (prev.isSmartDetection === true && prev.smartDetectionType === 'images' && prev.fromExtension === 'image' && prev.toExtension === 'pdf') { return prev; // No change needed } return { ...prev, isSmartDetection: true, smartDetectionType: 'images', fromExtension: 'image', toExtension: 'pdf' }; }); } else if (allWeb) { // All files are web files - use html-to-pdf conversion baseHook.setParameters(prev => { // Only update if something actually changed if (prev.isSmartDetection === true && prev.smartDetectionType === 'web' && prev.fromExtension === 'html' && prev.toExtension === 'pdf') { return prev; // No change needed } return { ...prev, isSmartDetection: true, smartDetectionType: 'web', fromExtension: 'html', toExtension: 'pdf' }; }); } else { // Mixed non-image types - use file-to-pdf conversion baseHook.setParameters(prev => { // Only update if something actually changed if (prev.isSmartDetection === true && prev.smartDetectionType === 'mixed' && prev.fromExtension === 'any' && prev.toExtension === 'pdf') { return prev; // No change needed } return { ...prev, isSmartDetection: true, smartDetectionType: 'mixed', fromExtension: 'any', toExtension: 'pdf' }; }); } } }, [baseHook.setParameters]); return { ...baseHook, getEndpoint, getAvailableToExtensions, analyzeFileTypes, }; };