From b0194e10a87e16797a568d8a30d53ebecd27d8a4 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sat, 18 Oct 2025 15:04:51 +0000
Subject: [PATCH] =DataStore.js and UploaderManager.js overhaul

---
 inc/rest/routes/UploadRoutes.php | 2254 ++++++++++++++++++++++++++++++++--------------------------
 1 files changed, 1,231 insertions(+), 1,023 deletions(-)

diff --git a/inc/rest/routes/UploadRoutes.php b/inc/rest/routes/UploadRoutes.php
index 3049d25..e47b5e9 100644
--- a/inc/rest/routes/UploadRoutes.php
+++ b/inc/rest/routes/UploadRoutes.php
@@ -5,6 +5,7 @@
 use JVBase\rest\RestRouteManager;
 use JVBase\meta\MetaManager;
 use JVBase\managers\UploadManager;
+use JVBase\utility\Features;
 use WP_REST_Request;
 use WP_REST_Response;
 use WP_Error;
@@ -29,42 +30,12 @@
      */
     public function registerRoutes():void
     {
-        register_rest_route($this->namespace, '/uploads', [
-            'methods' => 'POST',
-            'callback' => [$this, 'handleUploadRequest'],
-            'permission_callback' => [$this, 'checkPermission'],
-            'args' => [
-                'content' => [
-                    'required' => true,
-                    'type' => 'string'
-                ],
-                'user'   => [
-                    'required'  => true,
-                    'type'  => 'int'
-                ],
-                'post_id'   => [
-                    'required'  => false,
-                    'type'  => 'int'
-                ],
-                'term_id'   => [
-                    'required'  => false,
-                    'type'  => 'int'
-                ],
-                'field_name' => [
-                    'required'  => false,
-                    'type'  => 'string'
-                ],
-                'id' => [
-                    'required'  => false,
-                    'type'  => 'string'
-                ],
-                'mode' => [
-                    'required'  => false,
-                    'type'  => 'string',
-                    'default' => 'direct'
-                ]
-            ]
-        ]);
+		// Main upload endpoint
+		register_rest_route($this->namespace, '/uploads', [
+			'methods' => 'POST',
+			'callback' => [$this, 'handleUpload'],
+			'permission_callback' => [$this, 'checkPermission']
+		]);
 
 		register_rest_route($this->namespace, '/uploads/groups', [
 			'methods' => 'POST',
@@ -92,55 +63,139 @@
 			'callback' => [$this, 'handleMetadataUpdate'],
 			'permission_callback' => [$this, 'checkPermission'],
 			'args' => [
-				'id' => [
-					'type' => 'string',
-					'required' => false,
-					'description' => 'Original upload operation ID (for updates during processing)'
-				],
-				'attachment_ids' => [
-					'type' => 'array',
-					'required' => false,
-					'description' => 'Direct attachment IDs (for updates after completion)',
-					'items' => ['type' => 'integer']
-				],
-				'upload_ids' => [
-					'type' => 'array',
-					'required' => false,
-					'description' => 'Frontend upload IDs to map to attachments',
-					'items' => ['type' => 'string']
-				],
-				'metadata' => [
-					'type' => 'object',
-					'required' => true,
-					'description' => 'Metadata changes to apply'
-				],
 				'user' => [
 					'type' => 'integer',
 					'required' => true
-				]
+				],
+				'items' => [
+					'type' => 'array',
+					'required' => true,
+					'description' => 'Direct attachment IDs (for updates after completion)',
+				],
 			]
 		]);
     }
 
+	/**
+	 * Build the main $context for UploadManager from the $request params
+	 * @param WP_REST_Request $request
+	 * @return array
+	 */
     protected function buildUploadArgs(WP_REST_Request $request):array
     {
         $data = $request->get_params();
-		error_log('Upload Args: '.print_r($data, true));
-        $args = [
-			'content'        => (array_key_exists('content', $data) && (array_key_exists(jvbGetValidType($data['content']), JVB_CONTENT) || $data['content'] === 'options')) ? jvbGetValidType($data['content']) : false,
-            'user'            => (array_key_exists('user', $data) && $this->userCheck($data['user'])) ? (int)$data['user'] : false,
-            'id'    => (array_key_exists('id', $data) && is_string($data['id'])) ? sanitize_text_field(str_replace('_upload', '', $data['id'])) : false,
-            'post_id'        => (array_key_exists('post_id', $data) && is_numeric($data['post_id'])) ? absint($data['post_id']) : false,
-            'term_id'        => (array_key_exists('term_id', $data) && is_numeric($data['term_id'])) ? absint($data['term_id']) : false,
-            'field_name'    => (array_key_exists('field_name', $data) && is_string($data['field_name'])) ? sanitize_text_field($data['field_name']) : false,
-            'mode'            => (array_key_exists('mode', $data) && in_array($data['mode'], ['direct', 'selection'])) ? sanitize_text_field($data['mode']) : false,
-        ];
+		$args = [];
+		foreach ($data as $key => $value) {
+			switch ($key) {
+				// Post Type/Taxonomy
+				case 'content':
+					$key = str_replace('-', '_', $key);
+					if ($value === 'options' || array_key_exists($value, JVB_CONTENT) || Features::forTaxonomy($key)->has('is_content')) {
+						$args['content'] = $value;
+					}
+					break;
+				case 'destination':
+					if (in_array($value, ['meta', 'post', 'post_group'])) {
+						$args['destination'] = sanitize_text_field($value);
+						if (in_array($value, ['post', 'post_group']) && empty($data['content'])) {
+							throw new Exception("Content type required for destination: {$value}");
+						}
+					}
+					break;
+				// User ID
+				case 'user':
+					if ($this->userCheck($value)) {
+						$args['user'] = (int) $value;
+						if (!array_key_exists('post_id', $data) && !array_key_exists('term_id', $data)) {
+							$args['post_id'] = (int)get_user_meta((int) $value, BASE.'link', true);
+						}
+					}
+					break;
+				// Operation ID
+				case 'id':
+					if (is_string($value)) {
+						$value = sanitize_text_field($value);
+						$args['id'] =  $value;
+						$args['upload'] = $value.'_upload';
+					}
+					break;
+				// Post ID
+				case 'post_id':
+					if (is_numeric($value)) {
+						$args['post_id'] = absint($value);
+					}
+					break;
+				// Term ID
+				case 'term_id':
+					if (is_numeric($value)) {
+						$args['term_id'] = absint($value);
+					}
+					break;
+				// Field Name, as defined for MetaManager.php
+				case 'field_name':
+					if (is_string($value)) {
+						$args['field_name'] = sanitize_text_field($value);
+					}
+					break;
+				// Upload Mode
+				case 'mode':
+					if (in_array($value, ['direct', 'selection'])) {
+						$args['mode'] = sanitize_text_field($value);
+					}
+					break;
+				case 'upload_ids':
+					if (is_string($value)) {
+						// Parse JSON array
+						$decoded = json_decode($value, true);
+						if (is_array($decoded)) {
+							$args['upload_ids'] = $decoded;
+						}
+					} elseif (is_array($value)) {
+						// Already an array (shouldn't happen with FormData JSON, but handle it)
+						$args['upload_ids'] = $value;
+					}
+					break;
+				case 'metadata':
+					if (!empty($value)) {
+						$metadata = is_string($value) ? json_decode($value, true) : $value;
+						if (is_array($metadata)) {
+							foreach ($metadata as $k => $v) {
+								if (in_array($k, ['title', 'caption', 'alt', 'depends_on'])) {
+									$args['metadata'][$k] = sanitize_text_field($v);
+								}
+							}
+						}
+					}
+					break;
+				case 'posts':
+					if (is_string($value)) {
+						$decoded = json_decode($value, true);
+						if (is_array($decoded)) {
+							$args['posts'] = $decoded;
+						}
+					}
+					break;
 
-        if ((!$args['post_id'] && !$args['term_id']) && contentIsJVBUserType($args['content'])) {
-            $args['post_id'] = (int)get_user_meta($args['user'], BASE.'link', true);
-        }
-
-        $args['upload'] = ($args['id']) ? $args['id'].'_upload' : false;
+				case 'group_titles':
+					if (is_string($value)) {
+						$decoded = json_decode($value, true);
+						if (is_array($decoded)) {
+							$args['group_titles'] = array_map('sanitize_text_field', $decoded);
+						}
+					}
+					break;
+				// Other field info
+				case 'field_key':
+				case 'field_type':
+				case 'subtype':
+				case 'item_id':
+				case 'context':
+					if (is_string($value)) {
+						$args[$key] = sanitize_text_field($value);
+					}
+					break;
+			}
+		}
         return $args;
     }
 
@@ -152,111 +207,41 @@
      *
      * @return WP_REST_Response
      */
