     1	/**
     2	 * Formidable File Detector for HTTP Multipart Uploads
     3	 *
     4	 * This detector handles multipart/form-data uploads using the formidable library.
     5	 * Unlike busboy, formidable saves files to disk temporarily, which is better
     6	 * for large files.
     7	 *
     8	 * Features:
     9	 * - Disk-based storage (better for large files)
    10	 * - Automatic temp file cleanup
    11	 * - Progress tracking support
    12	 * - Built-in file type detection
    13	 *
    14	 * Usage:
    15	 * ```javascript
    16	 * import { createFormidableDetector } from 'jsonrestapi/plugins/core/lib/formidable-detector.js';
    17	 *
    18	 * api.use(ExpressPlugin, {
    19	 *   fileParser: 'formidable',
    20	 *   fileParserOptions: {
    21	 *     uploadDir: './uploads/temp',
    22	 *     keepExtensions: true,
    23	 *     maxFileSize: 200 * 1024 * 1024 // 200MB
    24	 *   }
    25	 * });
    26	 * ```
    27	 */
    28	
    29	import { requirePackage } from 'hooked-api'
    30	import { promises as fs } from 'fs'
    31	import path from 'path'
    32	
    33	let formidable
    34	try {
    35	  formidable = (await import('formidable')).default
    36	} catch (e) {
    37	  requirePackage('formidable', 'express/http-connector',
    38	    'Formidable is required for multipart/form-data file uploads with disk storage. This is a peer dependency.')
    39	}
    40	
    41	/**
    42	 * Creates a formidable-based file detector
    43	 *
    44	 * @param {Object} options - Formidable configuration options
    45	 * @param {string} options.uploadDir - Directory for temporary files
    46	 * @param {boolean} options.keepExtensions - Keep file extensions
    47	 * @param {number} options.maxFileSize - Max file size in bytes
    48	 * @param {boolean} options.multiples - Allow multiple files per field
    49	 * @returns {Object} Detector object with detect() and parse() methods
    50	 */
    51	export function createFormidableDetector (options = {}) {
    52	  // Set default upload directory
    53	  const uploadDir = options.uploadDir || path.join(process.cwd(), 'uploads', 'temp')
    54	
    55	  return {
    56	    name: 'formidable-multipart',
    57	
    58	    /**
    59	     * Check if this detector can handle the request
    60	     * @param {Object} params - Request parameters
    61	     * @returns {boolean} True if this is a multipart request
    62	     */
    63	    detect: (params) => {
    64	      const req = params._httpReq || params._expressReq
    65	      if (!req || !req.headers) return false
    66	
    67	      const contentType = req.headers['content-type'] || ''
    68	      return contentType.includes('multipart/form-data')
    69	    },
    70	
    71	    /**
    72	     * Parse multipart data from the request
    73	     * @param {Object} params - Request parameters
    74	     * @returns {Promise<{fields: Object, files: Object}>} Parsed data
    75	     */
    76	    parse: async (params) => {
    77	      const req = params._httpReq || params._expressReq
    78	
    79	      // Ensure upload directory exists
    80	      await fs.mkdir(uploadDir, { recursive: true })
    81	
    82	      // Create form parser
    83	      const form = formidable({
    84	        uploadDir,
    85	        keepExtensions: true,
    86	        ...options
    87	      })
    88	
    89	      // Parse the request
    90	      const [fields, fileUploads] = await form.parse(req)
    91	
    92	      // Normalize formidable's file format to our standard format
    93	      const files = {}
    94	
    95	      for (const [fieldname, uploadedFiles] of Object.entries(fileUploads)) {
    96	        // Formidable returns arrays for multiple files
    97	        const fileArray = Array.isArray(uploadedFiles) ? uploadedFiles : [uploadedFiles]
    98	
    99	        // For now, just take the first file (TODO: handle multiple files per field)
   100	        const file = fileArray[0]
   101	
   102	        if (file) {
   103	          // Read file data into memory (for small files)
   104	          // For large files, you might want to keep them on disk
   105	          const data = await fs.readFile(file.filepath)
   106	
   107	          files[fieldname] = {
   108	            filename: file.originalFilename || 'unknown',
   109	            mimetype: file.mimetype || 'application/octet-stream',
   110	            size: file.size,
   111	            data,
   112	            filepath: file.filepath, // Keep for reference
   113	
   114	            // Cleanup function to remove temp file
   115	            cleanup: async () => {
   116	              try {
   117	                await fs.unlink(file.filepath)
   118	              } catch (error) {
   119	                // File might already be deleted, ignore
   120	                if (error.code !== 'ENOENT') {
   121	                  throw error
   122	                }
   123	              }
   124	            }
   125	          }
   126	        }
   127	      }
   128	
   129	      // Normalize fields (formidable returns arrays for repeated fields)
   130	      const normalizedFields = {}
   131	      for (const [key, value] of Object.entries(fields)) {
   132	        normalizedFields[key] = Array.isArray(value) && value.length === 1 ? value[0] : value
   133	      }
   134	
   135	      return { fields: normalizedFields, files }
   136	    }
   137	  }
   138	}
