<?php
if(!defined('ABSPATH'))exit;
require_once ABSPATH.'wp-admin/includes/image.php';
require_once ABSPATH.'wp-admin/includes/file.php';
require_once ABSPATH.'wp-admin/includes/media.php';
function drop_print_ensure_dir_exists_upload_handler($path){
    if(!file_exists($path)){
        if(!wp_mkdir_p($path)){
            return false;
        }
        @chmod($path,0755);
    }
    if(!is_writable($path)){
        if(!@chmod($path,0755)||!is_writable($path)){
            return false;
        }
    }
    return true;
}
function drop_print_cleanup_chunks_upload_handler($path){
    if(empty($path)||!is_dir($path))return;
    try{
        $iterator=new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path,FilesystemIterator::SKIP_DOTS|FilesystemIterator::UNIX_PATHS),
            RecursiveIteratorIterator::CHILD_FIRST
        );
        foreach($iterator as $file){
            if($file->isFile()&&'part'===$file->getExtension()) wp_delete_file($file->getRealPath());
        }
        if(!(new \FilesystemIterator($path))->valid()) @rmdir($path);
    }catch(Exception $e){
    }
}
function drop_print_handle_chunked_upload(){
    $final_filepath=null;
    $temp_chunk_dir=null;
    $generated_thumbnail_for_afp_path=null;
    $generated_thumbnail_data=null;
    $generated_thumbnail_mime_type=null;
    $compressed_filepath=null;
    $afp_filepath=null;
    $afp_temp_filepath=null;
    $public_key_resource=null;
    $afp_file_handle=null;
    $temp_assembled_filepath=null;
    $process_succeeded=false;
    try{
        check_ajax_referer('drop_print_nonce','nonce');
        $current_user_id = get_current_user_id();
        if($current_user_id === 0){
            throw new Exception(__('User not identified for upload.','drop-print'),403);
        }
        if(!current_user_can('upload_files')||!current_user_can('edit_products')){
            throw new Exception(__('Permission denied.','drop-print'),403);
        }
        $product_id=absint($_POST['product_id']??0);
        if(!$product_id)throw new Exception(__('Missing or invalid Product ID.','drop-print'),400);
        $user_settings = drop_print_get_user_settings($current_user_id);
        $client_connection_id_from_settings = $user_settings['client_user_connection_id'] ?? null;
        if(empty($client_connection_id_from_settings)){
            throw new Exception(__('Client connection ID is not configured in your user settings. Cannot process file.','drop-print'),412);
        }
        if(!isset($_FILES['file'])||empty($_FILES['file']['tmp_name'])||!is_uploaded_file($_FILES['file']['tmp_name'])|| !isset($_FILES['file']['error']) ||UPLOAD_ERR_OK!==$_FILES['file']['error']){
            $error_code = isset($_FILES['file']['error']) ? absint($_FILES['file']['error']) : UPLOAD_ERR_NO_FILE;
            $upload_errors=[
                UPLOAD_ERR_INI_SIZE=>__('File exceeds upload_max_filesize directive in php.ini.','drop-print'),
                UPLOAD_ERR_FORM_SIZE=>__('File exceeds MAX_FILE_SIZE directive specified in HTML form.','drop-print'),
                UPLOAD_ERR_PARTIAL=>__('File was only partially uploaded.','drop-print'),
                UPLOAD_ERR_NO_FILE=>__('No file was uploaded.','drop-print'),
                UPLOAD_ERR_NO_TMP_DIR=>__('Missing a temporary folder.','drop-print'),
                UPLOAD_ERR_CANT_WRITE=>__('Failed to write file to disk.','drop-print'),
                UPLOAD_ERR_EXTENSION=>__('A PHP extension stopped the file upload.','drop-print'),
            ];
            throw new Exception(__('Upload Error: ','drop-print').($upload_errors[$error_code]??__('Unknown upload error','drop-print')).' (Code: '.$error_code.')',400);
        }
        $required_params=['dzuuid','dzchunkindex','dztotalfilesize','dzchunksize','dztotalchunkcount','dzfilename'];
        $params=[];
        foreach($required_params as $param){
            if(!isset($_POST[$param])||''===$_POST[$param]){
                throw new Exception(sprintf(__('Missing required chunk parameter: %1$s','drop-print'),esc_html($param)),400);
            }
            if('dzuuid'===$param)$params[$param]=sanitize_key($_POST[$param]);
            elseif(in_array($param,['dzchunkindex','dztotalfilesize','dzchunksize','dztotalchunkcount'],true))$params[$param]=absint($_POST[$param]);
            elseif('dzfilename'===$param)$params[$param]=sanitize_file_name(wp_unslash($_POST[$param]));
            else $params[$param]=sanitize_text_field(wp_unslash($_POST[$param]));
        }
        if($params['dztotalchunkcount']<=0||$params['dzchunkindex']<0||$params['dzchunkindex']>=$params['dztotalchunkcount']){
            throw new Exception(__('Invalid chunk index/count received.','drop-print'),400);
        }
        $upload_dir_info=wp_upload_dir();
        if(!empty($upload_dir_info['error']))throw new Exception(__('WordPress upload directory error: ','drop-print').$upload_dir_info['error'],500);
        $final_basedir=trailingslashit($upload_dir_info['basedir']).'drop-print';
        $chunks_basedir=trailingslashit($final_basedir).'chunks';
        $final_baseurl=trailingslashit($upload_dir_info['baseurl']).'drop-print';
        if(!drop_print_ensure_dir_exists_upload_handler($final_basedir)||!drop_print_ensure_dir_exists_upload_handler($chunks_basedir)){
            throw new Exception(__('Upload directory setup failed. Check permissions.','drop-print'),500);
        }
        $file_uuid=$params['dzuuid'];
        $chunk_index=$params['dzchunkindex'];
        $total_chunks=$params['dztotalchunkcount'];
        $original_name=$params['dzfilename'];
        $temp_chunk_dir=trailingslashit($chunks_basedir).$file_uuid;
        $chunk_filepath=trailingslashit($temp_chunk_dir).$chunk_index.'.part';
        if(!drop_print_ensure_dir_exists_upload_handler($temp_chunk_dir))throw new Exception(__('Cannot create temporary chunk directory.','drop-print'),500);
        if(!move_uploaded_file($_FILES['file']['tmp_name'],$chunk_filepath))throw new Exception(__('Failed to move uploaded chunk.','drop-print'),500);
        clearstatcache();
        $chunk_files=is_dir($temp_chunk_dir)?array_diff(scandir($temp_chunk_dir),['.','..']):[];
        if(count($chunk_files)<$total_chunks){
            wp_send_json_success(['message'=>__('Chunk received.','drop-print'),'chunk_index'=>$chunk_index]);
        }
        $temp_assembled_filename=wp_unique_filename($final_basedir,'temp_assembly_'.$file_uuid.'_'.$original_name);
        $temp_assembled_filepath=trailingslashit($final_basedir).$temp_assembled_filename;
        $output_resource=null;
        $assembly_success=false;
        try{
            $output_resource=@fopen($temp_assembled_filepath,'wb');
            if(!$output_resource){
                throw new Exception(sprintf(__('Failed to open temporary assembly file for writing: %1$s','drop-print'),esc_html(basename($temp_assembled_filepath))));
            }
            for($i=0;$i<$total_chunks;$i++){
                $chunk_part_path=trailingslashit($temp_chunk_dir).$i.'.part';
                if(!file_exists($chunk_part_path)){
                    throw new Exception(sprintf(__('Missing chunk %1$d during assembly.','drop-print'),$i));
                }
                $chunk_resource=@fopen($chunk_part_path,'rb');
                if(!$chunk_resource){
                    throw new Exception(sprintf(__('Cannot open chunk %1$d.','drop-print'),$i));
                }
                while(!feof($chunk_resource)){
                    $buffer=fread($chunk_resource,8192);
                    if(false===$buffer){
                        @fclose($chunk_resource);
                        throw new Exception(sprintf(__('Error reading chunk %1$d.','drop-print'),$i));
                    }
                    if(false===fwrite($output_resource,$buffer)){
                        @fclose($chunk_resource);
                        throw new Exception(sprintf(__('Error writing chunk %1$d to assembly file.','drop-print'),$i));
                    }
                }
                @fclose($chunk_resource);
            }
            $assembly_success=true;
        }finally{
            if($output_resource)@fclose($output_resource);
        }
        drop_print_cleanup_chunks_upload_handler($temp_chunk_dir);
        $temp_chunk_dir=null;
        clearstatcache(true,$temp_assembled_filepath);
        if(!$assembly_success||!file_exists($temp_assembled_filepath)||filesize($temp_assembled_filepath)<=0){
            if(file_exists($temp_assembled_filepath)) wp_delete_file($temp_assembled_filepath);
            $temp_assembled_filepath=null;
            throw new Exception(__('File assembly failed or result is empty.','drop-print'),500);
        }
        $pathinfo=pathinfo($original_name);
        $safe_original_base=sanitize_title($pathinfo['filename']);
        $original_extension=isset($pathinfo['extension'])?strtolower($pathinfo['extension']):'';
        $new_base_name=$safe_original_base.'-'.$file_uuid;
        $new_final_filename=$new_base_name.($original_extension?'.'.$original_extension:'');
        $new_final_filename=wp_unique_filename($final_basedir,$new_final_filename);
        $final_filepath=trailingslashit($final_basedir).$new_final_filename;
        $final_filename_saved=$new_final_filename;
        if(!rename($temp_assembled_filepath,$final_filepath)){
            $final_filepath=$temp_assembled_filepath;
            $final_filename_saved=$temp_assembled_filename;
            $new_base_name=pathinfo($final_filename_saved,PATHINFO_FILENAME);
        }
        $temp_assembled_filepath=null;
        $attachment_id=null;
        $media_library_thumb_url=null;
        $preview_url=null;
        $camera_make_model=null;
        $preview_path=null;
        $source_icc_name=null;
        $source_icc_sha256=null;
        $width=null;
        $height=null;
        $original_hash=null;
        $hash_algo='sha256';
        $meta_changed=false;
        $meta_array=get_post_meta($product_id,'_drop_print_meta',true);
        if(!is_array($meta_array))$meta_array=[];
        $preview_editor=wp_get_image_editor($final_filepath);
        if(!is_wp_error($preview_editor)){
            $src_size=$preview_editor->get_size();
            $needs_resize=is_array($src_size)&&((int)($src_size['width']??0)>2000||(int)($src_size['height']??0)>2000);
            $resize_ok=true;
            if($needs_resize){
                $preview_resize=$preview_editor->resize(2000,2000,false);
                $resize_ok=!is_wp_error($preview_resize);
            }
            if($resize_ok){
                $preview_editor->set_quality(72);
                $preview_filename=wp_unique_filename($final_basedir,$new_base_name.'-preview.jpg');
                $preview_target=trailingslashit($final_basedir).$preview_filename;
                $saved_preview=$preview_editor->save($preview_target,'image/jpeg');
                if(!is_wp_error($saved_preview)&&!empty($saved_preview['path'])&&file_exists($saved_preview['path'])){
                    $preview_path=$saved_preview['path'];
                    $preview_basename=$saved_preview['file']??basename($saved_preview['path']);
                    $preview_url=trailingslashit($final_baseurl).$preview_basename;
                    if(($meta_array['preview_url']??null)!==$preview_url){$meta_array['preview_url']=esc_url_raw($preview_url);$meta_changed=true;}
                    if(($meta_array['preview_path']??null)!==$preview_path){$meta_array['preview_path']=$preview_path;$meta_changed=true;}
                }
            }
            unset($preview_editor);
        }
        $source_icc=drop_print_source_icc_identity($final_filepath);
        if(!empty($source_icc['name'])){
            $source_icc_name=$source_icc['name'];
            $source_icc_sha256=$source_icc['sha256']??null;
            if(($meta_array['source_icc_name']??null)!==$source_icc_name){$meta_array['source_icc_name']=sanitize_text_field($source_icc_name);$meta_changed=true;}
            if(($meta_array['source_icc_sha256']??null)!==$source_icc_sha256){$meta_array['source_icc_sha256']=$source_icc_sha256;$meta_changed=true;}
        }
        $camera=drop_print_camera_exif_identity($final_filepath);
        if(!empty($camera)){
            $camera_make_model=$camera;
            if(($meta_array['camera_make_model']??null)!==$camera_make_model){$meta_array['camera_make_model']=sanitize_text_field($camera_make_model);$meta_changed=true;}
        }
        $image_editor=wp_get_image_editor($final_filepath);
        if(!is_wp_error($image_editor)){
            $resize_op_result=$image_editor->resize(300,300,false);
            if(!is_wp_error($resize_op_result)){
                $path_parts_for_thumb=pathinfo($final_filename_saved);
                $temp_thumb_for_afp_filename=wp_unique_filename($final_basedir,$path_parts_for_thumb['filename'].'-afpthumb.'.($path_parts_for_thumb['extension']?:'jpg'));
                $generated_thumbnail_for_afp_path=trailingslashit($final_basedir).$temp_thumb_for_afp_filename;
                $saved_afp_thumb_details=$image_editor->save($generated_thumbnail_for_afp_path);
                if(!is_wp_error($saved_afp_thumb_details)&&file_exists($generated_thumbnail_for_afp_path)){
                    $generated_thumbnail_data=file_get_contents($generated_thumbnail_for_afp_path);
                    $generated_thumbnail_mime_type=$saved_afp_thumb_details['mime-type']??mime_content_type($generated_thumbnail_for_afp_path);
                    if(false===$generated_thumbnail_data||empty($generated_thumbnail_mime_type)){
                        $generated_thumbnail_data=null;
                        $generated_thumbnail_mime_type=null;
                    }
                    $wp_ml_thumb_filename=wp_unique_filename($final_basedir,$path_parts_for_thumb['filename'].'-wpmlthumb.'.($path_parts_for_thumb['extension']?:'jpg'));
                    $wp_ml_thumb_path=trailingslashit($final_basedir).$wp_ml_thumb_filename;
                    if(copy($generated_thumbnail_for_afp_path,$wp_ml_thumb_path)){
                        $file_array_for_ml=['name'=>basename($wp_ml_thumb_path),'tmp_name'=>$wp_ml_thumb_path,'error'=>0,'size'=>filesize($wp_ml_thumb_path)];
                        $attachment_id=media_handle_sideload($file_array_for_ml,$product_id,null,['post_parent'=>$product_id]);
                        if(!is_wp_error($attachment_id)){
                            if(!isset($meta_array['preview_id'])||absint($meta_array['preview_id'])!==$attachment_id){
                                $meta_array['preview_id']=$attachment_id;
                                $meta_changed=true;
                            }
                            $media_library_thumb_url=wp_get_attachment_thumb_url($attachment_id);
                        }else{
                            $attachment_id=null;
                        }
                        if(file_exists($wp_ml_thumb_path)) wp_delete_file($wp_ml_thumb_path);
                    }
                }
                if($generated_thumbnail_for_afp_path&&file_exists($generated_thumbnail_for_afp_path)) wp_delete_file($generated_thumbnail_for_afp_path);
                $generated_thumbnail_for_afp_path=null;
            }
        }
        $dimensions=@getimagesize($final_filepath);
        if(false===$dimensions||empty($dimensions[0])||empty($dimensions[1])){$width=0;
            $height=0;
        }else{$width=absint($dimensions[0]);
            $height=absint($dimensions[1]);
            if(in_array(drop_print_exif_orientation($final_filepath),[5,6,7,8],true)){
                [$width,$height]=[$height,$width];
            }
            $new_dims=['width'=>$width,'height'=>$height];
            if(!isset($meta_array['dimensions'])||$meta_array['dimensions']!==$new_dims){
                $meta_array['dimensions']=$new_dims;
                $meta_changed=true;
            }
        }
        if(!isset($meta_array['file'])||$meta_array['file']!==$new_base_name){
            $meta_array['file']=sanitize_text_field($new_base_name);
            $meta_changed=true;
        }
        if($meta_changed)update_post_meta($product_id,'_drop_print_meta',$meta_array);
        $original_hash=hash_file($hash_algo,$final_filepath);
        if(false===$original_hash){
            $original_hash='';
        }
        $source_file_for_processing=$final_filepath;
        if(class_exists('ZipArchive')){
            $path_parts_for_zip=pathinfo($final_filepath);
            $compressed_filepath_temp=trailingslashit($path_parts_for_zip['dirname']).$path_parts_for_zip['filename'].'_temp.zip';
            $zip=new ZipArchive();
            if(true===$zip->open($compressed_filepath_temp,ZipArchive::CREATE|ZipArchive::OVERWRITE)){
                if($zip->addFile($final_filepath,$original_name)&&$zip->close()){
                    $compressed_filepath=$compressed_filepath_temp;
                    $source_file_for_processing=$compressed_filepath;
                }else{
                    $zip->close();
                    if(file_exists($compressed_filepath_temp)) wp_delete_file($compressed_filepath_temp);
                }
            }
        }
        $is_encrypted=false;
$encryption_enabled = (bool) (
    $user_settings['encryption'] ?? false
);
        $public_key_pem=null;
        $user_public_key_b64 = $user_settings['public_key_pem_b64'] ?? null;
        if($encryption_enabled && extension_loaded('openssl') && !empty($user_public_key_b64)){
            $decoded_pem_key=base64_decode($user_public_key_b64,true);
            if(false!==$decoded_pem_key){
                $public_key_pem=trim($decoded_pem_key);
                if(!str_starts_with($public_key_pem,'-----BEGIN PUBLIC KEY-----')){
                    $public_key_pem=null;
                }
            }else{
                $public_key_pem=null;
            }
        }
        $container_ext=defined('DROP_PRINT_CONTAINER_EXT')?DROP_PRINT_CONTAINER_EXT:'.drop';
        $afp_filename=$new_base_name.$container_ext;
        $afp_filepath=trailingslashit($final_basedir).$afp_filename;
        $afp_file_url=trailingslashit($final_baseurl).$afp_filename;
        $afp_temp_filepath=$afp_filepath.'.part';
        $afp_file_handle=@fopen($afp_temp_filepath,'wb');
        if(!$afp_file_handle){
            throw new Exception(sprintf(__('Failed to open container file for writing: %1$s','drop-print'),esc_html(basename($afp_temp_filepath))));
        }
        $write=static function($data) use ($afp_file_handle){
            $len=strlen($data);
            if(0===$len){return;}
            $written=fwrite($afp_file_handle,$data);
            if(false===$written||$written<$len){
                throw new Exception(__('Short or failed write to container file.','drop-print'),500);
            }
        };
        $write(pack('C',0));
        if($generated_thumbnail_data&&$generated_thumbnail_mime_type){
            $write(pack('C',1));
            $mime_bytes=mb_convert_encoding($generated_thumbnail_mime_type,'UTF-8');
            $mime_len=strlen($mime_bytes);
            if($mime_len>255){
                fseek($afp_file_handle,-1,SEEK_CUR);
                $write(pack('C',0));
            }else{
                $write(pack('C',$mime_len));
                $write($mime_bytes);
                $write(pack('N',strlen($generated_thumbnail_data)));
                $write($generated_thumbnail_data);
            }
        }else{
            $write(pack('C',0));
        }
        $write(hex2bin($original_hash?:str_repeat('00',32)));
        $client_id_bytes=mb_convert_encoding(strval($client_connection_id_from_settings),'UTF-8');
        $write(pack('n',strlen($client_id_bytes)));
        $write($client_id_bytes);
        if($public_key_pem){
            $max_attempts=3;
            $encryption_success=false;
            $encryption_error='';
            for($attempt=1;$attempt<=$max_attempts;$attempt++){
                $public_key_resource=null;
                try{
                    $cipher='aes-256-cbc';
                    $public_key_resource=openssl_pkey_get_public($public_key_pem);
                    if(!$public_key_resource)throw new Exception('Failed to load public key resource. '.openssl_error_string());
                    $symmetric_key=random_bytes(32);
                    $iv_length=openssl_cipher_iv_length($cipher);
                    if(false===$iv_length)throw new Exception('Could not get IV length for cipher: '.$cipher);
                    $iv=openssl_random_pseudo_bytes($iv_length);
                    $encrypted_symmetric_key='';
                    if(!openssl_public_encrypt($symmetric_key,$encrypted_symmetric_key,$public_key_resource,OPENSSL_PKCS1_OAEP_PADDING))throw new Exception('Failed to encrypt symmetric key. '.openssl_error_string());
                    openssl_free_key($public_key_resource);
                    $public_key_resource=null;
                    $plaintext=file_get_contents($source_file_for_processing);
                    if(false===$plaintext)throw new Exception('Failed to read source file for encryption.');
                    $encrypted_data=openssl_encrypt($plaintext,$cipher,$symmetric_key,0,$iv);
                    if(false===$encrypted_data)throw new Exception('Failed to encrypt data. '.openssl_error_string());
                    unset($plaintext,$symmetric_key);
                    $write($iv);
                    $write(pack('N',strlen($encrypted_symmetric_key)));
                    $write($encrypted_symmetric_key);
                    $write($encrypted_data);
                    unset($encrypted_data);
                    $is_encrypted=true;
                    $encryption_success=true;
                    break;
                }catch(Exception $e){
                    $encryption_error=$e->getMessage();
                    if($public_key_resource)openssl_free_key($public_key_resource);
                    if($attempt<$max_attempts)usleep(500000);
                }
            }
            if(!$encryption_success){
                if(-1===fseek($afp_file_handle,0))throw new Exception(__('Failed to rewind container for unencrypted fallback.','drop-print'),500);
                $write(pack('C',0));
                if($generated_thumbnail_data&&$generated_thumbnail_mime_type){
                    $write(pack('C',1));
                    $mime_bytes=mb_convert_encoding($generated_thumbnail_mime_type,'UTF-8');
                    $mime_len=strlen($mime_bytes);
                    if($mime_len>255){$write(pack('C',0));
                    }else{
                        $write(pack('C',$mime_len));
                        $write($mime_bytes);
                        $write(pack('N',strlen($generated_thumbnail_data)));
                        $write($generated_thumbnail_data);
                    }
                }else{
                    $write(pack('C',0));
                }
                $write(hex2bin($original_hash?:str_repeat('00',32)));
                $write(pack('n',strlen($client_id_bytes)));
                $write($client_id_bytes);
                $source_content=file_get_contents($source_file_for_processing);
                if(false===$source_content)throw new Exception('Failed read for unencrypted container write after encryption failure.');
                $write($source_content);
                unset($source_content);
                $is_encrypted=false;
            }
        }else{
            $source_content=file_get_contents($source_file_for_processing);
            if(false===$source_content)throw new Exception('Failed read for unencrypted container write.');
            $write($source_content);
            unset($source_content);
            $is_encrypted=false;
        }
        $payload_end=ftell($afp_file_handle);
        if(false===$payload_end)throw new Exception(__('Failed to determine container length.','drop-print'),500);
        if(-1===fseek($afp_file_handle,0))throw new Exception(__('Failed to seek container header.','drop-print'),500);
        $write(pack('C',$is_encrypted?1:0));
        if(false===ftruncate($afp_file_handle,$payload_end))throw new Exception(__('Failed to finalize container length.','drop-print'),500);
        if(false===fflush($afp_file_handle))throw new Exception(__('Failed to flush container to disk.','drop-print'),500);
        $close_ok=fclose($afp_file_handle);
        $afp_file_handle=null;
        if(false===$close_ok)throw new Exception(__('Failed to close container file cleanly.','drop-print'),500);
        clearstatcache(true,$afp_temp_filepath);
        $min_header_size_no_thumb=1+1+32+2+strlen($client_id_bytes);
        if(!file_exists($afp_temp_filepath)||filesize($afp_temp_filepath)<$min_header_size_no_thumb){
            throw new Exception(__('Verification failed: container file missing or incomplete.','drop-print'),500);
        }
        if(!rename($afp_temp_filepath,$afp_filepath)){
            throw new Exception(__('Failed to move finalized container into place.','drop-print'),500);
        }
        $afp_temp_filepath=null;
        clearstatcache(true,$afp_filepath);
        if($compressed_filepath&&file_exists($compressed_filepath)){
            if(!wp_delete_file($compressed_filepath));
        }
        $compressed_filepath=null;
        if($final_filepath&&file_exists($final_filepath)){
            if(!wp_delete_file($final_filepath));
        }
        $final_filepath=null;
        $response_data=[
            'file_location'=>$afp_file_url,
            'width'=>$width,
            'height'=>$height,
            'attachment_url'=>$media_library_thumb_url,
            'thumbnail_id'=>$attachment_id,
            'afp_base_filename'=>$new_base_name,
            'preview_url'=>$preview_url,
            'source_icc_name'=>$source_icc_name,
            'source_icc_sha256'=>$source_icc_sha256,
            'camera_make_model'=>$camera_make_model,
        ];
        if(!empty($new_base_name) && $product_id > 0 && function_exists('drop_print_files_upsert_record')){
            $record_fields = [
                'file_size' => (int)(filesize($afp_filepath) ?: 0),
                'encrypted' => (bool)$is_encrypted,
                'uploaded_by' => $current_user_id,
                'created_at' => time(),
                'source_name' => sanitize_file_name($original_name),
            ];
            $container_hash = hash_file('sha256', $afp_filepath);
            if(false !== $container_hash){
                $record_fields['container_sha256'] = $container_hash;
            }
            if(!empty($original_hash)){
                $record_fields['source_sha256'] = $original_hash;
                $record_fields['sha256_provenance'] = 'upload';
            }
            drop_print_files_upsert_record($new_base_name, $record_fields);
            drop_print_files_add_product_ref($new_base_name, $product_id);
        }
        $process_succeeded=true;
        wp_send_json_success($response_data);
    }catch(Exception $e){
        $error_code=is_int($e->getCode())&&$e->getCode()>=400&&$e->getCode()<600?$e->getCode():500;
        if(!empty($generated_thumbnail_for_afp_path)&&file_exists($generated_thumbnail_for_afp_path)) wp_delete_file($generated_thumbnail_for_afp_path);
        wp_send_json_error(['message'=>__('Processing failed: ','drop-print').$e->getMessage()],$error_code);
    }finally{
        if($afp_file_handle)@fclose($afp_file_handle);
        if($public_key_resource)openssl_free_key($public_key_resource);
        if(!empty($temp_chunk_dir))drop_print_cleanup_chunks_upload_handler($temp_chunk_dir);
        if(!$process_succeeded){
            if(!empty($afp_temp_filepath)&&file_exists($afp_temp_filepath)) wp_delete_file($afp_temp_filepath);
            if(!empty($afp_filepath)&&file_exists($afp_filepath)) wp_delete_file($afp_filepath);
            if(!empty($final_filepath)&&file_exists($final_filepath)) wp_delete_file($final_filepath);
            if(!empty($compressed_filepath)&&file_exists($compressed_filepath)) wp_delete_file($compressed_filepath);
            if(!empty($temp_assembled_filepath)&&file_exists($temp_assembled_filepath)) wp_delete_file($temp_assembled_filepath);
        }
    }
}
add_action('wp_ajax_drop_print_process_upload','drop_print_handle_chunked_upload');
if(!function_exists('drop_print_icc_read_description')){
    function drop_print_icc_read_description($bytes){
        $len=strlen($bytes);
        if($len<132)return '';
        $tag_count=(int)(unpack('N',substr($bytes,128,4))[1]??0);
        if($tag_count<=0||$tag_count>1000)return '';
        $table_start=132;
        if($len<$table_start+$tag_count*12)return '';
        $desc_offset=0;
        for($i=0;$i<$tag_count;$i++){
            $entry=substr($bytes,$table_start+$i*12,12);
            if(substr($entry,0,4)==='desc'){
                $desc_offset=(int)(unpack('N',substr($entry,4,4))[1]??0);
                break;
            }
        }
        if($desc_offset<=0||$desc_offset+12>$len)return '';
        $type=substr($bytes,$desc_offset,4);
        if($type==='desc'){
            $count=(int)(unpack('N',substr($bytes,$desc_offset+8,4))[1]??0);
            if($count<=0||$desc_offset+12+$count>$len)return '';
            return trim(rtrim(substr($bytes,$desc_offset+12,$count),"\0"));
        }
        if($type==='mluc'){
            $num=(int)(unpack('N',substr($bytes,$desc_offset+8,4))[1]??0);
            if($num<=0||$desc_offset+28>$len)return '';
            $rec_length=(int)(unpack('N',substr($bytes,$desc_offset+20,4))[1]??0);
            $rec_offset=(int)(unpack('N',substr($bytes,$desc_offset+24,4))[1]??0);
            if($rec_length<=0||$desc_offset+$rec_offset+$rec_length>$len)return '';
            $utf16=substr($bytes,$desc_offset+$rec_offset,$rec_length);
            $name=function_exists('mb_convert_encoding')?@mb_convert_encoding($utf16,'UTF-8','UTF-16BE'):@iconv('UTF-16BE','UTF-8//IGNORE',$utf16);
            return trim(rtrim((string)$name,"\0"));
        }
        return '';
    }
}
if(!function_exists('drop_print_source_icc_bytes_from_jpeg_app2')){
    function drop_print_source_icc_bytes_from_jpeg_app2($path){
        $fh=@fopen($path,'rb');
        if(!$fh)return null;
        $chunks=[];
        try{
            if(fread($fh,2)!=="\xFF\xD8")return null;
            while(!feof($fh)){
                $marker=fread($fh,2);
                if(strlen($marker)<2||$marker[0]!=="\xFF")break;
                $m=ord($marker[1]);
                if($m===0xD9||$m===0xDA)break;
                $lenbytes=fread($fh,2);
                if(strlen($lenbytes)<2)break;
                $seg_len=unpack('n',$lenbytes)[1];
                if($seg_len<2)break;
                $seg=fread($fh,$seg_len-2);
                if($m===0xE2&&strlen($seg)>14&&substr($seg,0,12)==="ICC_PROFILE\x00"){
                    $seq=ord($seg[12]);
                    $chunks[$seq]=substr($seg,14);
                }
            }
        }finally{
            fclose($fh);
        }
        if(empty($chunks))return null;
        ksort($chunks);
        return implode('',$chunks);
    }
}
if(!function_exists('drop_print_exif_orientation')){
    function drop_print_exif_orientation($path){
        if(!function_exists('exif_read_data')||!file_exists($path)){return 1;}
        $type=@exif_imagetype($path);
        if($type!==IMAGETYPE_JPEG&&$type!==IMAGETYPE_TIFF_II&&$type!==IMAGETYPE_TIFF_MM){return 1;}
        $exif=@exif_read_data($path,'IFD0',false);
        if(!is_array($exif)||empty($exif['Orientation'])){return 1;}
        return absint($exif['Orientation']);
    }
}
if(!function_exists('drop_print_camera_exif_identity')){
    function drop_print_camera_exif_identity($path){
        if(!function_exists('exif_read_data')||!file_exists($path)){return null;}
        $type=@exif_imagetype($path);
        if($type!==IMAGETYPE_JPEG&&$type!==IMAGETYPE_TIFF_II&&$type!==IMAGETYPE_TIFF_MM){return null;}
        $exif=@exif_read_data($path,'IFD0',false);
        if(!is_array($exif)){return null;}
        $make=isset($exif['Make'])?trim((string)$exif['Make']):'';
        $model=isset($exif['Model'])?trim((string)$exif['Model']):'';
        if($make===''&&$model===''){return null;}
        if($make!==''&&$model!==''&&stripos($model,$make)===0){$label=$model;}
        else{$label=trim($make.' '.$model);}
        $label=preg_replace('/\s+/',' ',$label);
        return $label!==''?$label:null;
    }
}
if(!function_exists('drop_print_source_icc_identity')){
    function drop_print_source_icc_identity($path){
        $out=['name'=>null,'sha256'=>null,'assumed_srgb'=>false];
        $icc=null;
        $ext=strtolower(pathinfo($path,PATHINFO_EXTENSION));
        if(in_array($ext,['jpg','jpeg'],true)){
            $icc=drop_print_source_icc_bytes_from_jpeg_app2($path);
        }
        if($icc===null&&extension_loaded('imagick')&&class_exists('Imagick')){
            try{
                $im=new Imagick();
                $im->readImage($path);
                $profiles=$im->getImageProfiles('icc',true);
                if(!empty($profiles['icc']))$icc=$profiles['icc'];
                $im->clear();
                $im->destroy();
            }catch(Throwable $e){
                $icc=null;
            }
        }
        if($icc!==null&&strlen($icc)>=132&&substr($icc,36,4)==='acsp'){
            $out['sha256']=hash('sha256',$icc);
            $name=drop_print_icc_read_description($icc);
            $out['name']=$name!==''?$name:'Embedded profile';
        }else{
            $out['assumed_srgb']=true;
            $out['name']='sRGB (assumed)';
        }
        return $out;
    }
}
