From a9b3b28d001941921aa70d37fdc87c758a163a44 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 05 Jun 2026 16:47:03 +0000
Subject: [PATCH] =Some hefty changes to FeedBlock. Transitioning to loading first page in php to save on extra requests. Got a bit to do yet, but I have to work on Northeh for a bit here.

---
 inc/rest/routes/UploadRoutes.php | 2381 ++++++++++++++++++++++++++---------------------------------
 1 files changed, 1,041 insertions(+), 1,340 deletions(-)

diff --git a/inc/rest/routes/UploadRoutes.php b/inc/rest/routes/UploadRoutes.php
index 3049d25..5222e4d 100644
--- a/inc/rest/routes/UploadRoutes.php
+++ b/inc/rest/routes/UploadRoutes.php
@@ -1,10 +1,15 @@
 <?php
 namespace JVBase\rest\routes;
 
-use JVBase\JVB;
-use JVBase\rest\RestRouteManager;
-use JVBase\meta\MetaManager;
+use JVBase\managers\queue\executors\UploadExecutor;
+use JVBase\managers\queue\mergers\UploadMerger;
+use JVBase\managers\queue\TypeConfig;
+use JVBase\registrar\Registrar;
+use JVBase\rest\PermissionHandler;
+use JVBase\rest\Rest;
+use JVBase\meta\Meta;
 use JVBase\managers\UploadManager;
+use JVBase\rest\Route;
 use WP_REST_Request;
 use WP_REST_Response;
 use WP_Error;
@@ -13,134 +18,257 @@
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
 }
-class UploadRoutes extends RestRouteManager
+class UploadRoutes extends Rest
 {
 
     public function __construct()
     {
-		$this->action = 'dash-';
         parent::__construct();
-        add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
+
+		add_action('init', [$this, 'registerUploadExecutors'], 5);
     }
 
+	/**
+	 * Register upload operation types with the queue's TypeRegistry
+	 */
+	public function registerUploadExecutors(): void
+	{
+		$registry = JVB()->queue()->registry();
+		$executor = new UploadExecutor();
+		$merger = new UploadMerger('secured_files');
+
+		// Image uploads - chunked at 5 files
+		$registry->register('image_upload', new TypeConfig(
+			mergeable: $merger,
+			executor: $executor,
+			chunkKey: 'secured_files',
+			chunkSize: 3
+		));
+
+		// Video uploads - one at a time (heavy processing)
+		$registry->register('video_upload', new TypeConfig(
+			mergeable: $merger,
+			executor: $executor,
+			chunkKey: 'secured_files',
+			chunkSize: 1
+		));
+
+		// Document uploads - chunked at 10
+		$registry->register('document_upload', new TypeConfig(
+			mergeable: $merger,
+			executor: $executor,
+			chunkKey: 'secured_files',
+			chunkSize: 5
+		));
+
+		// Metadata updates
+		$registry->register('update_image_meta', new TypeConfig(
+			executor: $executor
+		));
+
+		// Cleanup - chunked at 5
+		$registry->register('temporary_cleanup', new TypeConfig(
+			executor: $executor,
+			chunkKey: 'files',
+			chunkSize: 5
+		));
+
+		// Attach to content (depends on upload completing)
+		$registry->register('attach_upload_to_content', new TypeConfig(
+			executor: $executor
+		));
+
+		// Process upload groups into posts
+		$registry->register('process_upload_groups', new TypeConfig(
+			executor: $executor,
+			chunkKey: 'posts',
+			chunkSize: 5
+		));
+	}
+
     /**
      * Registers upload routes
      * @return void
      */
     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'
-                ]
-            ]
-        ]);
+		Route::for('uploads')
+			->post([$this, 'handleUpload'])
+			->auth(PermissionHandler::combine(['nonce']))
+			->rateLimit(30)
+			->register();
 