-	public function handleUploadRequest(WP_REST_Request $request): WP_REST_Response
+	public function handleUpload(WP_REST_Request $request): WP_REST_Response
 	{
 		try {
-			if (!isset($_FILES['files'])) {
-				return $this->createStandardResponse(
-					false,
-					[],
-					__('No files uploaded.', 'jvb')
-				);
-			}
-
-
+			$files = $request->get_file_params();
 			$args = $this->buildUploadArgs($request);
-			error_log('Validating upload args: '.print_r($args, true));
-			// Validation...
-			$validation_errors = [];
-			if (!$args['content'] && !$args['term_id'] && !$args['post_id']) $validation_errors[] = 'content or term or post id required';
-			if (!$args['user']) $validation_errors[] = 'user';
-			if (!$args['id']) $validation_errors[] = 'operation_id';
-			if (!empty($validation_errors)) {
-				error_log('Validation Errors: '.print_r($validation_errors, true));
-				return $this->createStandardResponse(
-					false,
-					['missing_fields' => $validation_errors],
-					'Required fields missing: ' . implode(', ', $validation_errors)
-				);
+
+			if (!$args['content'] || !$args['user']) {
+
+				$this->logError('Missing required data');
+				return new WP_REST_Response([
+					'success'	=> false,
+					'message'	=> 'Missing required data'
+				]);
 			}
 
-			$secured_files = $this->processFileUploads($_FILES['files'], $args);
+			// Step 1: Secure all uploaded files
+			$secured_files = $this->secureFiles($files, $args);
+
 			if (empty($secured_files)) {
-				error_log('Images not processed');
-				return $this->createStandardResponse(
-					false,
-					[],
-					'No valid files could be processed'
-				);
+				$this->logError('No valid files to upload');
+				return new WP_REST_Response([
+					'success'	=> false,
+					'message'	=> 'No valid files to upload'
+				]);
 			}
 
+			// Step 2: Queue for processing via OperationQueue
+			$operation_id = $this->queueProcessing($secured_files, $args);
 
-
-			$queue = JVB()->queue();
-			error_log('Queuing Operation...');
-			$queue->queueOperation(
-				'image_upload',
-				$args['user'],
-				[
-					'secured_files' => $secured_files,
-					'upload_map'	=>  explode(',',$request->get_param('upload_map')),
-					'content' => $args['content'],
-					'post_id' => $args['post_id'],
-					'term_id' => $args['term_id'],
-					'field_name' => $args['field_name'],
-					'mode' => $args['mode'],
-					'operation_id' => $args['id']
-				],
-				[
-					'operation_id' => $args['upload'],
-					'chunk_key'	=> 'secured_files',
-					'chunk_size' => 5,
-					'priority' => 'high',
-					'notification' => true,
-				]
-			);
-
-			error_log('Adding dependent operations...');
-
-
-			if ($args['mode'] !== 'selection') {
-				$queue->queueOperation(
-					'attach_upload_to_content',
-					$args['user'],
-					$args,
-					[
-						'operation_id' => $args['id'],
-						'priority' => 'high',
-						'depends_on' => $args['upload']
-					]
-				);
-			}
-
-			$queue->queueOperation(
-				'temporary_cleanup',
-				$args['user'],
-				[
-					'files' => $secured_files,
-					'content' => $args['content'],
-					'user' => $args['user']
-				],
-				[
-					'priority' => 'low',
-					'chunk_size'	=> 5,
-					'chunk_key'	=> 'files',
-					'depends_on' => $args['upload'],
-				]
-			);
-
-			return $this->createStandardResponse(
-				true,
-				[
-					'files_count' => count($secured_files),
-					'operation_id' => $args['id'],
-					'upload_operation_id' => $args['upload']
-				],
-				'Files secured and queued for processing'
-			);
+			return new WP_REST_Response([
+				'success' => true,
+				'operation_id' => $operation_id,
+				'file_count' => count($secured_files),
+				'message' => 'Files secured and queued for processing'
+			], 200);
 
 		} catch (Exception $e) {
 			// Error handling...
@@ -270,7 +255,7 @@
 				]
 			);
 
-			return $this->createStandardResponse(
+			return $this->sendResponse(
 				false,
 				['error_code' => 'upload_failed'],
 				'Upload processing failed: ' . $e->getMessage()
@@ -278,186 +263,453 @@
 		}
 	}
 
-	protected function processFileUploads(array $files, array $args): array
+	/**
+	 * Secure uploaded files to temporary storage
+	 */
+	protected function secureFiles(array $files, array $args): array
 	{
-		$uploader = new UploadManager($args['content'], $args['user']);
+		$uploader = new UploadManager();
 		$secured_files = [];
+		$upload_map = [];
+		$errors = [];
 
-		// Handle different file array structures
-//		if ($args['mode'] === 'selection') {
-//			// Selection mode: files[group][index]
-//			foreach ($files['tmp_name'] as $group => $images) {
-//				if (is_array($images)) {
-//					foreach ($images as $index => $tmp_name) {
-//						if ($files['error'][$group][$index] === UPLOAD_ERR_OK) {
-//							$file = [
-//								'name' => $files['name'][$group][$index],
-//								'type' => $files['type'][$group][$index],
-//								'tmp_name' => $tmp_name,
-//								'error' => $files['error'][$group][$index],
-//								'size' => $files['size'][$group][$index]
-//							];
-//							$secured_files[$group][] = $uploader->secureUploadedFile($file);
-//						}
-//					}
-//				}
-//			}
-//		} else {
-			// Direct mode: files[index] or files[0][index]
-			$tmp_names = isset($files['tmp_name'][0]) && is_array($files['tmp_name'][0])
-				? $files['tmp_name'][0]
-				: $files['tmp_name'];
+		$context = $args;
+		unset($context['upload_ids']);
 
-			foreach ($tmp_names as $index => $tmp_name) {
-				$file_data = isset($files['tmp_name'][0]) && is_array($files['tmp_name'][0]) ? [
-					'name' => $files['name'][0][$index],
-					'type' => $files['type'][0][$index],
-					'tmp_name' => $tmp_name,
-					'error' => $files['error'][0][$index],
-					'size' => $files['size'][0][$index]
-				] : [
-					'name' => $files['name'][$index],
-					'type' => $files['type'][$index],
-					'tmp_name' => $tmp_name,
-					'error' => $files['error'][$index],
-					'size' => $files['size'][$index]
-				];
+		$config = $this->getFieldConfig($args);
 
-				if ($file_data['error'] === UPLOAD_ERR_OK) {
-					$secured_files[] = $uploader->secureUploadedFile($file_data);
-				}
+		$file_array = $files['files'] ?? $files;
+		$tmp_names = isset($file_array['tmp_name'][0]) && is_array($file_array['tmp_name'][0])
+			? $file_array['tmp_name'][0]
+			: $file_array['tmp_name'];
+
+		foreach ($tmp_names as $index => $tmp_name) {
+			$file_data = isset($file_array['tmp_name'][0]) && is_array($file_array['tmp_name'][0]) ? [
+				'name' => $file_array['name'][0][$index],
+				'type' => $file_array['type'][0][$index],
+				'tmp_name' => $tmp_name,
+				'error' => $file_array['error'][0][$index],
+				'size' => $file_array['size'][0][$index]
+			] : [
+				'name' => $file_array['name'][$index],
+				'type' => $file_array['type'][$index],
+				'tmp_name' => $tmp_name,
+				'error' => $file_array['error'][$index],
+				'size' => $file_array['size'][$index]
+			];
+
+			if ($file_data['error'] !== UPLOAD_ERR_OK) {
+				$errors[$index] = $this->getUploadErrorMessage($file_data['error']);
+				continue;
 			}
-//		}
 
-		return $secured_files;
+			try {
+				$secured = $uploader->secureUploadedFile($file_data, $context);
+
+				if (empty($secured)) {
+					throw new Exception('Failed to secure file');
+				}
+
+				$frontend_id = $args['upload_ids'][$index] ?? 'upload_' . $index;
+				$upload_map[$index] = $frontend_id;
+
+				$secured_files[] = $secured;
+			} catch (Exception $e) {
+				$errors[$index] = $e->getMessage();
+			}
+		}
+
+		return [
+			'files' => $secured_files,
+			'upload_map' => $upload_map,
+			'errors' => $errors
+		];
 	}
 
-    /**
-     * Process upload operation in the background
-     */
-    /**
-     * @param WP_Error|array $result
-     * @param object $operation
-     * @param array $data
-     *
-     * @return array|WP_Error
-     */
-    public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array
-    {
-        switch ($operation->type) {
-            case 'image_upload':
-                return $this->processImageUpload($result, $operation, $data);
-			case 'process_upload_groups':
-				return $this->processUploadGroups($result, $operation, $data);
-			case 'update_metadata':
-				return $this->processMetadataUpdate($result, $operation, $data);
-			case 'temporary_cleanup':
-                return $this->processTemporaryCleanup($result, $operation, $data);
-            case 'attach_upload_to_content':
-                return $this->attachUploadToContent($result, $operation, $data);
-            default:
-                return $result;
-        }
-    }
+	protected function getUploadErrorMessage(int $error_code): string
+	{
+		return match($error_code) {
+			UPLOAD_ERR_INI_SIZE => 'File exceeds maximum upload size',
+			UPLOAD_ERR_FORM_SIZE => 'File exceeds form maximum size',
+			UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
+			UPLOAD_ERR_NO_FILE => 'No file was uploaded',
+			UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
+			UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
+			UPLOAD_ERR_EXTENSION => 'Upload stopped by extension',
+			default => 'Unknown upload error'
+		};
+	}
+
 
 	/**
-	 * Process attachment metadata updates
+	 * Queue files for processing
 	 */
-	protected function processAttachmentMetadata(WP_Error|array $result, object $operation, array $data): WP_Error|array
+	protected function queueProcessing(array $secured_data, array $args):string
+	{
+		$operation_type = $this->determineOperationType($secured_data['files'][0] ?? []);
+
+		$chunkSize = 5;
+		if ($operation_type === 'video') {
+			$chunkSize = 1;
+		} elseif ($operation_type === 'document') {
+			$chunkSize = 10;
+		}
+		JVB()->queue()->queueOperation(
+			$operation_type,
+			$args['user'],
+			array_merge(
+				[
+					'secured_files' 	=> $secured_data['files'],
+					'upload_map' 		=> $secured_data['upload_map'],
+				],
+				$args
+			),
+			[
+				'operation_id'	=> $args['upload'],
+				'chunk_key'		=> 'secured_files',
+				'chunk_size'	=> $chunkSize
+			]
+		);
+
+		if ($args['mode'] !== 'selection') {
+			JVB()->queue()->queueOperation(
+				'attach_upload_to_content',
+				$args['user'],
+				$args,
+				[
+					'priority'		=> 'high',
+					'operation_id'	=> $args['id'],
+					'depends_on'	=> $args['upload']
+				]
+			);
+		}
+
+		JVB()->queue()->queueOperation(
+			'temporary_cleanup',
+			$args['user'],
+			[
+				'files'		=> $secured_data['files'],
+				'context'	=> $args,
+			],
+			[
+				'priority'		=> 'low',
+				'chunk_size'	=> 5,
+				'chunk_key'		=> 'files',
+				'depends_on'	=> $args['upload']
+			]
+		);
+		return $args['id'];
+	}
+
+	/**
+	 * Determine operation type from file data
+	 */
+	protected function determineOperationType(array $file): string
+	{
+		$file_type = $file['file_type'] ?? 'image';
+
+		return match($file_type) {
+			'video' => 'video_upload',
+			'document' => 'document_upload',
+			default => 'image_upload'
+		};
+	}
+
+	/**
+	 * Step 3: Process operation from queue
+	 * Called by OperationQueue
+	 * @param WP_Error|array $result
+	 * @param object $operation
+	 * @param array $data
+	 *
+	 * @return array|WP_Error
+	 */
+	public function processOperation(array $result, object $operation, array $data): array
+	{
+		// Only handle our operation types
+		$handled_types = [
+			'image_upload',
+			'video_upload',
+			'document_upload',
+			'update_metadata',
+			'temporary_cleanup',
+			'attach_upload_to_content',
+			'process_upload_groups'
+		];
+
+		if (!in_array($operation->type, $handled_types)) {
+			return $result; // Not our operation, pass through
+		}
+
+		try {
+			// Route to appropriate handler
+			$handler_result = match($operation->type) {
+				'image_upload' => $this->processImageUpload($operation, $data),
+				'video_upload' => $this->processVideoUpload($operation, $data),
+				'document_upload' => $this->processDocumentUpload($operation, $data),
+				'update_metadata' => $this->processUploadMeta($result, $operation, $data),
+				'temporary_cleanup' => $this->processTemporaryCleanup($result, $operation, $data),
+				'attach_upload_to_content' => $this->processAttachToContent($operation, $data),
+				'process_upload_groups'	=> $this->processUploadGroups($result, $operation, $data),
+				default => new WP_Error('unknown_type', 'Unknown operation type')
+			};
+
+			// Handle WP_Error
+			if (is_wp_error($handler_result)) {
+				return [
+					'success' => false,
+					'result' => $handler_result->get_error_message()
+				];
+			}
+
+			return $handler_result;
+
+		} catch (Exception $e) {
+			JVB()->error()->log(
+				'[UploadRoutes]:processOperation',
+				$e->getMessage(),
+				[
+					'operation_id' => $operation->id,
+					'operation_type' => $operation->type
+				]
+			);
+
+			return [
+				'success' => false,
+				'result' => $e->getMessage()
+			];
+		}
+	}
+	/**
+	 * Standardize processing result
+	 */
+	protected function standardizeResult(array $result): array
+	{
+		return [
+			'attachment_id' => $result['attachment_id'],
+			'url' => $result['url'],
+			'file' => $result['file'],
+			'upload_id' => $result['upload_id'] ?? null
+		];
+	}
+
+	protected function processAttachToContent(object $operation, array $data): array
 	{
 		try {
-			$metadata_updates = $data['metadata_updates'];
-			$updated_count = 0;
-			$failed_count = 0;
-			$results = [];
+			// Get the results from the upload operation
+			$upload_results = JVB()->queue()->getOperationValue($data['upload'], 'result', true);
 
-			foreach ($metadata_updates as $update) {
-				try {
-					$attachment_id = (int)$update['attachment_id'];
-					$upload_id = $update['upload_id'];
+			if (empty($upload_results)) {
+				throw new Exception('No upload results found for operation: ' . $data['upload']);
+			}
 
-					// Verify attachment exists and user has permission
-					if (!$this->verifyAttachmentAccess($attachment_id, $operation->user_id)) {
-						$failed_count++;
-						$results[] = [
-							'upload_id' => $upload_id,
-							'attachment_id' => $attachment_id,
-							'success' => false,
-							'error' => 'Attachment not found or no permission'
-						];
-						continue;
-					}
-
-					// Apply metadata updates
-					$updated_fields = [];
-
-					// Update alt text
-					if (isset($update['alt'])) {
-						$alt_text = sanitize_text_field($update['alt']);
-						update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text);
-						$updated_fields[] = 'alt';
-					}
-
-					// Update title
-					if (isset($update['title'])) {
-						$title = sanitize_text_field($update['title']);
-						wp_update_post([
-							'ID' => $attachment_id,
-							'post_title' => $title
-						]);
-						$updated_fields[] = 'title';
-					}
-
-					// Update caption
-					if (isset($update['caption'])) {
-						$caption = sanitize_textarea_field($update['caption']);
-						wp_update_post([
-							'ID' => $attachment_id,
-							'post_excerpt' => $caption
-						]);
-						$updated_fields[] = 'caption';
-					}
-
-					// Update description
-					if (isset($update['description'])) {
-						$description = sanitize_textarea_field($update['description']);
-						wp_update_post([
-							'ID' => $attachment_id,
-							'post_content' => $description
-						]);
-						$updated_fields[] = 'description';
-					}
-
-					$updated_count++;
-					$results[] = [
-						'upload_id' => $upload_id,
-						'attachment_id' => $attachment_id,
-						'success' => true,
-						'updated_fields' => $updated_fields
-					];
-
-				} catch (Exception $e) {
-					$failed_count++;
-					$results[] = [
-						'upload_id' => $update['upload_id'] ?? 'unknown',
-						'attachment_id' => $update['attachment_id'] ?? 0,
-						'success' => false,
-						'error' => $e->getMessage()
-					];
-				}
+			// Now attach to the specified content
+			if (!empty($data['field_name'])) {
+				$this->updateFieldValue($data, $upload_results);
 			}
 
 			return [
 				'success' => true,
-				'data' => $results,
-				'updated_count' => $updated_count,
-				'failed_count' => $failed_count,
-				'message' => "Metadata updated: {$updated_count} succeeded, {$failed_count} failed"
+				'result' => 'Attachments linked to content'
+			];
+
+		} catch (Exception $e) {
+			return [
+				'success' => false,
+				'result' => $e->getMessage()
+			];
+		}
+	}
+
+	/**
+	 * Cleanup temporary files after processing
+	 */
+	protected function cleanupTempFiles(array $secured_files, int $user_id): void
+	{
+		$uploader = new UploadManager();
+
+		foreach ($secured_files as $secured) {
+			if (!empty($secured['temp_path']) && file_exists($secured['temp_path'])) {
+				@unlink($secured['temp_path']);
+			}
+		}
+
+		// Clean up empty directories
+		$uploader->cleanupEmptyTempDirs($user_id);
+	}
+
+	/**
+	 * Update field value with new attachment IDs
+	 */
+	protected function updateFieldValue(array $data, array $results): void
+	{
+		if ((!array_key_exists('post_id', $data) && !array_key_exists('term_id', $data) && !array_key_exists('user', $data))  && !array_key_exists('field_name', $data)) {
+			return;
+		}
+
+		$attachment_ids = array_column($results, 'attachment_id');
+		if (array_key_exists('post_id', $data)) {
+			$meta = new MetaManager($data['post_id'], 'post');
+		} elseif (array_key_exists('term_id', $data)) {
+			$meta = new MetaManager($data['term_id'], 'term');
+		} else {
+			$link = (int)get_user_meta($data['user'], BASE.'link');
+			$meta = new MetaManager($link, 'post');
+		}
+
+		// Get existing value
+		$existing = $meta->getValue($data['field_name']);
+		$existing_ids = !empty($existing) ? explode(',', $existing) : [];
+
+		// Merge with new IDs
+		$all_ids = array_unique(array_merge($existing_ids, $attachment_ids));
+
+		// Update with comma-separated string
+		$meta->updateValue($data['field_name'], implode(',', $all_ids));
+	}
+
+	/**
+	 * Generic file upload processor - works for images, videos, documents
+	 */
+	protected function processFileUpload(object $operation, array $data, string $file_type): WP_Error|array
+	{
+		try {
+			$uploader = new UploadManager();
+			$processed_results = [];
+			$config = $this->getFieldConfig($data);
+
+			$args = $data;
+			unset($args['secured_files']);
+			unset($args['upload_map']);
+
+			foreach ($data['secured_files'] as $index => $secured_file) {
+				$result = $uploader->processUpload(
+					$secured_file['temp_path'],
+					array_merge(
+						$config,
+						[
+							'file_type' => $file_type,
+							'user_id' => $operation->user_id,
+							'post_id' => (int)($data['post_id'] ?? 0),
+							'term_id' => (int)($data['term_id'] ?? 0),
+							'original_name' => $secured_file['original_name'],
+							'mime_type' => $secured_file['mime_type'],
+							'content' => $data['content'] ?? '', // For directory pattern
+						]
+					)
+				);
+
+				if (!is_wp_error($result)) {
+					$standardized = $this->standardizeResult($result);
+
+					// Map to frontend upload ID
+					if (isset($data['upload_map'][$index])) {
+						$standardized['upload_id'] = $data['upload_map'][$index];
+					}
+
+					// Apply frontend metadata if provided
+					if (!empty($data['frontend_metadata'][$index])) {
+						$this->applyMeta(
+							$standardized['attachment_id'],
+							$data['frontend_metadata'][$index]
+						);
+					}
+
+					$processed_results[$standardized['upload_id']] = $standardized;
+				}
+			}
+
+			$this->handleUploadDestination($data, $processed_results);
+
+			// Cleanup temporary files
+			$this->cleanupTempFiles($data['secured_files'], $operation->user_id);
+
+			return [
+				'success' => true,
+				'result' => $processed_results
 			];
 
 		} catch (Exception $e) {
 			JVB()->error()->log(
-				'[UploadRoutes]:processAttachmentMetadata',
+				'[UploadRoutes]:processFileUpload',
+				$e->getMessage(),
+				[
+					'operation_id' => $operation->id,
+					'file_type' => $file_type,
+					'user_id' => $operation->user_id
+				]
+			);
+			return [
+				'success'	=> false,
+				'result'	=> $e->getMessage()
+			];
+		}
+	}
+
+	/**
+	 * Process image uploads
+	 */
+	protected function processImageUpload(object $operation, array $data): WP_Error|array
+	{
+		return $this->processFileUpload($operation, $data, 'image');
+	}
+
+	/**
+	 * Process video uploads
+	 */
+	protected function processVideoUpload(object $operation, array $data): WP_Error|array
+	{
+		return $this->processFileUpload($operation, $data, 'video');
+	}
+
+	/**
+	 * Process document uploads
+	 */
+	protected function processDocumentUpload(object $operation, array $data): WP_Error|array
+	{
+		return $this->processFileUpload($operation, $data, 'document');
+	}
+
+	/**
+	 * Process temporary cleanup operation
+	 * Called by OperationQueue for 'temporary_cleanup' operations
+	 *
+	 * @param array $result
+	 * @param object $operation
+	 * @param array $data
+	 * @return array
+	 */
+	protected function processTemporaryCleanup(array $result, object $operation, array $data): array
+	{
+		try {
+			// Cleanup temporary files if they exist
+			if (!empty($data['secured_files'])) {
+				$this->cleanupTempFiles($data['secured_files'], $operation->user_id);
+			}
+
+			// If specific temp paths provided
+			if (!empty($data['temp_paths']) && is_array($data['temp_paths'])) {
+				foreach ($data['temp_paths'] as $temp_path) {
+					if (file_exists($temp_path)) {
+						@unlink($temp_path);
+					}
+				}
+			}
+
+			// Cleanup empty temp directories for this user
+			if (!empty($operation->user_id)) {
+				$uploader = new UploadManager();
+				$uploader->cleanupEmptyTempDirs($operation->user_id);
+			}
+
+			return [
+				'success' => true,
+				'result' => 'Temporary files cleaned up successfully'
+			];
+
+		} catch (Exception $e) {
+			JVB()->error()->log(
+				'[UploadRoutes]:processTemporaryCleanup',
 				$e->getMessage(),
 				[
 					'operation_id' => $operation->id,
@@ -465,17 +717,275 @@
 				]
 			);
 
-			return new WP_Error(
-				'metadata_processing_failed',
+			return [
+				'success' => false,
+				'result' => $e->getMessage()
+			];
+		}
+	}
+
+	/**
+	 * Handle metadata update requests
+	 */
+	public function handleMetadataUpdate(WP_REST_Request $request): WP_REST_Response
+	{
+		try {
+			$data = $request->get_params();
+
+			// Validate user permissions
+			if (!$this->userCheck($data['user'])) {
+				return $this->sendResponse(
+					false,
+					['error_code' => 'invalid_user'],
+					'Invalid user specified'
+				);
+			}
+
+			$items = $data['items']??false;
+			if (!$items) {
+				return $this->sendResponse(
+					true,
+					[
+					],
+					'No items to update'
+				);
+			}
+			$pending = [];
+			$attachments = array_filter($items, function ($item) {
+				return is_numeric($item);
+			}, ARRAY_FILTER_USE_KEY);
+			if (count($attachments) !== count($items)) {
+				$pending = array_filter($items, function ($item) {
+					return !is_numeric($item);
+				}, ARRAY_FILTER_USE_KEY);
+			}
+
+
+			if (!empty($attachments)) {
+				// Phase 2B: Direct attachment update (images already processed)
+				return $this->updateMeta($attachments, $data['user']);
+			}
+			elseif (!empty($pending)) {
+				// Phase 2A: Queue metadata update with dependency on upload operation
+				return $this->queueMetaUpdate($pending, $data['user']);
+			}
+
+			return $this->sendResponse(
+				false,
+				['error_code' => 'missing_identifiers'],
+				'Must provide either attachment_ids or operation_id'
+			);
+
+		} catch (Exception $e) {
+			JVB()->error()->log(
+				'[UploadRoutes]:handleMetadataUpdate',
+				$e->getMessage(),
+				[
+					'request_data' => $request->get_params(),
+					'trace' => $e->getTraceAsString()
+				]
+			);
+
+			return $this->sendResponse(
+				false,
+				['error_code' => 'metadata_update_failed'],
+				'Metadata update failed: ' . $e->getMessage()
+			);
+		}
+	}
+	/**
+	 * Update metadata directly on completed attachments
+	 */
+	protected function updateMeta(array $data, int $user): WP_REST_Response
+	{
+		$updated_count = 0;
+		$errors = [];
+
+		foreach ($data as $attachment_id => $info) {
+			try {
+				// Verify attachment exists and user has permission
+				if (!$this->verifyAttachmentAccess($attachment_id, $user)) {
+					$errors[] = "No permission to edit attachment {$attachment_id}";
+					continue;
+				}
+
+				$this->applyMeta($attachment_id, $info);
+				$updated_count++;
+
+			} catch (Exception $e) {
+				$errors[] = "Failed to update attachment {$attachment_id}: " . $e->getMessage();
+			}
+		}
+
+		return $this->sendResponse(
+			$updated_count > 0,
+			[
+				'updated_count' => $updated_count,
+				'errors' => $errors,
+				'attachment_ids' => $data['attachment_ids']
+			],
+			$updated_count > 0 ?
+				"Updated metadata for {$updated_count} attachment(s)" :
+				'No attachments were updated'
+		);
+	}
+	/**
+	 * Queue metadata update with dependency on upload operation
+	 */
+	protected function queueMetaUpdate(array $data, int $user): WP_REST_Response
+	{
+		$queue = JVB()->queue();
+		$depends_on = [];
+		$errors = [];
+		$original = count($data);
+		foreach ($data as $uploadID => $info) {
+			if (!array_key_exists('depends_on', $info)) {
+				unset($data[$uploadID]);
+				$errors[$uploadID] = $info;
+				continue;
+			}
+			if (!in_array($info['depends_on'], $depends_on)) {
+				$depends_on[] = $info['depends_on'];
+			}
+		}
+		$operationID = $queue->queueOperation(
+			'update_metadata',
+			$user,
+			$data,
+			[
+				'depends_on' => $depends_on,
+				'priority' => 'medium',
+			]
+		);
+
+		return $this->sendResponse(
+			true,
+			[
+				'operation_id' => $operationID,
+				'message'		=> "Successfully queued ".count($data)." of {$original} meta updates"
+			],
+			'Metadata update queued - will apply after upload completes'
+		);
+	}
+
+	/**
+	 * Process metadata update operation (called by queue processor)
+	 */
+	public function processUploadMeta(WP_Error|array $result, object $operation, array $data): WP_Error|array
+	{
+		try {
+			if (!is_array($operation->depends_on)) {
+				$operation->depends_on = [$operation->depends_on];
+			}
+			$updated_count = 0;
+			$errors = [];
+			foreach ($operation->depends_on as $dependency) {
+				$operationData = JVB()->queue()->getOperation($dependency);
+				if (!$operationData || $operationData->status !== 'completed') {
+					throw new Exception('Original upload operation not found or not completed');
+				}
+
+				$uploadResults = json_decode($operationData->result, true);
+				if (!$uploadResults || !$uploadResults['success']) {
+					throw new Exception('Original upload operation failed');
+				}
+				$attachmentsToUpdate = array_filter($data, function ($item) use ($dependency) {
+					return $item['depends_on'] === $dependency;
+				});
+
+				$uploadIDToAttachment = $this->buildUploadToAttachmentMapping(
+					array_keys($attachmentsToUpdate),
+					$uploadResults['data']
+				);
+
+				if (empty($uploadIDToAttachment)) {
+					throw new Exception('No valid upload ID to attachment ID mappings found');
+				}
+
+				foreach ($uploadIDToAttachment as $uploadId => $attachmentId) {
+					try {
+						$this->applyMeta($attachmentId, $attachmentsToUpdate[$uploadId]);
+						$updated_count++;
+					} catch (Exception $e) {
+						$errors[] = "Upload {$uploadId} (Attachment {$attachmentId}): " . $e->getMessage();
+					}
+				}
+			}
+
+			return [
+				'success' => $updated_count > 0,
+				'result'	=> [
+					'updated_count' => $updated_count,
+					'mappings' => $uploadIDToAttachment,
+					'errors' => $errors,
+					'message' => "Applied metadata to {$updated_count} attachment(s)"
+				]
+			];
+
+		} catch (Exception $e) {
+			JVB()->error()->log(
+				'[UploadRoutes]:processUploadMeta',
 				$e->getMessage(),
 				[
 					'operation_id' => $operation->id,
-					'error_code' => 'metadata_update_error'
+					'original_operation_id' => $data['original_operation_id'] ?? 'unknown'
 				]
 			);
+
+			return [
+				'success'	=> false,
+				'result'	=> $e->getMessage()
+			];
 		}
 	}
 
+	protected function applyMeta(int $attachment_id, array $metadata): void
+	{
+		// Update alt text
+		if (!empty($metadata['alt'])) {
+			update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($metadata['alt']));
+		}
+		$postUpdates = [];
+		// Update title
+		if (!empty($metadata['title'])) {
+			$postUpdates['post_title'] = $metadata['title'];
+		}
+
+		// Update caption
+		if (!empty($metadata['caption'])) {
+			$postUpdates['post_excerpt'] = sanitize_textarea_field($metadata['caption']);
+		}
+
+		if (!empty($postUpdates)) {
+			$postUpdates['ID'] = $attachment_id;
+			wp_update_post($postUpdates);
+		}
+	}
+
+	/**
+	 * Build mapping from frontend upload IDs to WordPress attachment IDs
+	 */
+	protected function buildUploadToAttachmentMapping(array $upload_ids, array $results): array
+	{
+		$mapping = [];
+
+		foreach ($results as $result) {
+			if (!isset($result['upload_id']) || !isset($result['attachment_id'])) {
+				continue;
+			}
+
+			$upload_id = $result['upload_id'];
+			$attachment_id = $result['attachment_id'];
+
+			// Validate that this upload_id was requested
+			if (in_array($upload_id, $upload_ids)) {
+				$mapping[$upload_id] = $attachment_id;
+			}
+		}
+
+		return $mapping;
+	}
+
 	/**
 	 * Verify user has access to attachment
 	 */
@@ -491,177 +1001,58 @@
 		return ($attachment->post_author == $user_id) || current_user_can('manage_options');
 	}
 
-	protected function processImageUpload(WP_Error|array $result, object $operation, array $data): WP_Error|array
-	{
-		try {
-			$uploader = new UploadManager($data['content'], $operation->user_id);
-			$processed_results = [];
-
-			// Extract frontend metadata if available
-			$frontend_metadata = $this->extractFrontendMetadata($data);
-
-
-			foreach ($data['secured_files'] as $index => $secured_file) {
-				$result = $uploader->processImageFromStorage(
-					$secured_file['temp_path'],
-					$data['content'],
-					(int)($data['post_id'] ?? 0),
-					(int)($data['term_id'] ?? 0),
-					$secured_file['original_name'] ?? ''
-				);
-
-				if (!is_wp_error($result)) {
-					$standardized = $this->standardizeImageResult($result);
-
-					// FIX: Proper mapping using consistent naming
-					if (isset($data['upload_map'][$index])) {
-						$standardized['upload_id'] = $data['upload_map'][$index];
-					} else {
-						error_log("Warning: No upload_map found for index {$index}");
-						$standardized['upload_id'] = 'unknown_' . $index;
-					}
-
-					$file_metadata = $frontend_metadata[$index] ?? null;
-					if ($file_metadata) {
-						$this->applyFrontendMetadata($standardized['attachment_id'], $file_metadata);
-					}
-
-					$processed_results[] = $standardized;
-				}
-			}
-
-			// Update field metadata if specified
-			if ($data['field_name']) {
-				$this->updateFieldMetadata($data, $processed_results);
-			}
-
-			return [
-				'success' => true,
-				'result' => $processed_results
-			];
-
-		} catch (Exception $e) {
-			JVB()->error()->log(
-				'[UploadRoutes]:processImageUpload',
-				$e->getMessage(),
-				[
-					'operation_id' => $operation->id,
-					'user_id' => $operation->user_id,
-					'content' => $data['content'],
-					'upload_map' => $data['upload_map'] ?? 'missing'
-				]
-			);
-
-			return [
-				'success'	=> false,
-				'result'	=> $e->getMessage()
-			];
-		}
-	}
-	protected function extractFrontendMetadata(array $data): array
-	{
-		$metadata = [];
-
-		// Check if we have metadata in the request
-		if (isset($_POST['metadata'])) {
-			foreach ($_POST['metadata'] as $index => $meta_json) {
-				$decoded = json_decode($meta_json, true);
-				if ($decoded) {
-					$metadata[$index] = $decoded;
-				}
-			}
-		}
-
-		return $metadata;
-	}
-
-	protected function applyFrontendMetadata(int $attachment_id, array $metadata): void
-	{
-		// Update alt text
-		if (!empty($metadata['alt'])) {
-			update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($metadata['alt']));
-		}
-
-		// Update title
-		if (!empty($metadata['title'])) {
-			wp_update_post([
-				'ID' => $attachment_id,
-				'post_title' => sanitize_text_field($metadata['title'])
-			]);
-		}
-
-		// Update caption
-		if (!empty($metadata['caption'])) {
-			wp_update_post([
-				'ID' => $attachment_id,
-				'post_excerpt' => sanitize_textarea_field($metadata['caption'])
-			]);
-		}
-	}
-	protected function countProcessedFiles(array $processed_results): int
-	{
-		$count = 0;
-		foreach ($processed_results as $result) {
-			if (is_array($result) && isset($result[0])) {
-				// Grouped results
-				$count += count($result);
-			} else {
-				// Single result
-				$count++;
-			}
-		}
-		return $count;
-	}
-	protected function updateFieldMetadata(array $data, array $processed_results): void
-	{
-		if (!$data['field_name']) return;
-
-		$type = ($data['post_id']) ? 'post' : (($data['term_id']) ? 'term' : false);
-		if (!$type) return;
-
-		$ID = ($type == 'post') ? $data['post_id'] : $data['term_id'];
-		$meta = new MetaManager($ID, $type);
-
-		// Get attachment IDs from processed results
-		$attachment_ids = [];
-		if ($data['mode'] == 'selection') {
-			// Selection mode: grouped results
-			foreach ($processed_results as $group => $files) {
-				foreach ($files as $file_result) {
-					$attachment_ids[] = $file_result['attachment_id'];
-				}
-			}
-		} else {
-			// Direct mode: flat array
-			foreach ($processed_results as $file_result) {
-				$attachment_ids[] = $file_result['attachment_id'];
-			}
-		}
-
-		if (!empty($attachment_ids)) {
-			$value = implode(',', array_map('absint', $attachment_ids));
-			$meta->updateValue($data['field_name'], $value);
-		}
-	}
-
 	/**
-	 * Standardize image processing results for frontend
+	 * Get field configuration for upload processing
 	 */
