import {createServer} from 'node:http';
import {readFile,stat} from 'node:fs/promises';
import {resolve,sep,extname} from 'node:path';
import {fileURLToPath,pathToFileURL} from 'node:url';
import {writeArchive} from './archive.mjs';
import {validateVariant} from './scenarios.mjs';
import {validateOptions} from './validation.mjs';
const PUBLIC=fileURLToPath(new URL('../public/',import.meta.url));
function json(res,status,data){if(res.destroyed)return;res.writeHead(status,{'Content-Type':'application/json; charset=utf-8','Cache-Control':'no-store','X-Content-Type-Options':'nosniff'});res.end(JSON.stringify(data));}
export function createWorkshopServer({onArchiveEnd}={}){
 let active=0;const server=createServer(async(req,res)=>{
  const host=req.headers.host,port=server.address()?.port;if(![`127.0.0.1:${port}`,`localhost:${port}`].includes(host)){json(res,400,{error:'Для учебного сервера используйте локальный адрес.'});return;}
  let url;try{url=new URL(req.url,'http://'+host);}catch{json(res,400,{error:'Некорректный URL.'});return;}
  if(url.pathname==='/api/archive'){
   if(req.method!=='POST'){json(res,405,{error:'Нужен POST с JSON.'});return;}
   if(req.headers.origin&&req.headers.origin!=='http://'+host){json(res,403,{error:'Запрос другого происхождения запрещён.'});return;}
   if(!/^application\/json(?:\s*;|$)/i.test(req.headers['content-type']||'')){json(res,415,{error:'Content-Type должен быть application/json.'});return;}
   if(active>=2){json(res,429,{error:'Одновременно допускаются два архива.'});return;}
   active++;const controller=new AbortController();let bytes=0;const timeout=setTimeout(()=>{controller.abort(new Error('Превышено время подготовки архива.'));req.destroy();res.destroy();},10000);timeout.unref();
   res.on('close',()=>{if(!res.writableFinished)controller.abort(new Error('Клиент закрыл соединение.'));});
   try{
    const chunks=[];for await(const chunk of req){bytes+=chunk.length;if(bytes>1024){json(res,413,{error:'JSON не больше 1024 байт.'});return;}chunks.push(chunk);}
    let input;try{input=validateOptions(JSON.parse(Buffer.concat(chunks).toString('utf8')));validateVariant(input.scene,input.variant);}catch(error){json(res,400,{error:error.message});return;}
    res.setHeader('Content-Type','application/zip');res.setHeader('Content-Disposition',`attachment; filename="archiver-${input.scene}-${input.variant}.zip"`);res.setHeader('Cache-Control','no-store');res.setHeader('X-Content-Type-Options','nosniff');
    const result=await writeArchive(res,input,{signal:controller.signal});onArchiveEnd?.({ok:true,...result});
   }catch(error){onArchiveEnd?.({ok:false,cancelled:controller.signal.aborted,error:error.message});if(!res.headersSent&&!res.destroyed)json(res,500,{error:'Не удалось завершить ZIP.'});else res.destroy();}
   finally{clearTimeout(timeout);active--;}
   return;
  }
  if(req.method!=='GET'&&req.method!=='HEAD'){json(res,405,{error:'Метод не поддерживается.'});return;}
  try{const path=decodeURIComponent(url.pathname==='/'?'/index.html':url.pathname),file=resolve(PUBLIC,'.'+path);if(!file.startsWith(resolve(PUBLIC)+sep)||(await stat(file)).isDirectory())throw new Error('Not found');const mime={'.html':'text/html; charset=utf-8','.js':'text/javascript; charset=utf-8','.css':'text/css; charset=utf-8','.json':'application/json; charset=utf-8','.zip':'application/zip','.txt':'text/plain; charset=utf-8'};res.writeHead(200,{'Content-Type':mime[extname(file)]||'application/octet-stream','X-Content-Type-Options':'nosniff','Cache-Control':'no-store'});res.end(req.method==='HEAD'?undefined:await readFile(file));}catch{json(res,404,{error:'Файл не найден.'});}
 });server.requestTimeout=15000;server.headersTimeout=10000;return server;
}
if(process.argv[1]&&import.meta.url===pathToFileURL(resolve(process.argv[1])).href){const port=Number(process.env.PORT||8173);if(!Number.isInteger(port)||port<1024||port>65535)throw new Error('PORT: целое число 1024–65535');createWorkshopServer().listen(port,'127.0.0.1',()=>console.log(`Archiver workshop: http://127.0.0.1:${port}/ (только loopback)`));}
