<?php
declare(strict_types=1);
function mq_base(){return __DIR__;}
function mq_queue_dir(){
 $d=mq_base().'/.mesora-queue';if(!is_dir($d))@mkdir($d,0700,true);
 if(!is_file($d.'/.htaccess'))@file_put_contents($d.'/.htaccess',"Require all denied\nDeny from all\n");
 return $d;
}
function ms_root(){
 $d=mq_base().'/storage';if(!is_dir($d))@mkdir($d,0700,true);
 if(!is_file($d.'/.htaccess'))@file_put_contents($d.'/.htaccess',"Require all denied\nDeny from all\n");
 if(!is_file($d.'/index.php'))@file_put_contents($d.'/index.php',"<?php http_response_code(403); exit;\n");
 foreach(['images','voice','audio','video','files'] as $b)if(!is_dir($d.'/'.$b))@mkdir($d.'/'.$b,0700,true);
 return $d;
}
function ms_bucket_from_method($method){
 return match((string)$method){'sendPhoto'=>'images','sendVoice'=>'voice','sendAudio'=>'audio','sendVideo'=>'video',default=>'files'};
}
function ms_bucket_from_name($name,$hint=''){
 if($hint!=='')return in_array($hint,['images','voice','audio','video','files'],true)?$hint:'files';
 $ext=strtolower(pathinfo((string)$name,PATHINFO_EXTENSION));
 if(in_array($ext,['jpg','jpeg','png','webp','gif'],true))return 'images';
 if(in_array($ext,['ogg','oga'],true))return 'voice';
 if(in_array($ext,['mp3','m4a','aac','wav','flac'],true))return 'audio';
 if(in_array($ext,['mp4','mov','mkv','webm'],true))return 'video';
 return 'files';
}
function ms_safe_ext($name){$e=strtolower(pathinfo((string)$name,PATHINFO_EXTENSION));return preg_match('/^[a-z0-9]{1,8}$/D',$e)?'.'.$e:'.bin';}
function ms_store_bytes($bytes,$name,$bucket='files'){
 if(!is_string($bytes)||$bytes==='')return false;$root=ms_root();$bucket=ms_bucket_from_name($name,$bucket);
 $ym=gmdate('Y/m');$dir=$root.'/'.$bucket.'/'.$ym;if(!is_dir($dir))@mkdir($dir,0700,true);
 $id=gmdate('YmdHis').'-'.bin2hex(random_bytes(8)).ms_safe_ext($name);$path=$dir.'/'.$id;
 if(@file_put_contents($path,$bytes,LOCK_EX)===false)return false;@chmod($path,0600);return $path;
}
function ms_store_upload($tmp,$name,$bucket='files'){
 if(!is_file($tmp))return false;$root=ms_root();$bucket=ms_bucket_from_name($name,$bucket);
 $ym=gmdate('Y/m');$dir=$root.'/'.$bucket.'/'.$ym;if(!is_dir($dir))@mkdir($dir,0700,true);
 $id=gmdate('YmdHis').'-'.bin2hex(random_bytes(8)).ms_safe_ext($name);$path=$dir.'/'.$id;
 if(!@move_uploaded_file($tmp,$path)&&!@copy($tmp,$path))return false;@chmod($path,0600);return $path;
}
function ms_cleanup($days=30){
 $cut=time()-max(1,(int)$days)*86400;$root=ms_root();
 $it=new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root,FilesystemIterator::SKIP_DOTS),RecursiveIteratorIterator::CHILD_FIRST);
 foreach($it as $f){$p=$f->getPathname();if($f->isFile()&&basename($p)!=='.htaccess'&&basename($p)!=='index.php'&&$f->getMTime()<$cut)@unlink($p);}
}
function mq_id(){return gmdate('YmdHis').'-'.bin2hex(random_bytes(8));}
function mq_write_json($path,$data){$fp=@fopen($path,'xb');if(!$fp)return false;$ok=false;if(@flock($fp,LOCK_EX)){$raw=json_encode($data,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);$ok=@fwrite($fp,$raw)!==false;@flock($fp,LOCK_UN);}@fclose($fp);return $ok;}
function mq_enqueue_media($meta,$tmp){
 $d=mq_queue_dir();$id=mq_id();$bucket=ms_bucket_from_method((string)($meta['method']??''));$stored=ms_store_upload($tmp,(string)($meta['name']??'media.bin'),$bucket);if(!$stored)return false;
 $job=['id'=>$id,'type'=>'media','meta'=>$meta,'file'=>$stored,'attempts'=>0,'next_at'=>0,'created_at'=>time()];if(!mq_write_json($d.'/'.$id.'.job',$job))return false;return $id;
}
function mq_jobs(){$files=glob(mq_queue_dir().'/*.job')?:[];sort($files,SORT_STRING);return $files;}
function mq_load($file){$x=json_decode((string)@file_get_contents($file),true);return is_array($x)?$x:null;}
function mq_save($file,$job){$tmp=$file.'.tmp';$raw=json_encode($job,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);if(@file_put_contents($tmp,$raw,LOCK_EX)===false)return false;return @rename($tmp,$file);}
function mq_finish($jobfile,$job){@unlink($jobfile);} // keep stored media in storage
function mq_fail($jobfile,$job,$error){$job['attempts']=(int)($job['attempts']??0)+1;$job['last_error']=substr((string)$error,0,500);if($job['attempts']>=5){$job['failed_at']=time();@rename($jobfile,$jobfile.'.failed');return;}$job['next_at']=time()+min(300,5*(2**($job['attempts']-1)));mq_save($jobfile,$job);}
