<?php
declare(strict_types=1);

require_once '/home/jrundfpc/private_ww/colabgpt/action_relay.php';

function colabgpt_direct_html(string $title,string $body,int $status=200): never
{
    http_response_code($status);
    header('Content-Type: text/html; charset=utf-8');
    header('Cache-Control: no-store');
    header('X-Content-Type-Options: nosniff');
    header('Referrer-Policy: no-referrer');
    $safeTitle=htmlspecialchars($title,ENT_QUOTES|ENT_SUBSTITUTE,'UTF-8');
    echo '<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
    echo '<title>'.$safeTitle.'</title><style>body{font-family:Arial,sans-serif;background:#f7f2e8;color:#243447;max-width:780px;margin:40px auto;padding:0 20px;line-height:1.55}';
    echo '.card{background:#fffaf1;border:1px solid #9ab8b5;border-radius:12px;padding:24px}h1{color:#2f6f73}button{background:#2f6f73;color:white;border:0;border-radius:8px;padding:12px 18px;font-size:16px}';
    echo 'input[type=file]{display:block;margin:18px 0;font-size:16px}.ok{background:#e4f0e3;padding:12px;border-radius:8px}.err{background:#f3e2e0;padding:12px;border-radius:8px}';
    echo '.progress{width:100%;height:18px}.status{margin-top:14px;white-space:pre-wrap}</style></head><body>';
    echo '<div class="card"><h1>'.$safeTitle.'</h1>'.$body.'</div></body></html>';
    exit;
}

function colabgpt_direct_json(array $payload,int $status=200): never
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    header('Cache-Control: no-store');
    header('X-Content-Type-Options: nosniff');
    header('Referrer-Policy: no-referrer');
    echo json_encode($payload,JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR);
    exit;
}

$token=isset($_GET['t'])&&is_string($_GET['t'])?$_GET['t']:'';
if($token==='') colabgpt_direct_html('Invalid link','<p class="err">This link is missing its secure token.</p>',400);