-		register_rest_route($this->namespace, '/uploads/groups', [
-			'methods' => 'POST',
-			'callback' => [$this, 'handleGroupingRequest'],
-			'permission_callback' => [$this, 'checkPermission'],
-			'args' => [
-				'id' => [
-					'required' => true,
-					'type' => 'string',
-					'description' => 'Original upload operation ID'
-				],
-				'content' => [
-					'required' => true,
-					'type' => 'string'
-				],
-				'user' => [
-					'required' => true,
-					'type' => 'integer'
-				],
-			]
-		]);
+		Route::for('uploads/groups')
+			->post([$this, 'handleGroupingRequest'])
+			->auth(PermissionHandler::combine(['nonce']))
+			->rateLimit(30)
+			->args([
+				'id' 		=> 'string|required',
+				'content' 	=> 'string|required',
+				'user'		=> 'int|required'
+			])
+			->register();
 
-		register_rest_route($this->namespace, '/uploads/meta', [
-			'methods' => 'POST',
-			'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
-				]
-			]
-		]);
+		Route::for('uploads/meta')
+			->post([$this, 'handleMetadataUpdate'])
+			->auth(PermissionHandler::combine(['nonce']))
+			->rateLimit(30)
+			->args([
+				'user'	=> 'int|required',
+				'items'	=> 'array|required',
+				'id'    => 'string'
+			])
+			->register();
     }
 