-	protected function standardizeImageResult(array $result): array
+	protected function getFieldConfig(array $data): array
 	{
-		return [
-			'attachment_id' => $result['attachment_id'],
-			'url' => $result['url'],
-			'file_path' => $result['file'] ?? '',
-			'success' => $result['success'] ?? true,
-			'metadata' => [
-				'alt_text' => get_post_meta($result['attachment_id'], '_wp_attachment_image_alt', true),
-				'title' => get_the_title($result['attachment_id']),
-				'mime_type' => get_post_mime_type($result['attachment_id'])
-			]
+		static $config_cache = [];
+
+		$cache_key = md5(json_encode([
+			$args['content'] ?? '',
+			$args['field_name'] ?? ''
+		]));
+
+		if (isset($config_cache[$cache_key])) {
+			return $config_cache[$cache_key];
+		}
+
+		$config = [
+			'allowed_types' => null,
+			'max_size' => null,
+			'convert' => 'webp',
+			'quality' => 80,
+			'create_thumbnails' => true,
 		];
+
+		// Get field definition from registry if available
+		if (!empty($args['content']) && !empty($args['field_name'])) {
+			$content_type = $args['content'];
+			$field_name = $args['field_name'];
+
+			if (array_key_exists($content_type, JVB_CONTENT)) {
+				$content_fields = JVB_CONTENT[$content_type]['fields'] ?? [];
+				if (array_key_exists($field_name, $content_fields)) {
+					$field_def = $content_fields[$field_name];
+
+					// Extract relevant config from field definition
+					$config = array_merge($config, [
+						'allowed_types' => $field_def['accepted_types'] ?? null,
+						'max_size' => $field_def['max_size'] ?? null,
+						'convert' => $field_def['convert'] ?? 'webp',
+						'quality' => $field_def['quality'] ?? 80,
+						'create_thumbnails' => $field_def['create_thumbnails'] ?? true,
+					]);
+				}
+			}
+		}
+
+		$config_cache[$cache_key] = $config;
+		return $config;
 	}
 
+
+
 	/**
 	 * Get files info for error logging
 	 */
@@ -674,164 +1065,7 @@
 		];
 	}
 
