// Isolated UI fixture using the actual built prefab, scripts, engine and bundle assets.
// Build web-mobile first; optionally set CLOUD_RISE_BUILD, then node tools/preview-cloud-rise.cjs [port].
const http=require('node:http'),fs=require('node:fs'),path=require('node:path');
const root=path.resolve(__dirname,'..');
const built=process.env.CLOUD_RISE_BUILD||path.join(root,'build/cloud-rise-prefab-verification/web-mobile');
const port=Number(process.argv[2]||8765);
const engine=fs.readdirSync(built).find(n=>/^cocos2d-js.*\.js$/.test(n));
const html = `
cloudRise · Cocos UI preview
本地测试 报名 stage1 stage2待开始 stage2 stage3 成功 失败 超时 查看 通关
`;
const boot = `
window.previewErrors=[];window.addEventListener('error',e=>previewErrors.push(e.message));
window.addEventListener('unhandledrejection',e=>previewErrors.push(String(e.reason)));
let listeners=[],data,ack=false,view;
const profile={username:'小猫玩家',useravatar:'',useravatarIcon:'icon_9'};
const makeStage=n=>({stage:n,target:[5,7,9][n-1],pool:[10000,15000,20000][n-1],success_num:0,survivors:100,status:'playing',reward:0,opponents:Array.from({length:99},(_,i)=>({...profile,username:'玩家'+(i+1),alive:true}))});
const service={error:'',data:()=>data,run:()=>data.run,stage:()=>data.run&&data.run.stages.at(-1),now:()=>Date.now(),visible:()=>true,subscribe:fn=>(listeners.push(fn),()=>listeners=listeners.filter(x=>x!==fn)),resultKey:()=>data.run&&data.run.id+':'+data.run.stage+':'+data.run.status,unseenResult:()=>!ack&&data.run&&['waiting','failed','completed','expired'].includes(data.run.status),acknowledgeResult:()=>ack=true,sync:async()=>true,
startStage:async()=>{const n=data.run&&data.run.status==='waiting'?data.run.stage+1:1;data.available=false;if(!data.run)data.run={id:'preview',stage:0,status:'playing',expiresAt:Date.now()+86400000,stages:[]};data.run.stage=n;data.run.status='playing';data.run.stages.push(makeStage(n));ack=false;listeners.forEach(fn=>fn());return true;}};
window.previewService=service;
function fixture(name){ack=false;data={available:true,pools:[10000,15000,20000],period:{enabled:true,startsAt:Date.now()-1000,endsAt:Date.now()+86400000},run:null};if(name!=='报名'){const n=name.includes('stage3')?3:name.includes('stage2')?2:1;data.available=false;data.run={id:'preview-'+name,stage:n,status:'playing',expiresAt:Date.now()+86395000,stages:[makeStage(n)]};const s=service.stage();s.success_num=2;s.survivors=47;s.opponents.forEach((o,i)=>o.alive=i<46);if(name==='stage2待开始'||name==='成功'){s.stage=1;s.target=5;s.success_num=5;s.status='won';s.rewardSaved=true;s.survivors=3;s.reward=3340;data.run.stage=1;data.run.status='waiting';s.opponents.forEach((o,i)=>o.alive=i<2);}if(name==='失败'){s.status='lost';data.run.status='failed';}if(name==='超时'){s.status='expired';data.run.status='expired';data.run.expiresAt=Date.now()-1000;}}}
fixture('报名');
cc.assetManager.init({});
const loadBundle=name=>new Promise((resolve,reject)=>cc.assetManager.loadBundle(name,(error,bundle)=>error?reject(error):resolve(bundle)));
loadBundle('internal').then(()=>cc.game.run({id:'GameCanvas',debugMode:1,frameRate:30,renderMode:0,groupList:['default'],collisionMatrix:[[true]]},async()=>{
try {
cc.view.enableRetina(true);cc.view.resizeWithBrowserSize(true);cc.view.setDesignResolutionSize(1080,2200,cc.ResolutionPolicy.SHOW_ALL);
cc.fx={GameConfig:{GM_INFO:{...profile,uid:'fixture',username:'我',coin:100,otherLevel:0}},GameTool:{getActionName:()=> 'action11'},StorageMessage:{getStorage:()=>null,setStorage:()=>{}}};
await loadBundle('resources');await loadBundle('main');const bundle=await loadBundle('cloud_rise_home');
const runtime=window.__require('CloudRiseRuntime').default;Object.assign(runtime,service);
const prefab=await new Promise((resolve,reject)=>bundle.load('prefab/CloudRise',cc.Prefab,(error,asset)=>error?reject(error):resolve(asset)));
const scene=new cc.Scene(),canvas=new cc.Node('Canvas');scene.addChild(canvas);const comp=canvas.addComponent(cc.Canvas);comp.designResolution=cc.size(1080,2200);comp.fitHeight=true;comp.fitWidth=true;cc.director.runSceneImmediate(scene);
const backdrop=new cc.Node('PreviewHome');canvas.addChild(backdrop);const bg=backdrop.addComponent(cc.Graphics);bg.fillColor=cc.color(240,224,188);bg.rect(-540,-1100,1080,2200);bg.fill();
for(const [text,x,y] of [['首页 · 本地预览',0,1010],['体力 5',-290,880],['金币 563',290,880],['关卡地图',0,-920]]){const n=new cc.Node();backdrop.addChild(n);n.setPosition(x,y);const l=n.addComponent(cc.Label);l.string=text;l.fontSize=44;n.color=cc.color(94,68,42);}
let panelNode=null;
view={close(){if(panelNode){panelNode.destroy();panelNode=null;}},async open(){panelNode=cc.instantiate(prefab);canvas.addChild(panelNode);const panel=panelNode.getComponent('CloudRisePanel');const mode=service.unseenResult()?'result':service.run()&&service.run().status==='playing'?'progress':'overview';await panel.preparePage(mode);panel.initialize(mode,false,()=>view.close(),()=>console.log('进入主线'));}};
await view.open();window.previewReady=true;
document.getElementById('show').onclick=async()=>{view.close();fixture(document.getElementById('state').value);listeners.forEach(fn=>fn());await view.open();};
document.getElementById('win').onclick=()=>{const s=service.stage();if(!s||s.status!=='playing')return;s.success_num++;s.survivors=Math.max(1,s.survivors-8);if(s.success_num>=s.target){s.status='won';s.rewardSaved=true;s.reward=Math.ceil(Math.ceil(s.pool/s.survivors)/10)*10;data.run.status=data.run.stage===3?'completed':'waiting';}listeners.forEach(fn=>fn());};
}catch(error){console.error('Prefab preview failed',error);}
})).catch(console.error);
`;
http.createServer((req,res)=>{
const url=new URL(req.url,'http://localhost').pathname;
if(url==='/'){res.setHeader('Content-Type','text/html; charset=utf-8');return res.end(html);}
if(url==='/preview.js'){res.setHeader('Content-Type','text/javascript; charset=utf-8');return res.end(boot);}
let relative=url==='/engine.js'?engine:decodeURIComponent(url).replace(/^\//,'');
const match=relative.match(/^assets\/([^/]+)\/(config\.json|index\.js)$/);
if(match){const dir=path.join(built,'assets',match[1]);if(fs.existsSync(dir)){const name=fs.readdirSync(dir).find(n=>match[2]==='config.json'?/^config.*\.json$/.test(n):/^index.*\.js$/.test(n));if(name)relative='assets/'+match[1]+'/'+name;}}
const file=path.resolve(built,relative);
if(!file.startsWith(path.resolve(built)+path.sep)||!fs.existsSync(file)||!fs.statSync(file).isFile()){res.writeHead(404);return res.end();}
res.setHeader('Content-Type',file.endsWith('.js')?'text/javascript':file.endsWith('.json')?'application/json':file.endsWith('.png')?'image/png':'application/octet-stream');fs.createReadStream(file).pipe(res);
}).listen(port,'127.0.0.1',()=>console.log('cloudRise prefab preview http://127.0.0.1:'+port));