// Temporary QA overlay mounted only for ?pipeVerify=1 on the shared cockpit route. // It exercises the actual scene; it is removed from mapService before delivery. import { Cartesian3, Cartographic, HeadingPitchRange, sampleTerrainMostDetailed } from 'cesium' import { toPipeNetwork } from '../../src/modules/cockpit/domain/pipeNetwork.js' import { pipeColorKey } from '../../src/modules/cockpit/domain/pipeVisual.js' export function mountPipeAcceptance({ viewer, host, setViewMode, applyViewPreset }) { const params=new URLSearchParams(location.search) const originalFetch=window.fetch.bind(window),missingKind=params.get('pipeMissing') if (['valve','elbow','branch'].includes(missingKind)) { window.fetch=(input,options)=>String(input).endsWith(`/models/pipes/${missingKind}.gltf`) ? Promise.resolve(new Response('isolated missing template',{status:404})) : originalFetch(input,options) } const panel = document.createElement('aside') panel.id = 'pipe-acceptance-overlay' panel.style.cssText = 'position:fixed;left:25%;bottom:12px;z-index:1200;max-width:55vw;max-height:36vh;overflow:auto;padding:10px;background:#04121feb;color:white;font:13px monospace;border:1px solid #32c5ff' panel.innerHTML = '管体验收(开发隔离控件)
'
    const buttons = panel.querySelector('div'), output = panel.querySelector('pre')
    let network, target, range = 160, heading = 0
    const results = {}
    const emit = (key, value) => { results[key] = value; output.textContent = JSON.stringify(results, null, 2) }
    const add = (name, action) => {
        const button = document.createElement('button')
        button.textContent = name; button.style.cssText = 'margin:3px;padding:5px;color:#fff;background:#12426a;border:1px solid #328abc'
        button.onclick = () => Promise.resolve(action()).catch(error => emit('error', String(error.message || error)))
        buttons.appendChild(button)
    }
    const pipes = async () => {
        if (!network) {
            const config = await fetch('/cockpit/config.json').then(r => r.json())
            const payload = await fetch(config.api.baseUrl + '/infrastructure/geometry').then(r => r.json())
            if (payload.code !== 200) throw new Error('geometry business error')
            network = toPipeNetwork(payload.data)
        }
        return network
    }
    const aim = async (key) => {
        const data = await pipes()
        const row = key === 'MANHOLE' ? data.components.find(p => p.industry === 'MANHOLE') : data.pipes.find(p => p.diameter > 0 && pipeColorKey(p) === key)
        if (!row) throw new Error('No real target ' + key)
        const a = row.position || row.positions[0], b = row.position || row.positions.at(-1)
        const lon = (a[0]+b[0])/2, lat = (a[1]+b[1])/2
        const [point] = await sampleTerrainMostDetailed(viewer.terrainProvider, [Cartographic.fromDegrees(lon,lat)])
        target = {code:row.code,key,lon,lat,height:point.height}
        setViewMode('pipeline'); heading=0
        applyViewPreset({lon,lat,height:point.height},{heading:0,pitch:-45,range},{immediate:true})
        viewer.scene.requestRender()
        emit('target', {...target,range,remark:row.remark||'',canvas:[viewer.canvas.width,viewer.canvas.height]})
    }
    const frame = () => new Promise(resolve => {
        const detach = viewer.scene.postRender.addEventListener(() => { detach(); resolve() })
        viewer.scene.requestRender()
    })
    const capture = () => {
        const canvas=document.createElement('canvas');canvas.width=viewer.canvas.width;canvas.height=viewer.canvas.height
        const ctx=canvas.getContext('2d',{willReadFrequently:true});ctx.drawImage(viewer.canvas,0,0)
        return ctx.getImageData(0,0,canvas.width,canvas.height).data
    }
    const modelList = () => {
        const models=[]
        for(let i=0;iaim('SEWAGE'))
    add('真实雨水近景',()=>aim('RAINWATER'))
    add('真实燃气近景',()=>aim('GAS'))
    add('真实供水近景',()=>aim('WATER_SUPPLY'))
    add('窨井紫色',()=>aim('MANHOLE'))
    add('远景800m',async()=>{range=800;await aim(target?.key||'SEWAGE')})
    add('近景160m',async()=>{range=160;await aim(target?.key||'SEWAGE')})
    add('测管体像素',async()=>{
        if(!target)throw new Error('Choose target')
        const source=viewer.dataSources.getByName('cockpit-pipelines')[0],shown=source.show
        const models=modelList(),states=models.map(m=>m.show)
        source.show=false;models.forEach(m=>m.show=false)
        try{
            await frame();await frame();const before=capture()
            models.forEach((m,i)=>m.show=states[i]);await frame();await frame();const after=capture()
            const count={GAS:0,WATER_SUPPLY:0,SEWAGE:0,RAINWATER:0};let changed=0
            for(let i=0;i120&&r>g*1.7&&r>b*1.35)count.GAS++
                if(b>120&&b>r*1.7&&b>g*1.15)count.WATER_SUPPLY++
                if(g>120&&g>r*1.7&&g>b*1.3)count.SEWAGE++
                if(g>120&&b>120&&r<100&&Math.abs(g-b)<90)count.RAINWATER++}
            emit('pixels',{modelOnly:true,backgroundSubtracted:true,target:target.code,color:target.key,counts:count,changed,pass:(count[target.key]||0)>=80,canvas:[viewer.canvas.width,viewer.canvas.height]})
        }finally{models.forEach((m,i)=>m.show=states[i]);source.show=shown;viewer.scene.requestRender()}
    })
    add('原生rAF',()=>{
        const start=performance.now(),times=[]
        const next=t=>{times.push(t);if(t-start<5000){requestAnimationFrame(next);return}const secs=(times.at(-1)-times[0])/1000;emit('raf',{frames:times.length,seconds:secs,fps:(times.length-1)/secs,visible:document.visibilityState,focused:document.hasFocus()})}
        requestAnimationFrame(next)
    })
    add('4K运动FPS',()=>{
        if(!target)throw new Error('Choose target')
        const scene=viewer.scene,old=scene.requestRenderMode,start=performance.now(),times=[];let hidden=false,lostFocus=false
        scene.requestRenderMode=false
        const detach=scene.postRender.addEventListener(()=>{
            const now=performance.now();times.push(now);hidden ||= document.visibilityState!=='visible';lostFocus ||= !document.hasFocus()
            if(now-start<10000){heading=(now-start)/10000*Math.PI/4;applyViewPreset({lon:target.lon,lat:target.lat,height:target.height},{heading:heading*180/Math.PI,pitch:-45,range},{immediate:true});return}
            detach();scene.requestRenderMode=old
            const gaps=times.slice(1).map((t,i)=>t-times[i]).sort((a,b)=>a-b),secs=(times.at(-1)-times[0])/1000
            const gl=viewer.canvas.getContext('webgl2')||viewer.canvas.getContext('webgl'),debug=gl.getExtension('WEBGL_debug_renderer_info')
            emit('fps',{frames:times.length,seconds:secs,fps:(times.length-1)/secs,p95:gaps[Math.floor(gaps.length*.95)],max:gaps.at(-1),canvas:[viewer.canvas.width,viewer.canvas.height],hidden,lostFocus,renderer:debug?gl.getParameter(debug.UNMASKED_RENDERER_WEBGL):gl.getParameter(gl.RENDERER),pass:(times.length-1)/secs>=30})
        })
    })
    add('隔离四类样例',async()=>{
        const [{CustomDataSource,Color,HeightReference},{createCesiumPipeModels}]=await Promise.all([import('cesium'),import('../../src/modules/cockpit/services/cesiumPipeModels.js')])
        const lon=110.39028,lat=28.44368
        const [ground]=await sampleTerrainMostDetailed(viewer.terrainProvider,[Cartographic.fromDegrees(lon,lat)])
        const example={pipes:[{code:'ISOLATED-SPLINE',industry:'GAS',diameter:300,positions:[[lon-.00012,lat],[lon-.00005,lat],[lon,lat+.00005]]}],components:[
            {code:'ISOLATED-ELBOW',kind:'elbow',industry:'GAS',position:[lon+.00004,lat]},
            {code:'ISOLATED-VALVE',kind:'valve',industry:'GAS',position:[lon+.00007,lat]},
            {code:'ISOLATED-TEE',kind:'branch',label:'tee',industry:'GAS',position:[lon+.0001,lat]},
            {code:'ISOLATED-CROSS',kind:'branch',label:'cross',industry:'GAS',position:[lon+.00013,lat]}]}
        const source=new CustomDataSource('pipe-isolated-acceptance')
        for(const row of example.components)source.entities.add({id:'component:'+row.code,position:Cartesian3.fromDegrees(...row.position),point:{pixelSize:14,color:Color.RED,heightReference:HeightReference.CLAMP_TO_GROUND,disableDepthTestDistance:Infinity},label:{text:row.kind+'(隔离样例)',heightReference:HeightReference.CLAMP_TO_GROUND,disableDepthTestDistance:Infinity}})
        await viewer.dataSources.add(source)
        const layer=createCesiumPipeModels(viewer,{source,onChange:s=>emit('examples',{isolated:true,missingKind,instances:s.instances,batches:s.batches,pending:s.pending,failed:s.failed,fallback:s.fallback,visibleSymbols:source.entities.values.filter(e=>e.show).length,byKind:s.byKind})})
        setViewMode('pipeline');applyViewPreset({lon,lat,height:ground.height},{heading:0,pitch:-45,range:60},{immediate:true})
        layer.sync(example)
        results.isolated=true
    })
    add('收起诊断',()=>panel.hidden=true)
    host.appendChild(panel)
    return {destroy:()=>panel.remove()}
}