try{
    $service=colabgpt_direct_file_transfer_service();
    $method=strtoupper((string)($_SERVER['REQUEST_METHOD']??'GET'));

    // Download tokens remain direct GET-only.
    try{
        $claims=$service->decodePublicToken($token,'download');
        if($method!=='GET') colabgpt_direct_html('Method not allowed','<p class="err">This download link only supports GET.</p>',405);
        $service->downloadFile($claims);
    }catch(RuntimeException $downloadError){
        if(!in_array($downloadError->getMessage(),['DIRECT_TRANSFER_TOKEN_INVALID','DIRECT_TRANSFER_TOKEN_EXPIRED'],true)){
            throw $downloadError;
        }
    }

    $claims=$service->decodePublicToken($token,'upload');

    if($method==='GET'){
        $safeToken=json_encode($token,JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT|JSON_THROW_ON_ERROR);
        $body=<<<HTML
<p>Select one or more files. Browser fallback uploads use <b>20 MB raw binary chunks</b>, assembled and verified on the server.</p>
<p><b>No Base64 or GPT-mediated file chunks are used.</b></p>
<input id="files" type="file" multiple required>
<button id="upload" type="button">Upload files</button>
<progress id="progress" class="progress" max="100" value="0"></progress>
<div id="status" class="status"></div>
<p><small>The secure upload link expires automatically. Keep this page open until all selected files finish.</small></p>
<script>
const token={$safeToken};
const CHUNK=20*1024*1024;
const filesEl=document.getElementById('files');
const button=document.getElementById('upload');
const progress=document.getElementById('progress');
const statusEl=document.getElementById('status');

async function api(op, options={}) {
  const url='?t='+encodeURIComponent(token)+'&op='+encodeURIComponent(op)+(options.query||'');
  const resp=await fetch(url, options.fetch||{});
  let data=null;
  try { data=await resp.json(); } catch(e) {}
  if(!resp.ok || !data || data.ok===false) {
    throw new Error((data&&data.error)||('HTTP_'+resp.status));
  }
  return data;
}

async function uploadOne(file, fileNo, fileCount) {
  const totalChunks=Math.ceil(file.size/CHUNK);
  statusEl.textContent='Preparing '+file.name+' ('+fileNo+'/'+fileCount+')';
  const init=await api('init',{
    fetch:{
      method:'POST',
      headers:{'Content-Type':'application/json'},
      body:JSON.stringify({fileName:file.name,fileSize:file.size,totalChunks})
    }
  });
  const uploadId=init.uploadId;

  for(let i=0;i<totalChunks;i++){
    const start=i*CHUNK;
    const end=Math.min(file.size,start+CHUNK);
    const blob=file.slice(start,end);
    let attempt=0;
    for(;;){
      try{
        statusEl.textContent='Uploading '+file.name+' chunk '+(i+1)+' of '+totalChunks;
        await api('chunk',{
          query:'&uploadId='+encodeURIComponent(uploadId)+'&chunkIndex='+i,
          fetch:{method:'POST',headers:{'Content-Type':'application/octet-stream'},body:blob}
        });
        break;
      }catch(err){
        attempt++;
        if(attempt>=3) throw err;
        statusEl.textContent='Retrying '+file.name+' chunk '+(i+1)+' of '+totalChunks;
        await new Promise(r=>setTimeout(r,1000*attempt));
      }
    }
    const completed=((fileNo-1)+(i+1)/totalChunks)/fileCount;
    progress.value=Math.round(completed*100);
  }

  statusEl.textContent='Verifying and committing '+file.name;
  const done=await api('complete',{
    fetch:{
      method:'POST',
      headers:{'Content-Type':'application/json'},
      body:JSON.stringify({uploadId})
    }
  });
  return done;
}

button.addEventListener('click',async()=>{
  const files=Array.from(filesEl.files||[]);
  if(files.length<1){statusEl.textContent='Choose at least one file.';return;}
  if(files.length>10){statusEl.textContent='Choose no more than 10 files at once.';return;}
  button.disabled=true;filesEl.disabled=true;progress.value=0;
  const results=[];
  try{
    for(let i=0;i<files.length;i++){
      const r=await uploadOne(files[i],i+1,files.length);
      results.push(r);
    }
    progress.value=100;
    statusEl.textContent='Upload complete. '+results.length+' file(s) stored successfully. You can return to CollabGPT.';
  }catch(err){
    statusEl.textContent='Upload stopped: '+err.message+'. You can retry; completed chunks are safely idempotent.';
  }finally{
    button.disabled=false;filesEl.disabled=false;
  }
});
</script>
HTML;
        colabgpt_direct_html('Upload project files',$body);
    }

    if($method!=='POST'){
        colabgpt_direct_json(['ok'=>false,'error'=>'METHOD_NOT_ALLOWED'],405);
    }

    $op=isset($_GET['op'])&&is_string($_GET['op'])?$_GET['op']:'';
    if($op==='init'){
        $raw=file_get_contents('php://input');
        if(!is_string($raw)) throw new RuntimeException('DIRECT_UPLOAD_REQUEST_INVALID');
        try{$input=json_decode($raw,true,16,JSON_THROW_ON_ERROR);}
        catch(Throwable){throw new InvalidArgumentException('DIRECT_UPLOAD_REQUEST_INVALID');}
        if(!is_array($input)) throw new InvalidArgumentException('DIRECT_UPLOAD_REQUEST_INVALID');
        $name=$input['fileName']??null;
        $size=$input['fileSize']??null;
        $total=$input['totalChunks']??null;
        if(!is_string($name)||!is_int($size)||!is_int($total)) throw new InvalidArgumentException('DIRECT_UPLOAD_REQUEST_INVALID');
        $result=$service->beginChunkedUpload($claims,$name,$size,$total);
        colabgpt_direct_json(['ok'=>true]+$result);
    }

    if($op==='chunk'){
        $uploadId=isset($_GET['uploadId'])&&is_string($_GET['uploadId'])?$_GET['uploadId']:'';
        $chunkRaw=$_GET['chunkIndex']??null;
        if($uploadId==='' || !(is_string($chunkRaw)||is_int($chunkRaw)) || !preg_match('/^\d+$/',(string)$chunkRaw)){
            throw new InvalidArgumentException('DIRECT_UPLOAD_CHUNK_REQUEST_INVALID');
        }
        $result=$service->storeChunkStream($claims,$uploadId,(int)$chunkRaw);
        colabgpt_direct_json(['ok'=>true]+$result);
    }

    if($op==='complete'){
        $raw=file_get_contents('php://input');
        if(!is_string($raw)) throw new RuntimeException('DIRECT_UPLOAD_REQUEST_INVALID');
        try{$input=json_decode($raw,true,16,JSON_THROW_ON_ERROR);}
        catch(Throwable){throw new InvalidArgumentException('DIRECT_UPLOAD_REQUEST_INVALID');}
        if(!is_array($input)||!isset($input['uploadId'])||!is_string($input['uploadId'])){
            throw new InvalidArgumentException('DIRECT_UPLOAD_REQUEST_INVALID');
        }
        $result=$service->completeChunkedUpload($claims,$input['uploadId']);
        colabgpt_direct_json(['ok'=>true]+$result);
    }

    colabgpt_direct_json(['ok'=>false,'error'=>'DIRECT_UPLOAD_OPERATION_INVALID'],400);
}catch(RuntimeException|InvalidArgumentException $e){
    $code=$e->getMessage();
    $safe=in_array($code,[
        'DIRECT_TRANSFER_TOKEN_EXPIRED','DIRECT_UPLOAD_SIZE_INVALID','DIRECT_UPLOAD_CHUNK_COUNT_INVALID',
        'DIRECT_UPLOAD_CHUNK_INDEX_INVALID','DIRECT_UPLOAD_CHUNK_SIZE_INVALID','DIRECT_UPLOAD_CHUNK_MISSING',
        'DIRECT_UPLOAD_NOT_FOUND','DIRECT_UPLOAD_CHUNK_CONFLICT','PROJECT_MEMBERSHIP_REQUIRED'
    ],true)?$code:'DIRECT_FILE_TRANSFER_FAILED';

    if(strtoupper((string)($_SERVER['REQUEST_METHOD']??'GET'))==='POST'){
        colabgpt_direct_json(['ok'=>false,'error'=>$safe],400);
    }
    $msg=$code==='DIRECT_TRANSFER_TOKEN_EXPIRED'
        ? 'This secure link has expired. Ask CollabGPT for a fresh link.'
        : 'The file transfer could not be completed safely.';
    colabgpt_direct_html('Transfer stopped','<p class="err">'.htmlspecialchars($msg,ENT_QUOTES|ENT_SUBSTITUTE,'UTF-8').'</p>',400);
}