-    /**
-     * @param WP_Error $result
-     * @param object $operation
-     * @param array $data
-     *
-     * @return array|WP_Error
-     */
-    public function processTemporaryCleanup(WP_Error|array $result, object $operation, array $data):WP_Error|array
-    {
-		error_log('Processing Temporary Cleanup...');
-        $JVB = JVB();
-        $logger = $JVB->error();
-
-		$uploader = new UploadManager($data['content'], $data['user']);
-
-        // Check if we have files to clean up
-        if (empty($data['files']) || !is_array($data['files'])) {
-            return ['success' => true, 'result' => 'No files to clean up'];
-        }
-
-        $success_count = 0;
-        $error_count = 0;
-        $skipped_count = 0;
-
-        foreach ($data['files'] as $file_data) {
-            // Skip if we don't have a temp path
-            if (empty($file_data['temp_path'])) {
-                $skipped_count++;
-                continue;
-            }
-
-            $temp_path = $file_data['temp_path'];
-
-            try {
-                // Only remove if the file exists
-                if (file_exists($temp_path)) {
-                    if (@unlink($temp_path)) {
-                        $success_count++;
-                    } else {
-                        $error_count++;
-                        $logger->log(
-                            'temp_cleanup',
-                            'Failed to delete temporary file',
-                            ['file' => $temp_path],
-                            'warning'
-                        );
-                    }
-                } else {
-                    // File already gone, count as success
-                    $skipped_count++;
-                }
-            } catch (Exception $e) {
-                $error_count++;
-                $logger->log(
-                    '[UploadManager]:processTempCleanupOperation',
-                    'Error during temp file cleanup: ' . $e->getMessage(),
-                    ['file' => $temp_path],
-                    'warning'
-                );
-            }
-        }
-
-        // After cleaning individual files, check if directory is empty and can be removed
-        $uploader->cleanupEmptyTempDirs($operation->user_id);
-
-        return [
-            'success' => true,
-			'result' => [
-				'removed' => $success_count,
-				'errors' => $error_count,
-				'skipped' => $skipped_count,
-				'message' => "Temporary files cleanup completed: {$success_count} removed, {$error_count} errors, {$skipped_count} skipped"
-			]
-        ];
-    }
-
-	protected function attachUploadToContent($result, $operation, $args):array|false
-	{
-		error_log('STARTING ATTACHING UPLOAD');
-		error_log('Upload ID: '.print_r($args['upload'], true));
-		$uploadOperation = JVB()->queue()->getOperation($args['upload'], true);
-		error_log('Upload Operation from Attach Image to Content: '.print_r($uploadOperation, true));
-		error_log('Args: '.print_r($args, true));
-
-
-		$field = ($args['field_name']??false) ? $args['field_name'] : 'post_thumbnail';
-		$ID = $args['post_id']??$args['term_id'];
-		$return = [
-			'success'	=> false,
-			'results'	=> []
-		];
-		$images = array_map('absint', array_column(json_decode($uploadOperation->result, true),'attachment_id'));
-		error_log('Images: '.print_r($images, true));
-		error_log('Parsed ID: '.print_r($ID, true));
-		if (array_key_exists('content', $args) && $args['content'] === 'options') {
-			$meta = new MetaManager(null, 'options');
-			if (str_contains($args['field_name'], ':')) {
-				$result = $meta->updateRepeaterRowField($args['field_name'], (count($images) === 1) ? $images[0] : $images);
-			} else {
-				$result = $meta->updateValue($args['field_name'], (count($images) === 1) ? $images[0] : $images);
-			}
-			$return = [
-				'success'	=> $result,
-			];
-		} elseif (str_starts_with($field, 'new_') && !$ID) {
-			//Create post
-			$success = [
-				'created'	=> [],
-				'errors'	=> []
-			];
-			$i = 1;
-			foreach ($images as $img) {
-				$ID = wp_insert_post([
-					'post_type'		=> jvbCheckBase($args['content']),
-					'post_author'	=> $operation->user_id,
-					'post_title'	=> 'New '.JVB_CONTENT[$args['content']]['singular'].' '.$i,
-					'post_content'	=> ' ',
-					'post_excerpt'	=> ' '
-				], true);
-
-				error_log('Created Post: '.print_r($ID, true));
-
-				if ($ID && !is_wp_error($ID)) {
-					$success['created'][] = $ID;
-					set_post_thumbnail($ID, $img);
-				}else {
-					$success['errors'][] = $ID;
-				}
-				$i++;
-			}
-			$return = [
-				'success'	=> true,
-				'result'	=> $success
-			];
-		} else {
-			if ($args['post_id']) {
-				$type = 'post';
-			} elseif ($args['term_id']) {
-				$type = 'term';
-			}
-			error_log('ID: '.print_r($ID, true));
-			error_log('Type: '.print_r($type, true));
-			$meta = new MetaManager($ID, $type);
-
-			$images = implode(',', $images);
-			error_log('Processed Image IDs: '.$images);
-			$success = $meta->updateValue($field, $images);
-
-			$return = [
-				'success'	=> $success,
-				'result'	=> 'Field '.$field.' updated successfully'
-			];
-		}
-
-		return $return;
-	}
-
-	protected function createStandardResponse(bool $success, array $data = [], string $message = '', string $operation_id = ''): WP_REST_Response
+	protected function sendResponse(bool $success, array $data = [], string $message = '', string $operation_id = ''): WP_REST_Response
 	{
 		$response = [
 			'success' => $success,
@@ -847,348 +1081,83 @@
 		return new WP_REST_Response($response);
 	}
 
-	/**
-	 * Handle metadata update requests
-	 */
-	public function handleMetadataUpdate(WP_REST_Request $request): WP_REST_Response
-	{
-		try {
-			$data = $request->get_params();
 
-			// Validate user permissions
-			if (!$this->userCheck($data['user'])) {
-				return $this->createStandardResponse(
-					false,
-					['error_code' => 'invalid_user'],
-					'Invalid user specified'
-				);
-			}
 
-			if (!empty($data['attachment_ids'])) {
-				// Phase 2B: Direct attachment update (images already processed)
-				return $this->updateAttachmentMetadataDirect($data);
-			}
-			elseif (!empty($data['operation_id'])) {
-				// Phase 2A: Queue metadata update with dependency on upload operation
-				return $this->queueMetadataUpdate($data);
-			}
 
-			return $this->createStandardResponse(
-				false,
-				['error_code' => 'missing_identifiers'],
-				'Must provide either attachment_ids or operation_id'
-			);
-
-		} catch (Exception $e) {
-			JVB()->error()->log(
-				'[UploadRoutes]:handleMetadataUpdate',
-				$e->getMessage(),
-				[
-					'request_data' => $request->get_params(),
-					'trace' => $e->getTraceAsString()
-				]
-			);
-
-			return $this->createStandardResponse(
-				false,
-				['error_code' => 'metadata_update_failed'],
-				'Metadata update failed: ' . $e->getMessage()
-			);
-		}
-	}
-	/**
-	 * Update metadata directly on completed attachments
-	 */
-	protected function updateAttachmentMetadataDirect(array $data): WP_REST_Response
-	{
-		$updated_count = 0;
-		$errors = [];
-
-		foreach ($data['attachment_ids'] as $attachment_id) {
-			try {
-				// Verify attachment exists and user has permission
-				if (!$this->canUserEditAttachment($data['user'], $attachment_id)) {
-					$errors[] = "No permission to edit attachment {$attachment_id}";
-					continue;
-				}
-
-				$this->applyMetadataToAttachment($attachment_id, $data['metadata']);
-				$updated_count++;
-
-			} catch (Exception $e) {
-				$errors[] = "Failed to update attachment {$attachment_id}: " . $e->getMessage();
-			}
-		}
-
-		return $this->createStandardResponse(
-			$updated_count > 0,
-			[
-				'updated_count' => $updated_count,
-				'errors' => $errors,
-				'attachment_ids' => $data['attachment_ids']
-			],
-			$updated_count > 0 ?
-				"Updated metadata for {$updated_count} attachment(s)" :
-				'No attachments were updated'
-		);
-	}
-	/**
-	 * Queue metadata update with dependency on upload operation
-	 */
-	protected function queueMetadataUpdate(array $data): WP_REST_Response
-	{
-		$queue = JVB()->queue();
-
-		// Generate unique operation ID for this metadata update
-		$metadata_operation_id = 'meta_' . uniqid();
-
-		$queue->queueOperation(
-			'update_metadata',
-			$data['user'],
-			[
-				'original_operation_id' => $data['operation_id'],
-				'upload_ids' => $data['upload_ids'] ?? [],
-				'metadata' => $data['metadata']
-			],
-			[
-				'operation_id' => $metadata_operation_id,
-				'depends_on' => $data['operation_id'], // Wait for upload completion
-				'priority' => 'medium',
-				'notification' => false // Don't spam notifications for metadata updates
-			]
-		);
-
-		return $this->createStandardResponse(
-			true,
-			[
-				'metadata_operation_id' => $metadata_operation_id,
-				'depends_on' => $data['operation_id'],
-				'upload_ids' => $data['upload_ids'] ?? []
-			],
-			'Metadata update queued - will apply after upload completes'
-		);
-	}
-
-	/**
-	 * Process metadata update operation (called by queue processor)
-	 */
-	public function processMetadataUpdate(WP_Error|array $result, object $operation, array $data): WP_Error|array
-	{
-		try {
-			// Get the completed upload operation to map upload IDs to attachment IDs
-			$uploadOperation = JVB()->queue()->getOperation($data['original_operation_id']);
-
-			if (!$uploadOperation || $uploadOperation->status !== 'completed') {
-				throw new Exception('Original upload operation not found or not completed');
-			}
-
-			$uploadResults = json_decode($uploadOperation->result, true);
-			if (!$uploadResults || !$uploadResults['success']) {
-				throw new Exception('Original upload operation failed');
-			}
-
-			// Build mapping from upload IDs to attachment IDs
-			$uploadIdToAttachment = $this->buildUploadToAttachmentMapping(
-				$data['upload_ids'],
-				$uploadResults['data']
-			);
-
-			if (empty($uploadIdToAttachment)) {
-				throw new Exception('No valid upload ID to attachment ID mappings found');
-			}
-
-			// Apply metadata to each mapped attachment
-			$updated_count = 0;
-			$errors = [];
-
-			foreach ($uploadIdToAttachment as $uploadId => $attachmentId) {
-				try {
-					$this->applyMetadataToAttachment($attachmentId, $data['metadata']);
-					$updated_count++;
-				} catch (Exception $e) {
-					$errors[] = "Upload {$uploadId} (Attachment {$attachmentId}): " . $e->getMessage();
-				}
-			}
-
-			return [
-				'success' => $updated_count > 0,
-				'result'	=> [
-					'updated_count' => $updated_count,
-					'mappings' => $uploadIdToAttachment,
-					'errors' => $errors,
-					'message' => "Applied metadata to {$updated_count} attachment(s)"
-				]
-			];
-
-		} catch (Exception $e) {
-			JVB()->error()->log(
-				'[UploadRoutes]:processMetadataUpdate',
-				$e->getMessage(),
-				[
-					'operation_id' => $operation->id,
-					'original_operation_id' => $data['original_operation_id'] ?? 'unknown'
-				]
-			);
-
-			return [
-				'success'	=> false,
-				'result'	=> $e->getMessage()
-			];
-		}
-	}
-
-	/**
-	 * Build mapping from frontend upload IDs to WordPress attachment IDs
-	 */
-	protected function buildUploadToAttachmentMapping(array $uploadIds, array $uploadResults): array
-	{
-		$mapping = [];
-
-		// Handle both flat array and grouped results
-		if (isset($uploadResults[0]) && is_array($uploadResults[0]) && isset($uploadResults[0]['attachment_id'])) {
-			// Flat array of results
-			foreach ($uploadResults as $index => $result) {
-				if (isset($uploadIds[$index]) && isset($result['attachment_id'])) {
-					$mapping[$uploadIds[$index]] = $result['attachment_id'];
-				}
-			}
-		} else {
-			// Grouped results (selection mode)
-			$resultIndex = 0;
-			foreach ($uploadResults as $group) {
-				if (is_array($group)) {
-					foreach ($group as $result) {
-						if (isset($uploadIds[$resultIndex]) && isset($result['attachment_id'])) {
-							$mapping[$uploadIds[$resultIndex]] = $result['attachment_id'];
-						}
-						$resultIndex++;
-					}
-				}
-			}
-		}
-
-		return $mapping;
-	}
-
-	/**
-	 * Apply metadata to a WordPress attachment
-	 */
-	protected function applyMetadataToAttachment(int $attachment_id, array $metadata): void
-	{
-		$updates = [];
-
-		// Handle title update
-		if (isset($metadata['title']) && !empty($metadata['title'])) {
-			$updates['post_title'] = sanitize_text_field($metadata['title']);
-		}
-
-		// Handle caption update
-		if (isset($metadata['caption'])) {
-			$updates['post_excerpt'] = sanitize_textarea_field($metadata['caption']);
-		}
-
-		// Update post if we have title or caption changes
-		if (!empty($updates)) {
-			$updates['ID'] = $attachment_id;
-			$result = wp_update_post($updates);
-
-			if (is_wp_error($result)) {
-				throw new Exception('Failed to update attachment post: ' . $result->get_error_message());
-			}
-		}
-
-		// Handle alt text update (stored as post meta)
-		if (isset($metadata['alt'])) {
-			$alt_result = update_post_meta(
-				$attachment_id,
-				'_wp_attachment_image_alt',
-				sanitize_text_field($metadata['alt'])
-			);
-
-			if ($alt_result === false) {
-				throw new Exception('Failed to update alt text meta');
-			}
-		}
-
-		// Handle any custom metadata fields
-		if (isset($metadata['custom']) && is_array($metadata['custom'])) {
-			foreach ($metadata['custom'] as $key => $value) {
-				$meta_key = 'jvb_' . sanitize_key($key);
-				update_post_meta($attachment_id, $meta_key, sanitize_text_field($value));
-			}
-		}
-
-		// Clear any attachment caches
-		clean_attachment_cache($attachment_id);
-	}
-
-	/**
-	 * Check if user can edit attachment
-	 */
-	protected function canUserEditAttachment(int $user_id, int $attachment_id): bool
-	{
-		// Basic permission check - attachment must exist
-		$attachment = get_post($attachment_id);
-		if (!$attachment || $attachment->post_type !== 'attachment') {
-			return false;
-		}
-
-		// User must be the author or have edit capabilities
-		if ($attachment->post_author == $user_id) {
-			return true;
-		}
-
-		// Check if user has edit_posts capability
-		$user = get_user_by('id', $user_id);
-		return $user && user_can($user, 'skip_moderation');
-	}
 
 	public function handleGroupingRequest(WP_REST_Request $request): WP_REST_Response
 	{
-		error_log('Handling Group Request from UploadRoutes.php');
 		try {
+			$files = $request->get_file_params();
+			$args = $this->buildUploadArgs($request);
 			$data = $request->get_params();
-			error_log('Processing Data: '.print_r($data, true));
 
-			if (!array_key_exists($data['content'], JVB_CONTENT)) {
-				return $this->createStandardResponse(
+			if (!$args['content'] || !$args['user'] || !$args['posts']) {
+
+				$this->logError('Missing required data');
+				return new WP_REST_Response([
+					'success'	=> false,
+					'message'	=> 'Missing required data'
+				]);
+			}
+
+			// Secure files to temporary storage
+			$secured_files = $this->secureFiles($files, $args);
+
+			if (empty($secured_files['files'])) {
+				return $this->sendResponse(
 					false,
-					['error_code' => 'invalid_content'],
-					'Invalid content type specified'
+					['error_code' => 'no_files'],
+					'No valid files to upload'
 				);
 			}
 
-
-			//Get the operationIDs of the image uploads for the depends on
-			$operationIDs = [];
-			foreach ($data['posts'] as $index => $post) {
-				foreach ($post['images'] as $img) {
-					if (array_key_exists('operationId', $img)) {
-						$operationIDs[] = $img['operationId'];
-					}
-				}
+			// Queue file upload operation
+			$operation_type = $this->determineOperationType($secured_data['files'][0] ?? []);
+			$chunkSize = 5;
+			if ($operation_type === 'video') {
+				$chunkSize = 1;
+			} elseif ($operation_type === 'document') {
+				$chunkSize = 10;
 			}
-			$operationIDs = array_unique($operationIDs);
 
-
-			$queue = JVB()->queue();
-			$queue->queueOperation(
-				'process_upload_groups',
-				$data['user'],
-				$data,
+			JVB()->queue()->queueOperation(
+				$operation_type,
+				$args['user'],
+				array_merge(
+					[
+						'secured_files' => $secured_files['files'],
+						'upload_map' => $secured_files['upload_map'],
+					],
+					$args
+				),
 				[
-					'operation_id'	=> $data['id'],
-					'depends_on'	=> $operationIDs
+					'operation_id' => $args['upload'],
+					'chunk_key' => 'secured_files',
+					'chunk_size' => $chunkSize
 				]
 			);
 
-			return $this->createStandardResponse(
+			JVB()->queue()->queueOperation(
+				'process_upload_groups',
+				$args['user'],
+				$args,
+				[
+					'operation_id' => $args['id'],
+					'depends_on' => [$args['upload']],
+					'priority' => 'high'
+				]
+			);
+
+			return $this->sendResponse(
 				true,
 				[
-					'operation_id' => $data['id'],
-					'count' => count($data['posts'] ?? [])
+					'operation_id' => $args['id'],
+					'upload_operation_id' => $args['upload'],
+					'post_count' => count($args['posts']),
+					'file_count' => count($secured_files['files'])
 				],
-				'Operation queued successfully'
+				'Files uploaded and posts queued for creation'
 			);
 
 		} catch (Exception $e) {
@@ -1201,7 +1170,7 @@
 				]
 			);
 
-			return $this->createStandardResponse(
+			return $this->sendResponse(
 				false,
 				['error_code' => 'grouping_failed'],
 				'Grouping operation failed: ' . $e->getMessage()
@@ -1212,86 +1181,99 @@
 	protected function processUploadGroups(WP_Error|array $result, object $operation, array $data): WP_Error|array
 	{
 		try {
-			error_log('Processing Upload Groups - Data: ' . print_r($data, true));
-
 			$queue = JVB()->queue();
 			$all_uploaded_images = [];
-			$used_upload_ids = [];
 
-			// Collect all uploaded images from dependencies
-			foreach(json_decode($operation->dependencies, true) as $operationID) {
-				$o = $queue->getOperation($operationID);
-				$r = json_decode($o->result, true);
+			// Get the upload operation ID from data
+			$dependencies = json_decode($operation->dependencies, true);
+			$uploads = [];
+			foreach($dependencies as $dependency) {
+				$op = $queue->getOperation($dependency);
 
-				error_log('Saved operation'.print_r($r, true));
-				if ($r['success'] && isset($r['data'])) {
-					foreach ($r['data'] as $img) {
-						$uploadKey = $img['upload_id'];
-						$all_uploaded_images[$uploadKey] = [
-							'upload_id' => $img['upload_id'],
-							'attachment_id' => (int)$img['attachment_id'],
-							'operation_id' => $operationID
-						];
-					}
+				$res = json_decode($op->result, true);
+				if ($op->status === 'completed') {
+					$uploads = array_merge($uploads, $res);
 				}
 			}
 
 
+			if (empty($uploads)) {
+				return [
+					'success'	=> false,
+					'result'	=> 'No uploads to process'
+				];
+			}
+
+			// Build map of upload_id => attachment_id
+			foreach ($uploads as $img) {
+				if (isset($img['upload_id'], $img['attachment_id'])) {
+					$all_uploaded_images[$img['upload_id']] = [
+						'upload_id' => $img['upload_id'],
+						'attachment_id' => (int)$img['attachment_id']
+					];
+				}
+			}
+
 			$content = jvbCheckBase($data['content']);
 			$user = (int)$data['user'];
 			$created_posts = [];
+			$used_upload_ids = [];
 
+			// Create posts from groups
 			foreach ($data['posts'] as $index => $post) {
+				$post_title = !empty($post['fields']['post_title'])
+					? sanitize_text_field($post['fields']['post_title'])
+					: 'New ' . JVB_CONTENT[$content]['singular'] . ' ' . ($index + 1);
+
+				$post_excerpt = !empty($post['fields']['post_excerpt'])
+					? sanitize_textarea_field($post['fields']['post_excerpt'])
+					: '';
+
 				$new_post_id = wp_insert_post([
 					'post_type' => $content,
 					'post_author' => $user,
 					'post_status' => 'draft',
-					'post_title' => array_key_exists('post_title', $post['fields'])
-						? sanitize_text_field($post['fields']['post_title'])
-						: 'New ' . JVB_CONTENT[$data['content']]['singular'],
-					'post_excerpt' => array_key_exists('post_excerpt', $post['fields'])
-						? sanitize_text_field($post['fields']['post_excerpt'])
-						: '',
+					'post_title' => $post_title,
+					'post_excerpt' => $post_excerpt,
 				]);
 
 				if ($new_post_id && !is_wp_error($new_post_id)) {
 					$created_posts[] = $new_post_id;
 
-					// Extract featured image
-					$featured_upload_id = array_key_exists('featured', $post['fields'])
-						? $post['fields']['featured']
-						: $post['images'][0]['upload_id'] ?? null;
-
+					// Get featured image upload_id
+					$featured_upload_id = (int)$post['fields']['featured'] ?? null;
 					$featured_attachment_id = null;
 					$gallery_attachment_ids = [];
 
 					// Process all images for this post
-					foreach ($post['images'] as $img) {
+					foreach ($post['images'] as $key => $img) {
 						$upload_id = $img['upload_id'];
 						$used_upload_ids[] = $upload_id;
 
 						if (isset($all_uploaded_images[$upload_id])) {
-							$attachment_id = $all_uploaded_images[$upload_id]['attachment_id'];
+							$attachment_id = (int)$all_uploaded_images[$upload_id]['attachment_id'];
 
 							if ($upload_id === $featured_upload_id) {
 								$featured_attachment_id = $attachment_id;
 							} else {
 								$gallery_attachment_ids[] = $attachment_id;
 							}
-						} else {
-							error_log("Warning: Could not find attachment for upload_id: {$upload_id}");
 						}
 					}
 
 					// Set featured image
 					if ($featured_attachment_id) {
 						set_post_thumbnail($new_post_id, (int)$featured_attachment_id);
+					} elseif (!empty($gallery_attachment_ids)) {
+						// If no featured set, use first image
+						set_post_thumbnail($new_post_id, (int)$gallery_attachment_ids[0]);
+						array_shift($gallery_attachment_ids);
 					}
 
 					// Set gallery images
 					if (!empty($gallery_attachment_ids)) {
 						$meta = new MetaManager($new_post_id, 'post');
-						$fields = jvbGetFields($data['content'], 'post');
+						$fields = jvbGetFields($content, 'post');
 
 						foreach ($fields as $name => $config) {
 							if ($config['type'] === 'gallery') {
@@ -1303,18 +1285,12 @@
 				}
 			}
 
-			// Cleanup unused uploaded images
-			$unused_images = array_diff_key($all_uploaded_images, array_flip($used_upload_ids));
-			$cleanup_result = $this->cleanupUnusedImages($unused_images);
-
 			return [
 				'success' => true,
-				'result'	=> [
+				'result' => [
 					'created_posts' => $created_posts,
 					'total_posts' => count($created_posts),
 					'used_images' => count($used_upload_ids),
-					'cleaned_up_images' => $cleanup_result['cleaned_count'],
-					'cleanup_errors' => $cleanup_result['errors'],
 					'message' => "Created " . count($created_posts) . " post(s) from uploads"
 				]
 			];
@@ -1325,8 +1301,7 @@
 				$e->getMessage(),
 				[
 					'operation_id' => $operation->id,
-					'user_id' => $operation->user_id,
-					'posts_count' => count($data['posts'] ?? [])
+					'user_id' => $operation->user_id
 				]
 			);
 
@@ -1353,7 +1328,6 @@
 
 					if ($deleted) {
 						$cleaned_count++;
-						error_log("Cleaned up unused image: attachment_id {$attachment_id}, upload_id {$upload_id}");
 					} else {
 						$errors[] = "Failed to delete attachment {$attachment_id} for upload {$upload_id}";
 					}
@@ -1375,9 +1349,6 @@
 
 	function getAttachmentID(array $image, array $storedResults): int|false
 	{
-		error_log('Looking for upload_id: ' . ($image['upload_id'] ?? 'NOT SET'));
-		error_log('Available results: ' . print_r(array_keys($storedResults), true));
-
 		foreach ($storedResults as $operationID => $uploads) {
 			foreach ($uploads as $upload) {
 				// FIX: Handle both case variations
@@ -1385,13 +1356,10 @@
 				$search_upload_id = $image['upload_id'];
 
 				if ($stored_upload_id === $search_upload_id) {
-					error_log("Found match! upload_id: {$search_upload_id} -> attachment_id: {$upload['attachment_id']}");
 					return (int)$upload['attachment_id'];
 				}
 			}
 		}
-
-		error_log("No match found for upload_id: " . ($image['upload_id'] ?? 'MISSING'));
 		return false;
 	}
 
@@ -1523,4 +1491,244 @@
 
 		return $title;
 	}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+	/**
+	 * Determine how to save uploaded files based on configuration
+	 */
+	protected function handleUploadDestination(array $data, array $results): void
+	{
+		// Determine destination from config
+		$destination = $data['destination'] ?? 'meta';
+
+		switch ($destination) {
+			case 'meta':
+				// Save to post/term/user meta
+				$this->saveToMeta($data, $results);
+				break;
+
+			case 'post':
+				// Create individual posts for each file
+				$this->createIndividualPosts($data, $results);
+				break;
+
+			case 'post_group':
+				// Create posts with grouped files
+				$this->createGroupedPosts($data, $results);
+				break;
+
+			default:
+				// No destination specified - files processed but not attached
+				break;
+		}
+	}
+
+	/**
+	 * Infer destination from existing data (backward compatibility)
+	 */
+	protected function inferDestination(array $data): string
+	{
+		// If field_name exists → saving to meta
+		if (!empty($data['field_name'])) {
+			return 'meta';
+		}
+
+		// If post_type exists without field_name → creating posts
+		if (!empty($data['content'])) {
+			return 'post';
+		}
+
+		// No destination
+		return 'none';
+	}
+
+	/**
+	 * Save attachment IDs to meta field
+	 */
+	protected function saveToMeta(array $data, array $results): void
+	{
+		if (empty($data['field_name'])) {
+			return;
+		}
+
+		$attachment_ids = array_column($results, 'attachment_id');
+
+		// Determine meta type
+		if (!empty($data['post_id'])) {
+			$meta = new MetaManager($data['post_id'], 'post');
+		} elseif (!empty($data['term_id'])) {
+			$meta = new MetaManager($data['term_id'], 'term');
+		} elseif (!empty($data['user'])) {
+			$link = (int)get_user_meta($data['user'], BASE.'link', true);
+			$meta = new MetaManager($link, 'post');
+		} else {
+			return;
+		}
+
+		// Get existing value
+		$existing = $meta->getValue($data['field_name']);
+		$existing_ids = !empty($existing) ? explode(',', $existing) : [];
+
+		// Merge with new IDs
+		$all_ids = array_unique(array_merge($existing_ids, $attachment_ids));
+
+		// Update with comma-separated string
+		$meta->updateValue($data['field_name'], implode(',', $all_ids));
+	}
+
+	/**
+	 * Create individual posts from uploads
+	 */
+	protected function createIndividualPosts(array $data, array $results): array
+	{
+		if (empty($data['content'])) {
+			return [];
+		}
+
+		$created_posts = [];
+
+		foreach ($results as $result) {
+			$attachment_id = $result['attachment_id'];
+			$attachment = get_post($attachment_id);
+
+			// Create post
+			$post_data = [
+				'post_type' => jvbCheckBase($data['content']),
+				'post_title' => $attachment->post_title,
+				'post_status' => 'draft',
+				'post_author' => $data['user'] ?? get_current_user_id(),
+			];
+
+			$post_id = wp_insert_post($post_data);
+
+			if (!is_wp_error($post_id)) {
+				// Set as featured image or attach to gallery
+				$this->attachFileToPost($post_id, $attachment_id, $data);
+
+				$created_posts[] = [
+					'post_id' => $post_id,
+					'attachment_id' => $attachment_id,
+				];
+			}
+		}
+
+		return $created_posts;
+	}
+
+	/**
+	 * Create posts with grouped uploads
+	 */
+	protected function createGroupedPosts(array $data, array $results): array
+	{
+		if (empty($data['content'])) {
+			return [];
+		}
+
+		$id_map = [];
+		foreach ($results as $result) {
+			if (isset($result['upload_id'], $result['attachment_id'])) {
+				$id_map[$result['upload_id']] = $result['attachment_id'];
+			}
+		}
+
+		// Groups come from frontend as metadata
+		$groups = $data['groups'] ?? [array_column($results, 'attachment_id')];
+		$created_posts = [];
+
+		foreach ($groups as $group_index => $group_upload_ids) {
+			$group_attachment_ids = array_filter(
+				array_map(fn($uid) => $id_map[$uid] ?? null, $group_upload_ids)
+			);
+
+			if (empty($group_attachment_ids)) continue;
+			// Create post for this group
+			$first_attachment = get_post($group_attachment_ids[0]);
+
+			$post_data = [
+				'post_type' => jvbCheckBase($data['content']),
+				'post_title' => $data['group_titles'][$group_index] ?? $first_attachment->post_title,
+				'post_status' => $data['post_status'] ?? 'draft',
+				'post_author' => $data['user'] ?? get_current_user_id(),
+			];
+
+			$post_id = wp_insert_post($post_data);
+
+			if (!is_wp_error($post_id)) {
+				// Attach all files in group
+				foreach ($group_attachment_ids as $index => $attachment_id) {
+					if ($index === 0) {
+						// First is featured
+						set_post_thumbnail($post_id, $attachment_id);
+					} else {
+						// Others go to gallery
+						$meta = new MetaManager($post_id, 'post');
+						$existing = $meta->getValue('gallery');
+						$existing_ids = !empty($existing) ? explode(',', $existing) : [];
+						$existing_ids[] = $attachment_id;
+						$meta->updateValue('gallery', implode(',', $existing_ids));
+					}
+				}
+
+				$created_posts[] = [
+					'post_id' => $post_id,
+					'attachment_ids' => $group_attachment_ids,
+				];
+			}
+		}
+
+		return $created_posts;
+	}
+
+	/**
+	 * Attach file to post based on file type
+	 */
+	protected function attachFileToPost(int $post_id, int $attachment_id, array $data): void
+	{
+		$attachment = get_post($attachment_id);
+		$mime_type = $attachment->post_mime_type;
+
+		// Determine file type
+		if (str_starts_with($mime_type, 'image/')) {
+			// Set as featured image
+			set_post_thumbnail($post_id, $attachment_id);
+		} elseif (str_starts_with($mime_type, 'video/')) {
+			// Save to video field
+			$meta = new MetaManager($post_id, 'post');
+			$meta->updateValue('video', $attachment_id);
+		} else {
+			// Documents - save to documents field
+			$meta = new MetaManager($post_id, 'post');
+			$existing = $meta->getValue('documents');
+			$existing_ids = !empty($existing) ? explode(',', $existing) : [];
+			$existing_ids[] = $attachment_id;
+			$meta->updateValue('documents', implode(',', $existing_ids));
+		}
+	}
 }

--
Gitblit v1.10.0