+	/**
+	 * 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 = [];
+		$registrar = Registrar::getInstance($data['content']??'');
 
-        if ((!$args['post_id'] && !$args['term_id']) && contentIsJVBUserType($args['content'])) {
-            $args['post_id'] = (int)get_user_meta($args['user'], BASE.'link', true);
-        }
+		foreach ($data as $key => $value) {
+			switch ($key) {
+				case 'depends_on':
+					if (is_string($value) && !empty($value)) {
+						$args['depends_on'] = sanitize_text_field($value);
+					}
+					break;
+				case 'item_id':
+					if (is_numeric($value)) {
+						$args['item_id'] = absint($value);
+						if ($registrar) {
+							switch ($registrar->getType()) {
+								case 'post':
+									$args['post_id'] = absint($value);
+									break;
+								case 'term':
+									$args['term_id'] = absint($value);
+									break;
+								case 'user':
+									$args['user_id'] = absint($value);
+									break;
+							}
+						}
+					}
+					break;
+				// Post Type/Taxonomy
+				case 'content':
+					$value = str_replace('-', '_', $value);
+					if ($value === 'options' || $registrar) {
+						$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', $args) &&
+							!array_key_exists('post_id', $data) &&
+							!array_key_exists('term_id', $data) &&
+							!array_key_exists('item_id', $data)) {
+							$args['post_id'] = (int)get_user_meta((int) $value, BASE.'profile_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 Meta.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;
 
-        $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 +280,32 @@
      *
      * @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['user']) {
+				 return $this->unauthorized();
+			}
+			if (!$args['content']) {
+				return $this->validationError(['message' => 'Missing content']);
 			}
 
-			$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 $this->error('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 $this->queued($operation_id, 'Files secured and queued for processing');
 
 		} catch (Exception $e) {
 			// Error handling...
@@ -269,195 +318,482 @@
 					'trace' => $e->getTraceAsString()
 				]
 			);
-
-			return $this->createStandardResponse(
-				false,
-				['error_code' => 'upload_failed'],
-				'Upload processing failed: ' . $e->getMessage()
-			);
+			return $this->error($e->getMessage());
 		}
 	}
 
-	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 = [];
+		$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]
-				];
+		$file_array = $files['files'] ?? $files;
+		if (!is_array($file_array['tmp_name'])) {
+			$file_array = [
+				'name' => [$file_array['name']],
+				'type' => [$file_array['type']],
+				'tmp_name' => [$file_array['tmp_name']],
+				'error' => [$file_array['error']],
+				'size' => [$file_array['size']]
+			];
+		}
+		$tmp_names = $file_array['tmp_name'] ?? [];
 
-				if ($file_data['error'] === UPLOAD_ERR_OK) {
-					$secured_files[] = $uploader->secureUploadedFile($file_data);
-				}
+
+
+		foreach ($tmp_names as $index => $tmp_name) {
+			$file_data = [
+				'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');
+				}
+
+				// Embed upload_id directly in the secured file data
+				$secured['upload_id'] = $args['upload_ids'][$index] ?? 'upload_' . $index;
+
+				$secured_files[] = $secured;
+			} catch (Exception $e) {
+				$errors[$index] = $e->getMessage();
+			}
+		}
+
+		return [
+			'files' => $secured_files,
+			'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;
+		}
+
+		error_log('Queueing Operation: '.print_r($operation_type, true));
+		error_log('With ID: '.print_r($args['upload'], true));
+		$queuedProcessing = JVB()->queue()->queueOperation(
+			$operation_type,
+			$args['user'],
+			array_merge(
+				['secured_files' => $secured_data['files']],
+				$args
+			),
+			[
+				'operation_id'  => $args['upload'],
+				'chunk_key'     => 'secured_files',
+				'chunk_size'    => $chunkSize
+			]
+		);
+
+		error_log('queuedProcessing operation: '.print_r($queuedProcessing, true));
+
+		$uploadOpId = $queuedProcessing['operation_id'];
+
+		if ($args['mode'] !== 'selection') {
+
+			// Only create attach_upload_to_content if the upload was NOT merged.
+			// When merged, the original upload's attach_upload_to_content
+			// will handle all files after the merged image_upload completes.
+			if (!$queuedProcessing['updated_existing']) {
+				JVB()->queue()->queueOperation(
+					'attach_upload_to_content',
+					$args['user'],
+					$args,
+					[
+						'priority'      => 'high',
+						'operation_id'  => $args['id'],
+						'depends_on'    => [$uploadOpId]
+					]
+				);
+			}
+		}
+
+		JVB()->queue()->queueOperation(
+			'temporary_cleanup',
+			$args['user'],
+			[
+				'files'     => $secured_data['files'],
+				'context'   => $args,
+			],
+			[
+				'priority'      => 'low',
+				'chunk_size'    => 5,
+				'chunk_key'     => 'files',
+				'depends_on'    => [$uploadOpId]
+			]
+		);
+
+		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;
+			if (empty($data['post_id']) || str_starts_with((string)($data['item_id'] ?? ''), 'new')) {
+				foreach ($operation->dependencies as $depId) {
+					$dep = JVB()->queue()->get($depId);
+					if ($dep && $dep->type === 'content_update' && !empty($dep->result['new_posts'])) {
+						$itemId = $data['item_id'] ?? null;
+						if ($itemId && isset($dep->result['new_posts'][$itemId])) {
+							$data['post_id'] = $dep->result['new_posts'][$itemId];
+							break;
+						}
 					}
-
-					// 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()
-					];
 				}
+
+				if (empty($data['post_id'])) {
+					throw new Exception('Could not resolve post_id from dependencies');
+				}
+			}
+
+			// 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 = Meta::forPost($data['post_id']);
+		} elseif (array_key_exists('term_id', $data)) {
+			$meta = Meta::forTerm($data['term_id']);
+		} else {
+			$link = (int)get_user_meta($data['user'], BASE.'profile_link');
+			$meta = Meta::forPost($link);
+		}
+
+		// Get existing value
+		$existing = $meta->get($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->set($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']);
+
+			foreach ($data['secured_files'] as $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'] ?? '',
+						]
+					)
+				);
+
+				if (!is_wp_error($result)) {
+					$standardized = $this->standardizeResult($result);
+
+					// Use embedded upload_id from the secured file itself
+					$standardized['upload_id'] = $secured_file['upload_id'] ?? null;
+
+					if ($standardized['upload_id']) {
+						$processed_results[$standardized['upload_id']] = $standardized;
+					} else {
+						$processed_results[] = $standardized;
+					}
+
+					// Apply frontend metadata if provided
+					if (!empty($secured_file['metadata'])) {
+						$this->applyMeta($standardized['attachment_id'], $secured_file['metadata']);
+					}
+				}
+			}
+
+			$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 +801,263 @@
 				]
 			);
 
-			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();
+
+			error_log('Received data for meta change: '.print_r($data, true));
+
+
+			$items = $data['items']??false;
+			if (!$items) {
+				return $this->sendResponse(
+					true,
+					[
+					],
+					'No items to update'
+				);
+			}
+			$pending = [];
+			$attachments = array_filter($items, function ($item) {
+				return array_key_exists('attachmentId', $item) || array_key_exists('uploadId', $item);
+			});
+
+
+			if (!empty($attachments)) {
+				error_log('Attachments: '.print_r($attachments, true));
+				return $this->queueMetaUpdate($attachments, absint($data['user']), sanitize_text_field($data['id']??''));
+			}
+
+
+			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 = [];
+		$ids = [];
+		foreach ($data as $info) {
+			try {
+				$attachment_id = $info['attachmentId'];
+				error_log('Updating attachment ID:'.print_r($attachment_id,true));
+				$ids[] = $attachment_id;
+				unset($info['attachmentId']);
+				// 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' => $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, ?string $operationId = null): WP_REST_Response
+	{
+		$queue = JVB()->queue();
+		$depends_on = [];
+		$errors = [];
+		$original = count($data);
+		foreach ($data as $uploadID => $info) {
+			if (array_key_exists('depends_on', $info) && !in_array($info['depends_on'], $depends_on)) {
+				$depends_on[] = $info['depends_on'];
+			}
+		}
+		$queueData = [
+			'depends_on' => $depends_on,
+		];
+		if ($operationId) {
+			$queueData['operation_id'] = $operationId;
+		}
+		$operationID = $queue->queueOperation(
+			'update_image_meta',
+			$user,
+			$data,
+			$queueData
+		);
+
+		return $this->sendResponse(
+			true,
+			[
+				'operation_id' => $operationID['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->state !== '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['image-alt-text'])) {
+			update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($metadata['image-alt-text']));
+		}
+		$postUpdates = [];
+		// Update title
+		if (!empty($metadata['image-title'])) {
+			$postUpdates['post_title'] = $metadata['image-title'];
+		}
+
+		// Update caption
+		if (!empty($metadata['image-caption'])) {
+			$postUpdates['post_excerpt'] = sanitize_textarea_field($metadata['image-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 +1073,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'];
+			$registrar = Registrar::getInstance($content_type);
+			if ($registrar) {
+				$content_fields = $registrar->getFields();
+				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 +1137,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,
@@ -844,353 +1150,68 @@
 			$response['operation_id'] = $operation_id;
 		}
 
-		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');
+		return $this->success($response);
 	}
 
 	public function handleGroupingRequest(WP_REST_Request $request): WP_REST_Response
 	{
-		error_log('Handling Group Request from UploadRoutes.php');
 		try {
-			$data = $request->get_params();
-			error_log('Processing Data: '.print_r($data, true));
+			$files = $request->get_file_params();
+			$args = $this->buildUploadArgs($request);
 
-			if (!array_key_exists($data['content'], JVB_CONTENT)) {
-				return $this->createStandardResponse(
-					false,
-					['error_code' => 'invalid_content'],
-					'Invalid content type specified'
-				);
+			if (!array_key_exists('user', $args) || $args['user'] === 0){
+				return $this->unauthorized();
+			}
+			if (!array_key_exists('content', $args) || empty($args['content'])) {
+				return $this->validationError(['message'=>'Missing required content']);
+			}
+			if (!array_key_exists('posts', $args) || empty($args['posts'])) {
+				return $this->validationError(['message' => 'Missing posts required']);
 			}
 
+			// Secure files to temporary storage
+			$secured_files = $this->secureFiles($files, $args);
 
-			//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'];
-					}
-				}
+			if (empty($secured_files['files'])) {
+				return $this->error('No valid files to upload');
 			}
-			$operationIDs = array_unique($operationIDs);
 
+			// Queue file upload operation
+			$operation_type = $this->determineOperationType($secured_files['files'][0] ?? []);
+			$chunkSize = 5;
+			if ($operation_type === 'video') {
+				$chunkSize = 1;
+			} elseif ($operation_type === 'document') {
+				$chunkSize = 10;
+			}
 
-			$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']],
+					$args
+				),
 				[
-					'operation_id'	=> $data['id'],
-					'depends_on'	=> $operationIDs
+					'operation_id' => $args['upload'],
+					'chunk_key' => 'secured_files',
+					'chunk_size' => $chunkSize
 				]
 			);
 
-			return $this->createStandardResponse(
-				true,
+			$ID = JVB()->queue()->queueOperation(
+				'process_upload_groups',
+				$args['user'],
+				$args,
 				[
-					'operation_id' => $data['id'],
-					'count' => count($data['posts'] ?? [])
-				],
-				'Operation queued successfully'
+					'operation_id' => $args['id'],
+					'depends_on' => [$args['upload']],
+					'priority' => 'high',
+					'chunk_key'	=> 'posts'
+				]
 			);
 
+			return $this->queued($ID['operation_id'], 'Files uploaded and posts queued for creation');
 		} catch (Exception $e) {
 			JVB()->error()->log(
 				'[UploadRoutes]:handleGroupingRequest',
@@ -1200,327 +1221,7 @@
 					'trace' => $e->getTraceAsString()
 				]
 			);
-
-			return $this->createStandardResponse(
-				false,
-				['error_code' => 'grouping_failed'],
-				'Grouping operation failed: ' . $e->getMessage()
-			);
+			return $this->error('Grouping operation failed: '.$e->getMessage());
 		}
 	}
-
-	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);
-
-				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
-						];
-					}
-				}
-			}
-
-
-			$content = jvbCheckBase($data['content']);
-			$user = (int)$data['user'];
-			$created_posts = [];
-
-			foreach ($data['posts'] as $index => $post) {
-				$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'])
-						: '',
-				]);
-
-				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;
-
-					$featured_attachment_id = null;
-					$gallery_attachment_ids = [];
-
-					// Process all images for this post
-					foreach ($post['images'] as $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'];
-
-							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);
-					}
-
-					// Set gallery images
-					if (!empty($gallery_attachment_ids)) {
-						$meta = new MetaManager($new_post_id, 'post');
-						$fields = jvbGetFields($data['content'], 'post');
-
-						foreach ($fields as $name => $config) {
-							if ($config['type'] === 'gallery') {
-								$meta->updateValue($name, implode(',', $gallery_attachment_ids));
-								break;
-							}
-						}
-					}
-				}
-			}
-
-			// 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'	=> [
-					'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"
-				]
-			];
-
-		} catch (Exception $e) {
-			JVB()->error()->log(
-				'[UploadRoutes]:processUploadGroups',
-				$e->getMessage(),
-				[
-					'operation_id' => $operation->id,
-					'user_id' => $operation->user_id,
-					'posts_count' => count($data['posts'] ?? [])
-				]
-			);
-
-			return [
-				'success' => false,
-				'result' => $e->getMessage()
-			];
-		}
-	}
-
-	protected function cleanupUnusedImages(array $unused_images): array
-	{
-		$cleaned_count = 0;
-		$errors = [];
-
-		foreach ($unused_images as $upload_id => $image_data) {
-			try {
-				$attachment_id = $image_data['attachment_id'];
-
-				// Verify this attachment exists and wasn't already deleted
-				if (get_post($attachment_id)) {
-					// Delete the attachment and its files
-					$deleted = wp_delete_attachment($attachment_id, true);
-
-					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}";
-					}
-				} else {
-					// Attachment already doesn't exist, count as cleaned
-					$cleaned_count++;
-				}
-
-			} catch (Exception $e) {
-				$errors[] = "Error cleaning up upload {$upload_id}: " . $e->getMessage();
-			}
-		}
-
-		return [
-			'cleaned_count' => $cleaned_count,
-			'errors' => $errors
-		];
-	}
-
-	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
-				$stored_upload_id = $upload['upload_id'];
-				$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;
-	}
-
-	function extractFeaturedItem(array &$items, string $meta_key = 'featured'): array
-	{
-		// Handle empty array
-		if (empty($items)) {
-			return [
-				'featured' => null,
-				'remaining' => []
-			];
-		}
-
-		$featured_index = null;
-
-		// First pass: look for featured item
-		foreach ($items as $index => $item) {
-			if (isset($item['meta'][$meta_key])) {
-				$featured_index = $index;
-				break;
-			}
-		}
-
-		// If no featured item found, use first item (index 0)
-		if ($featured_index === null) {
-			$featured_index = 0;
-		}
-
-		// Extract the featured/first item
-		$featured = $items[$featured_index];
-
-		// Remove the item from the original array
-		unset($items[$featured_index]);
-
-		// Re-index the array to maintain sequential indices
-		$remaining = array_values($items);
-
-		return [
-			'featured' => $featured,
-			'remaining' => $remaining
-		];
-	}
-
-	protected function mapUploadIdsToAttachments(array $uploadIds, array $uploadedImages): array
-	{
-		$mappedImages = [];
-
-		foreach ($uploadIds as $uploadId) {
-			$imageData = $this->findImageByUploadId($uploadId, $uploadedImages);
-			if ($imageData) {
-				$mappedImages[] = $imageData;
-			}
-		}
-
-		return $mappedImages;
-	}
-
-	protected function findImageByUploadId(string $uploadId, array $uploadedImages): ?array
-	{
-		// Handle both flat array and grouped results
-		foreach ($uploadedImages as $image) {
-			if (is_array($image)) {
-				// If it's a grouped result, check each image in the group
-				if (isset($image[0]) && is_array($image[0])) {
-					foreach ($image as $groupImage) {
-						if (isset($groupImage['upload_id']) && $groupImage['upload_id'] === $uploadId) {
-							return $groupImage;
-						}
-					}
-				} else {
-					// Single image result
-					if (isset($image['upload_id']) && $image['upload_id'] === $uploadId) {
-						return $image;
-					}
-				}
-			}
-		}
-
-		return null;
-	}
-
-	protected function determineFeaturedImage(array $group, array $groupImages): ?int
-	{
-		// Check if user has starred a specific image
-		if (!empty($group['featured_upload_id'])) {
-			foreach ($groupImages as $image) {
-				if (isset($image['upload_id']) && $image['upload_id'] === $group['featured_upload_id']) {
-					return $image['attachment_id'];
-				}
-			}
-		}
-
-		// Default to first image
-		return !empty($groupImages) ? $groupImages[0]['attachment_id'] : null;
-	}
-
-	protected function sanitizeGroupMetadata(array $metadata): array
-	{
-		$sanitized = [];
-
-		foreach ($metadata as $key => $value) {
-			$sanitizedKey = sanitize_key($key);
-
-			if (is_string($value)) {
-				$sanitized[$sanitizedKey] = sanitize_text_field($value);
-			} elseif (is_array($value)) {
-				$sanitized[$sanitizedKey] = array_map('sanitize_text_field', $value);
-			} else {
-				$sanitized[$sanitizedKey] = $value;
-			}
-		}
-
-		return $sanitized;
-	}
-
-	protected function generatePostTitle(string $content, int $userId): string
-	{
-		$username = get_user_meta($userId, 'first_name', true) ?: get_user_meta($userId, 'display_name', true);
-		$link = get_user_meta($userId, BASE.'link', true);
-		$city = function_exists('jvbArtistCity') ? jvbArtistCity($link) : '';
-
-		$title = ucfirst($content);
-		if ($username) {
-			$title .= ' by ' . $username;
-		}
-		if ($city) {
-			$title .= ' from ' . $city;
-		}
-
-		return $title;
-	}
 }

--
Gitblit v1.10.0