import {ROOT_CONTEXT,trace,SpanStatusCode} from '@opentelemetry/api';
import {TracerProvider,InMemorySpanExporter,SimpleSpanProcessor} from '@opentelemetry/sdk-trace';
export function percentile(values,p){if(!values.length)return 0;const sorted=[...values].sort((a,b)=>a-b);return sorted[Math.max(0,Math.ceil(p*sorted.length)-1)];}
export function latencySLI(values,threshold){return values.length?values.filter(v=>v<=threshold).length/values.length:1;}
const ms=t=>t[0]*1000+t[1]/1e6;
export async function runTrace(config){
 if(!['slow-db','lost-context','cardinality'].includes(config.preset))throw new Error('Choose a listed tracing scenario.');
 const safe=config.variant==='safe',exporter=new InMemorySpanExporter(),provider=new TracerProvider({spanProcessors:[new SimpleSpanProcessor({exporter})]}),tracer=provider.getTracer('zyel-trace-lab','151.032');
 const durations=[],series=new Set();let chosen;
 try{
  for(let i=0;i<8;i++){
   const root=tracer.startSpan('GET /report',{},ROOT_CONTEXT),parent=trace.setSpan(ROOT_CONTEXT,root),api=tracer.startSpan('report.service',{},parent);
   const db=tracer.startSpan('db.query orders',{},safe?trace.setSpan(parent,api):ROOT_CONTEXT);
   db.setAttribute('db.operation.name','SELECT');db.setAttribute('lab.fixture',true);
   const start=performance.now();await new Promise(r=>setTimeout(r,i===7?180:8));db.end();api.end();root.setStatus({code:SpanStatusCode.OK});root.end();durations.push(performance.now()-start);
   series.add(safe?'route=/report':`route=/report,customer_id=fixture-${i}`);if(i===7)chosen=root.spanContext().traceId;
  }
  await provider.forceFlush();const finished=exporter.getFinishedSpans();
  const selected=finished.filter(s=>s.spanContext().traceId===chosen).sort((a,b)=>ms(a.startTime)-ms(b.startTime));const min=Math.min(...selected.map(s=>ms(s.startTime)));
  const roots=finished.filter(s=>!s.parentSpanContext).length;
  const mean=durations.reduce((n,v)=>n+v,0)/durations.length,p95=percentile(durations,.95),sli=latencySLI(durations,100),target=.9;
  return {title:safe?'Follow the customer request across its spans':'Availability alone hides the wait',summary:'Actual OpenTelemetry spans surround real timer waits in this worker. The database work is an invented timed fixture. Metrics below are calculated from eight local requests, not a production monitoring window.',metrics:[['Successful responses','8 / 8'],['Mean latency',mean.toFixed(1)+' ms'],['p95 latency',p95.toFixed(1)+' ms'],['Under 100 ms',(sli*100).toFixed(1)+'%'],['Independent roots',roots],['Label sets',series.size]],spans:selected.map(s=>({name:s.name,start:ms(s.startTime)-min,duration:ms(s.duration)})),trace:[safe?'Explicit context passes the root through service and database spans. The slow query now shares the customer trace.':'The database span starts with ROOT_CONTEXT. It is exported, but is detached from the customer request.',`Availability is 100%; the latency SLI is ${(sli*100).toFixed(1)}% against this illustrative ${(target*100).toFixed(0)}% target.`,safe?'An alert can use slow-request rate and budget consumption; a mean-latency threshold may conceal the tail.':'An availability-only indicator stays green even when customers wait.',config.preset==='cardinality'?'Customer-specific metric labels create one label set per customer; bounded route labels keep the example to one. Customer identifiers belong in controlled traces or logs, not unbounded metric dimensions.':'Eight requests explain the arithmetic. A real SLO also defines eligible events, a time window, exclusions, minimum volume and alert burn rates.'],tables:[{title:'Selected customer trace',columns:['Span','Trace ID','Parent span ID','Duration ms'],rows:selected.map(s=>[s.name,s.spanContext().traceId,s.parentSpanContext?.spanId||'root',ms(s.duration).toFixed(2)])},{title:'Latency samples',columns:['Request','Actual duration ms','Meets 100 ms target'],rows:durations.map((d,i)=>[i+1,d.toFixed(2),d<=100?'yes':'no'])}]};
 }finally{await provider.shutdown();}
}
