/* ============================================================
   NeuroRoute — Developer pages: API Keys, Cost Estimator,
   Routing Explorer, Playground — all wired to live APIs.
   ============================================================ */
const D2 = window.DATA;

/* =====================  QUICKSTART  ===================== */
/* Drop-in integration card — base URL + OpenAI-SDK-compatible snippets.
   Customers swap base_url + api_key and it works. No request signing. */
function Quickstart(props){
  var apiKey = props.apiKey || 'nr_live_YOUR_KEY';
  var _tab = useState('curl'); var tab=_tab[0], setTab=_tab[1];
  var base = window.location.origin + '/v1';

  var snippets = {
    curl: 'curl '+base+'/chat/completions \\\n'+
          '  -H "Authorization: Bearer '+apiKey+'" \\\n'+
          '  -H "Content-Type: application/json" \\\n'+
          '  -d \'{\n'+
          '    "model": "auto",\n'+
          '    "messages": [{"role": "user", "content": "Hello!"}]\n'+
          '  }\'',
    python: 'from openai import OpenAI\n\n'+
            'client = OpenAI(\n'+
            '    base_url="'+base+'",\n'+
            '    api_key="'+apiKey+'",\n'+
            ')\n\n'+
            'resp = client.chat.completions.create(\n'+
            '    model="auto",   # let NeuroRoute pick the optimal model\n'+
            '    messages=[{"role": "user", "content": "Hello!"}],\n'+
            ')\n'+
            'print(resp.choices[0].message.content)',
    node: 'import OpenAI from "openai";\n\n'+
          'const client = new OpenAI({\n'+
          '  baseURL: "'+base+'",\n'+
          '  apiKey: "'+apiKey+'",\n'+
          '});\n\n'+
          'const resp = await client.chat.completions.create({\n'+
          '  model: "auto",\n'+
          '  messages: [{ role: "user", content: "Hello!" }],\n'+
          '});\n'+
          'console.log(resp.choices[0].message.content);',
  };
  var tabs = [['curl','cURL'],['python','Python'],['node','Node.js']];

  return (
    <SectionCard title="Quickstart — drop-in OpenAI compatible"
      sub="Point any OpenAI SDK at NeuroRoute by changing two lines: base URL + API key.">
      <div className="grid" style={{gap:14}}>
        <div className="grid" style={{gridTemplateColumns:'1fr 1fr',gap:12}}>
          <Field label="Base URL"><div className="row" style={{gap:8}}><code className="codeblock" style={{flex:1,padding:'9px 12px',fontSize:12.5}}>{base}</code><CopyBtn text={base}/></div></Field>
          <Field label="Model"><div className="row" style={{gap:8}}><code className="codeblock" style={{flex:1,padding:'9px 12px',fontSize:12.5}}>auto</code><span className="faint" style={{fontSize:11,alignSelf:'center'}}>or any model id</span></div></Field>
        </div>
        <div>
          <div className="seg" style={{marginBottom:10}}>
            {tabs.map(function(pair){return <button key={pair[0]} className={tab===pair[0]?'active':''} onClick={function(){setTab(pair[0]);}}>{pair[1]}</button>;})}
          </div>
          <div style={{position:'relative'}}>
            <pre className="codeblock" style={{padding:'14px 16px',fontSize:12.5,lineHeight:1.6,overflowX:'auto',margin:0,whiteSpace:'pre'}}>{snippets[tab]}</pre>
            <div style={{position:'absolute',top:10,right:10}}><CopyBtn text={snippets[tab]}/></div>
          </div>
        </div>
        <div className="faint" style={{fontSize:12.5,lineHeight:1.6}}>
          <b>Streaming</b> works too — set <code className="mono">stream: true</code>. Every response includes
          <code className="mono"> X-Routed-Model</code>, <code className="mono">X-Request-Cost</code> and
          <code className="mono"> X-Request-Id</code> headers. Paste the request ID into the Routing Explorer to see why a model was chosen.
        </div>
      </div>
    </SectionCard>
  );
}

/* =====================  API KEYS  ===================== */
function ApiKeysPage(){
  const t=useToast();
  const [loading, setLoading]=useState(true);
  const [keys, setKeys]=useState([]);
  const [modal, setModal]=useState(false);
  const [name, setName]=useState('');
  const [role, setRole]=useState('developer');
  const [fresh, setFresh]=useState(null);
  const [creating, setCreating]=useState(false);
  const [confirmRevoke, setConfirmRevoke]=useState(null);

  const loadKeys=function(){
    NR_API.listApiKeys().then(function(res){
      if(res && Array.isArray(res.keys)) {
        setKeys(res.keys.map(function(k){return {
          name: k.name, id: k.key_id, role: 'developer',
          created: k.created_at, status: k.active?'Active':'Revoked'
        };}));
      }
      setLoading(false);
    }).catch(function(){ setLoading(false); });
  };

  useEffect(loadKeys, []);

  if(loading) return <LoadingPage/>;

  const roleColor=function(r){return ({admin:'magenta',developer:'blue',viewer:'gray'})[r]||'gray';};

  const create=function(){
    if(!name) return;
    setCreating(true);
    NR_API.createApiKey(name, role).then(function(res){
      setCreating(false);
      if(res && res.key) {
        setFresh(res.key);
        setKeys(function(ks){return [{name:name, id:res.key_id||res.id||'key-'+Date.now(), key:res.key, role:role, created:new Date().toISOString(), status:'Active'}, ...ks];});
        setModal(false); setName(''); setRole('developer');
        t({title:'API key created', msg:'Copy it now — it won\'t be shown again.'});
      } else {
        t({kind:'error', title:'Failed to create key', msg:(res&&res.error&&res.error.message)||'Server returned 501 — DB key persistence not yet wired.'});
      }
    }).catch(function(){ setCreating(false); t({kind:'error', title:'Failed to create key'}); });
  };

  return (
    <div className="grid" style={{gap:18}}>
      <PageHead desc="Create and manage keys your applications use to authenticate with the NeuroRoute gateway.">
        <button className="btn btn-primary btn-sm" onClick={function(){setFresh(null);setModal(true);}}><Icon name="plus" size={15}/>Create new API key</button>
      </PageHead>

      {fresh && <div className="card card-pad" style={{background:'var(--amber-t)',borderColor:'rgba(245,158,11,.4)'}}>
        <div className="row" style={{gap:10,marginBottom:10}}><Icon name="alert" size={17} style={{color:'var(--amber)'}}/><b style={{color:'#fbc15a'}}>Save this key — it won't be shown again.</b></div>
        <div className="row" style={{gap:10}}>
          <code className="codeblock" style={{flex:1,padding:'10px 14px',fontSize:13,color:'#fbc15a'}}>{fresh}</code>
          <CopyBtn text={fresh}/>
          <button className="iconbtn" onClick={function(){setFresh(null);}}><Icon name="x" size={14}/></button>
        </div>
      </div>}

      {/* Quickstart — drop-in OpenAI-compatible integration */}
      <Quickstart apiKey={fresh}/>

      {keys.length===0 ?
        <EmptyState icon="apikey" title="No API keys yet"
          desc="Create an API key to start sending requests through the NeuroRoute gateway. Keys authenticate your requests and track usage."
          actions={[{label:'Create API Key', onClick:function(){setFresh(null);setModal(true);}, primary:true}]}/> :
        <SectionCard title="Active keys" sub={keys.filter(function(k){return k.status==='Active';}).length+' active · '+keys.length+' total'} pad={false}>
          <div className="scroll-x">
            <table className="table">
              <thead><tr><th>Name</th><th>Key ID</th><th>Role</th><th>Created</th><th>Status</th><th></th></tr></thead>
              <tbody>
                {keys.map(function(k){return (
                  <tr key={k.id} style={k.status==='Revoked'?{opacity:.5}:null}>
                    <td style={{fontWeight:600,color:'var(--tx-0)'}}>{k.name}</td>
                    <td className="num faint" style={{fontSize:12}}>{k.id}</td>
                    <td><Badge color={roleColor(k.role)} style={{textTransform:'capitalize'}}>{k.role}</Badge></td>
                    <td className="faint">{timeAgo(k.created)}</td>
                    <td><Badge color={k.status==='Active'?'green':'red'} dot>{k.status}</Badge></td>
                    <td style={{textAlign:'right'}}>
                      {k.status==='Active' &&
                        <button className="btn btn-danger btn-sm" onClick={function(){setConfirmRevoke(k);}}>Revoke</button>}
                    </td>
                  </tr>
                );})}
              </tbody>
            </table>
          </div>
        </SectionCard>
      }

      {confirmRevoke && <Modal title="Revoke API key?" sub={'This permanently disables "'+confirmRevoke.name+'". Apps using it will start failing immediately.'} onClose={function(){setConfirmRevoke(null);}}
        footer={<><button className="btn btn-ghost" onClick={function(){setConfirmRevoke(null);}}>Cancel</button>
          <button className="btn btn-danger" onClick={function(){
            var k=confirmRevoke; setConfirmRevoke(null);
            NR_API.revokeApiKey(k.id).then(function(ok){
              if(ok){
                setKeys(function(ks){return ks.map(function(x){return x.id===k.id?Object.assign({},x,{status:'Revoked'}):x;});});
                t({kind:'info',title:'Key revoked', msg:k.name});
              } else {
                t({kind:'error',title:'Failed to revoke key'});
              }
            });
          }}><Icon name="trash" size={14}/>Revoke key</button></>}>
        <p className="muted" style={{fontSize:14,margin:0}}>Key ID <code className="mono">{confirmRevoke.id}</code> will be invalidated. This cannot be undone.</p>
      </Modal>}

      {modal && <Modal title="Create API key" sub="Scope the key to a role for least-privilege access." onClose={function(){setModal(false);}}
        footer={<><button className="btn btn-ghost" onClick={function(){setModal(false);}}>Cancel</button><button className="btn btn-primary" disabled={!name||creating} onClick={create}>{creating?'Creating…':'Create key'}</button></>}>
        <div className="grid" style={{gap:16}}>
          <Field label="Key name" hint="A label to recognize this key later."><input className="input" placeholder="e.g. Production API" value={name} onChange={function(e){setName(e.target.value);}}/></Field>
          <Field label="Role">
            <div className="grid" style={{gap:8}}>
              {[['admin','Full access — manage keys, billing & config'],['developer','Use the API + view usage data'],['viewer','Read-only access to dashboards']].map(function(pair){
                var r=pair[0],d=pair[1];
                return (
                  <div key={r} className={'plan-opt'+(role===r?' sel':'')} style={{padding:'12px 14px'}} onClick={function(){setRole(r);}}>
                    <div className="spread"><span className="row" style={{gap:8}}><Badge color={roleColor(r)} style={{textTransform:'capitalize'}}>{r}</Badge></span>{role===r&&<Icon name="checkcircle" size={16} style={{color:'var(--blue)'}}/>}</div>
                    <div className="faint" style={{fontSize:12.5,marginTop:6}}>{d}</div>
                  </div>
                );
              })}
            </div>
          </Field>
        </div>
      </Modal>}
    </div>
  );
}

/* =====================  COST ESTIMATOR  ===================== */
function EstimatorPage(){
  var t=useToast();
  var _msg=useState(''); var msg=_msg[0], setMsg=_msg[1];
  var _model=useState('auto'); var model=_model[0], setModel=_model[1];
  var _strategy=useState('balanced'); var strategy=_strategy[0], setStrategy=_strategy[1];
  var _res=useState(null); var res=_res[0], setRes=_res[1];
  var _loading=useState(false); var loading=_loading[0], setLoading=_loading[1];
  var _err=useState(null); var err=_err[0], setErr=_err[1];

  var estimate=function(){
    if(!msg.trim()) return;
    setLoading(true); setErr(null);
    NR_API.estimate(msg, model, strategy).then(function(data){
      setLoading(false);
      if(data && data._ok && !data.error) {
        /* Normalize server response shape to a single internal shape */
        setRes({
          routed_model: data.selected_model || data.routed_model || (model!=='auto'?model:''),
          task_type: data.task_type || 'general',
          confidence: data.task_confidence || data.confidence || 0,
          estimated_cost: data.estimated_cost || 0,
          baseline_cost: data.counterfactual_gpt4o_cost || data.baseline_cost || 0,
          estimated_savings: data.estimated_savings || 0,
          estimated_tokens: {
            input: data.estimated_input_tokens || (data.estimated_tokens && data.estimated_tokens.input) || 0,
            output: data.estimated_output_tokens || (data.estimated_tokens && data.estimated_tokens.output) || 0,
          },
          strategy: data.strategy || strategy,
          decision_time_ms: data.decision_time_ms || 0,
          client_estimated: false,
        });
      }
      else {
        /* Fallback: client-side estimation from reference data */
        var chosen = model==='auto' ? D2.modelById('gemini-flash') : D2.modelById(model);
        var inT=Math.max(40,Math.round(msg.length/3.6)), outT=Math.round(inT*2.4+260);
        var cost=(inT*chosen.in+outT*chosen.out)/1e6;
        var baseCost=gpt4oBaseline(inT,outT);
        setRes({routed_model:chosen.id, task_type:'general', estimated_cost:cost, baseline_cost:baseCost,
          estimated_tokens:{input:inT,output:outT}, confidence:0.85, client_estimated:true});
      }
    }).catch(function(e){ setLoading(false); setErr(e.message||'Estimation failed'); });
  };

  return (
    <div className="grid" style={{gap:18}}>
      <PageHead desc="Compose a request and preview the routed model, cost, and savings before you send it."/>
      <div className="grid" style={{gridTemplateColumns:'1fr 1fr',alignItems:'start'}}>
        <SectionCard title="Compose request">
          <div className="grid" style={{gap:16}}>
            <Field label="Message"><textarea className="textarea" style={{minHeight:150}} placeholder="Refactor this Python function to use async/await..." value={msg} onChange={function(e){setMsg(e.target.value);}}/></Field>
            <div className="grid" style={{gridTemplateColumns:'1fr 1fr',gap:12}}>
              <Field label="Model"><select className="select" value={model} onChange={function(e){setModel(e.target.value);}}>
                <option value="auto">Auto (recommended)</option>
                {D2.MODELS.map(function(m){return <option key={m.id} value={m.id}>{m.name}</option>;})}</select></Field>
              <Field label="Strategy"><select className="select" value={strategy} onChange={function(e){setStrategy(e.target.value);}}>
                {D2.STRATEGIES.map(function(s){return <option key={s} value={s.toLowerCase().replace(/ /g,'-')}>{s}</option>;})}</select></Field>
            </div>
            <button className="btn btn-primary btn-block" disabled={!msg.trim()||loading} onClick={estimate}>
              <Icon name="estimator" size={16}/>{loading?'Estimating…':'Estimate cost'}
            </button>
          </div>
        </SectionCard>

        {res ? (function(){
          var chosen = D2.modelById(res.routed_model||'');
          var prov = D2.providerById(chosen.provider);
          var inT = (res.estimated_tokens&&res.estimated_tokens.input)||0;
          var outT = (res.estimated_tokens&&res.estimated_tokens.output)||0;
          var cost = res.estimated_cost||0;
          var baseCost = res.baseline_cost||gpt4oBaseline(inT,outT);
          var saved = baseCost - cost;
          var pct = baseCost>0 ? Math.round((1-cost/baseCost)*100) : 0;
          return (
            <div className="card">
              <div className="card-hd"><div className="card-title">Estimate</div>
                {res.client_estimated ? <Badge color="amber">Client-side</Badge> : <Badge color="green" dot>Server</Badge>}
              </div>
              <div style={{padding:20}}>
                <div className="card card-pad" style={{background:'var(--bg-inset)',marginBottom:16}}>
                  <div className="spread">
                    <span className="row" style={{gap:10}}>
                      <ProviderLogo id={chosen.provider} size={34}/>
                      <div><div style={{fontWeight:700}}>{chosen.name}</div><div className="faint" style={{fontSize:12}}>{prov.name} · q{chosen.q}</div></div>
                    </span>
                    <span style={{textAlign:'right'}}>
                      {res.task_type && <Badge color="blue">{res.task_type}</Badge>}
                      {res.confidence && <div className="faint mono" style={{fontSize:11,marginTop:4}}>{Math.round((res.confidence||0)*100)}% confidence</div>}
                    </span>
                  </div>
                </div>
                <div className="grid" style={{gridTemplateColumns:'1fr 1fr',gap:12,marginBottom:16}}>
                  <div className="card card-pad"><div className="faint" style={{fontSize:12}}>Input · {fmtNum(inT)} tok</div><div className="mono" style={{fontSize:17,marginTop:4}}>${(inT*chosen.in/1e6).toFixed(5)}</div></div>
                  <div className="card card-pad"><div className="faint" style={{fontSize:12}}>Output · {fmtNum(outT)} tok</div><div className="mono" style={{fontSize:17,marginTop:4}}>${(outT*chosen.out/1e6).toFixed(5)}</div></div>
                </div>
                {saved>0 && <div className="card card-pad" style={{textAlign:'center',background:'var(--green-t)',borderColor:'rgba(16,185,129,.3)'}}>
                  <div className="faint" style={{fontSize:12.5}}>You save vs GPT-4o (${baseCost.toFixed(5)})</div>
                  <div className="display route-text" style={{fontSize:34,margin:'6px 0'}}>${saved.toFixed(5)}</div>
                  <Badge color="green">{pct}% cheaper</Badge>
                </div>}
                <button className="btn btn-grad btn-block" style={{marginTop:16}} onClick={function(){go('playground');}}><Icon name="send" size={15}/>Send in Playground</button>
              </div>
            </div>
          );
        })() : (
          <div className="card empty"><div className="e-ico"><Icon name="estimator" size={26} style={{color:'var(--tx-2)'}}/></div><h3 style={{fontSize:16}}>No estimate yet</h3><p className="faint" style={{fontSize:13,maxWidth:260}}>Compose a request and hit Estimate to preview routing and savings.</p></div>
        )}
      </div>
      {err && <div className="card card-pad" style={{background:'var(--red-t)',borderColor:'rgba(239,68,68,.3)'}}><Icon name="alert" size={16}/> {err}</div>}
    </div>
  );
}

/* =====================  ROUTING EXPLORER  ===================== */
function ExplorerPage(){
  var _reqId=useState(''); var reqId=_reqId[0], setReqId=_reqId[1];
  var _data=useState(null); var data=_data[0], setData=_data[1];
  var _loading=useState(false); var loading=_loading[0], setLoading=_loading[1];
  var _error=useState(null); var error=_error[0], setError=_error[1];
  var _recent=useState([]); var recent=_recent[0], setRecent=_recent[1];

  useEffect(function(){
    NR_API.dashboardRouting(15).then(function(res){
      if(res && Array.isArray(res.decisions)) setRecent(res.decisions);
    }).catch(function(){});

    // Deep-link support: #/explorer?req=<id> (e.g. from the Playground chip)
    // auto-fills the box and traces the request immediately.
    var h = window.location.hash || '';
    var qi = h.indexOf('?');
    if(qi >= 0){
      try {
        var rid = new URLSearchParams(h.slice(qi+1)).get('req');
        if(rid){ lookup(rid); }
      } catch(e){}
    }
  },[]);

  var lookup=function(idOverride){
    var rid = (idOverride||reqId||'').trim();
    if(!rid) return;
    setReqId(rid);
    setLoading(true); setError(null); setData(null);
    NR_API.routingExplain(rid).then(function(res){
      setLoading(false);
      if(res) { setData(res); }
      else { setError('Request not found. It may have expired from the in-memory store (kept for ~5 min).'); }
    }).catch(function(){ setError('Failed to fetch routing explanation.'); setLoading(false); });
  };

  return (
    <div className="grid" style={{gap:18}}>
      <PageHead desc="Inspect exactly why a request was routed the way it was — scores, cascade trace, and cost proof."/>
      <div className="card card-pad">
        <div className="row" style={{gap:10}}>
          <div className="searchbar" style={{flex:1,maxWidth:'none'}}><Icon name="search" size={16}/><input value={reqId} onChange={function(e){setReqId(e.target.value);}} placeholder="Enter request ID (from X-Request-Id response header)" onKeyDown={function(e){if(e.key==='Enter') lookup();}}/></div>
          <button className="btn btn-primary" disabled={!reqId.trim()||loading} onClick={function(){lookup();}}>{loading?'Looking up…':'Trace request'}</button>
        </div>
      </div>

      {recent.length>0 && !data && !error && <SectionCard title="Recent routing decisions" sub="Click any request to inspect" pad={false}>
        <table className="table">
          <thead><tr><th>Time</th><th>Request ID</th><th>Model</th><th>Task</th><th style={{textAlign:'right'}}>Latency</th></tr></thead>
          <tbody>{recent.slice(0,10).map(function(r){
            var rid = r.request_id||r.RequestID||'';
            return (
              <tr key={rid} style={{cursor:'pointer'}} onClick={function(){lookup(rid);}}>
                <td className="faint">{timeAgo(r.created_at||r.CreatedAt)}</td>
                <td className="mono faint" style={{fontSize:12}}>{rid.slice(0,18)}…</td>
                <td><ModelCell id={r.model||r.Model||''}/></td>
                <td><Badge color="blue">{r.task_type||r.TaskType||'—'}</Badge></td>
                <td className="num faint" style={{textAlign:'right'}}>{(r.routing_latency_ms||r.RoutingLatencyMs||0)}ms</td>
              </tr>
            );
          })}</tbody>
        </table>
      </SectionCard>}

      {error && <div className="card card-pad" style={{background:'var(--red-t)',borderColor:'rgba(239,68,68,.3)'}}><Icon name="alert" size={16}/> {error}</div>}

      {data && (function(){
        var winner = D2.modelById(data.selected_model||data.SelectedModel||data.model||'');
        var taskType = data.task_type||data.TaskType||'general';
        var strat = data.strategy||data.Strategy||data.routing_strategy||'—';
        var conf = data.confidence||data.Confidence||0;
        var scores = data.scores||data.Scores||[];
        var cascade = data.cascade_trace||data.CascadeTrace||[];
        var cost = data.cost||data.Cost||0;
        var baseCost = data.baseline_cost||0;

        return <>
          <div className="grid" style={{gridTemplateColumns:'repeat(4,1fr)'}}>
            <div className="card card-pad"><div className="faint" style={{fontSize:12}}>Task classification</div><div style={{marginTop:8}}><Badge color="blue">{taskType}</Badge></div><div className="faint mono" style={{fontSize:11,marginTop:6}}>{Math.round(conf*100)}% confidence</div></div>
            <div className="card card-pad"><div className="faint" style={{fontSize:12}}>Strategy</div><div style={{marginTop:8,fontWeight:700}}>{strat}</div></div>
            <div className="card card-pad"><div className="faint" style={{fontSize:12}}>Routed model</div><div className="row" style={{gap:8,marginTop:8}}><ProviderLogo id={winner.provider} size={24}/><b>{winner.name}</b></div></div>
            {baseCost>0 ? <div className="card card-pad" style={{background:'var(--green-t)',borderColor:'rgba(16,185,129,.3)'}}><div className="faint" style={{fontSize:12}}>Saved vs GPT-4o</div><div className="display route-text" style={{fontSize:22,marginTop:6}}>{Math.round((1-cost/baseCost)*100)}%</div></div>
            : <div className="card card-pad"><div className="faint" style={{fontSize:12}}>Request cost</div><div className="mono" style={{fontSize:18,marginTop:6,color:'var(--green)'}}>${(+cost).toFixed(6)}</div></div>}
          </div>

          {scores.length>0 && <SectionCard title="Candidate models — scored" sub="Higher total wins" pad={false}>
            <table className="table">
              <thead><tr><th>Model</th><th style={{textAlign:'right'}}>Quality</th><th style={{textAlign:'right'}}>Cost</th><th style={{textAlign:'right'}}>Latency</th><th style={{textAlign:'right'}}>Total</th></tr></thead>
              <tbody>
                {scores.map(function(s,i){
                  var mid = s.model||s.Model||'';
                  return (
                    <tr key={mid} style={i===0?{background:'rgba(16,185,129,.08)'}:null}>
                      <td><span className="row" style={{gap:9}}><ModelCell id={mid}/>{i===0&&<Badge color="green" dot>Winner</Badge>}</span></td>
                      <td className="num" style={{textAlign:'right'}}>{(s.quality||s.Quality||0).toFixed(1)}</td>
                      <td className="num" style={{textAlign:'right'}}>{(s.cost||s.Cost||0).toFixed(1)}</td>
                      <td className="num" style={{textAlign:'right'}}>{(s.latency||s.Latency||0).toFixed(1)}</td>
                      <td className="num" style={{textAlign:'right',fontWeight:700,color:i===0?'var(--green)':'var(--tx-0)'}}>{(s.total||s.Total||0).toFixed(2)}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </SectionCard>}

          {cascade.length>0 && <SectionCard title="Cascade trace" sub="Try the cheapest model first, escalate only if quality fails">
            <div className="grid" style={{gap:10}}>
              {cascade.map(function(step,i){
                var ok = step.accepted||step.Accepted||false;
                var mid = step.model||step.Model||'';
                var note = step.reason||step.Reason||step.note||'';
                return (
                  <div key={i} className="row" style={{gap:12,padding:'12px 14px',background:'var(--bg-inset)',borderRadius:10}}>
                    <span className="badge" style={{width:24,height:24,padding:0,justifyContent:'center',borderRadius:7,background:ok?'var(--green-t)':'var(--red-t)',color:ok?'var(--green)':'#ff8095'}}>{i+1}</span>
                    <ModelCell id={mid}/>
                    <span className="faint" style={{fontSize:12.5,marginLeft:'auto'}}>{note}</span>
                    <Icon name={ok?'checkcircle':'x'} size={16} style={{color:ok?'var(--green)':'#ff8095'}}/>
                  </div>
                );
              })}
            </div>
          </SectionCard>}
        </>;
      })()}

      {!data && !error && !loading && recent.length===0 && <EmptyState icon="route" title="Inspect routing decisions"
        desc="Send a request via the Playground first — every routing decision shows up here automatically. You can also paste a request ID from any X-Request-Id response header."/>}
    </div>
  );
}

/* =====================  PLAYGROUND  ===================== */
/* Renders markdown safely. Returns sanitized HTML string. */
function renderMarkdown(text){
  if(!text) return '';
  try {
    if(window.marked && window.DOMPurify){
      window.marked.setOptions({breaks:true, gfm:true});
      var html = window.marked.parse(text);
      return window.DOMPurify.sanitize(html);
    }
  } catch(e){}
  /* Fallback: just escape + preserve newlines */
  return text.replace(/[&<>]/g, function(c){return ({'&':'&amp;','<':'&lt;','>':'&gt;'})[c];}).replace(/\n/g,'<br/>');
}

function PlaygroundPage(){
  var _model=useState('auto'); var model=_model[0], setModel=_model[1];
  var _strategy=useState('balanced'); var strategy=_strategy[0], setStrategy=_strategy[1];
  var _msgs=useState(function(){
    try { var saved=localStorage.getItem('nr_playground_msgs'); return saved?JSON.parse(saved):[]; }
    catch(e){ return []; }
  });
  var msgs=_msgs[0], setMsgs=_msgs[1];
  var _input=useState(''); var input=_input[0], setInput=_input[1];
  var _streaming=useState(false); var streaming=_streaming[0], setStreaming=_streaming[1];
  var _attach=useState(null); var attach=_attach[0], setAttach=_attach[1];
  var _estimate=useState(null); var estimate=_estimate[0], setEstimate=_estimate[1];
  var _estimating=useState(false); var estimating=_estimating[0], setEstimating=_estimating[1];
  var _activeModels=useState(D2.MODELS); var activeModels=_activeModels[0], setActiveModels=_activeModels[1];
  var _decision=useState(null); var decision=_decision[0], setDecision=_decision[1];
  var chatRef=useRef(null);
  var fileRef=useRef(null);
  var streamReaderRef=useRef(null);
  var t=useToast();

  /* Fetch health probe results and filter the model picker to only show active models.
     Falls back to the full reference list if /v1/health is unavailable. */
  useEffect(function(){
    NR_API.health().then(function(res){
      if(!res || !res.providers) return;
      var ok = D2.MODELS.filter(function(m){
        var s = res.providers[m.id];
        /* If the model isn't in the probe report at all, hide it (means it's not registered).
           If present and 'ok', show it. Anything else (error/unknown) → hide. */
        return s === 'ok';
      });
      if(ok.length>0) setActiveModels(ok);
    }).catch(function(){});
  },[]);

  /* Persist chat history (skip image attachments — too large) */
  useEffect(function(){
    try {
      var safe = msgs.map(function(m){
        if(m.attachment && m.attachment.kind==='image') {
          return Object.assign({}, m, {attachment:{kind:'image',name:m.attachment.name,size:m.attachment.size}});
        }
        return m;
      });
      localStorage.setItem('nr_playground_msgs', JSON.stringify(safe.slice(-50)));
    } catch(e){}
  }, [msgs]);

  var stopStream = function(){
    if(streamReaderRef.current) {
      try { streamReaderRef.current.cancel(); } catch(e){}
      streamReaderRef.current = null;
    }
    setStreaming(false);
  };

  var clearChat = function(){
    if(streaming) stopStream();
    setMsgs([]);
    setDecision(null);
    try { localStorage.removeItem('nr_playground_msgs'); } catch(e){}
  };

  var runEstimate = function(){
    if(!input.trim() || estimating) return;
    setEstimate(null);   /* clear previous result immediately so user sees fresh evaluation */
    setEstimating(true);
    NR_API.estimate(input.trim(), model, strategy).then(function(data){
      setEstimating(false);
      if(data && data._ok && !data.error){
        setEstimate({
          model: data.selected_model || (model!=='auto'?model:''),
          inputTokens: data.estimated_input_tokens || 0,
          outputTokens: data.estimated_output_tokens || 0,
          cost: data.estimated_cost || 0,
          baseline: data.counterfactual_gpt4o_cost || 0,
          savings: data.estimated_savings || 0,
          taskType: data.task_type || 'general',
        });
      } else {
        var msg = (data && data.error && data.error.message) || (data && data.error) || 'Server returned status '+(data&&data._status);
        t({kind:'error', title:'Estimate failed', msg: String(msg)});
      }
    }).catch(function(e){ setEstimating(false); t({kind:'error', title:'Estimate failed', msg:e&&e.message||'network error'}); });
  };

  var scrollBottom=function(){ if(chatRef.current) chatRef.current.scrollTop = chatRef.current.scrollHeight; };

  var handleFile = function(e){
    var file = e.target.files && e.target.files[0];
    if(!file) return;
    /* 10MB cap */
    if(file.size > 10*1024*1024) { t({kind:'error',title:'File too large',msg:'Max 10MB allowed.'}); return; }
    var reader = new FileReader();
    var isImage = file.type.indexOf('image/')===0;
    var isText = /\.(txt|md|csv|json|js|jsx|ts|tsx|py|go|java|rb|sh|yaml|yml|xml|html|css|sql|log)$/i.test(file.name) || file.type.indexOf('text/')===0 || file.type==='application/json';

    reader.onload = function(ev){
      if(isImage) {
        setAttach({kind:'image', name:file.name, size:file.size, dataUrl:ev.target.result, mime:file.type});
      } else if(isText) {
        setAttach({kind:'text', name:file.name, size:file.size, content:ev.target.result});
      } else {
        t({kind:'error',title:'Unsupported file',msg:'Attach images or text files (.txt, .md, .csv, .json, code).'});
      }
    };
    reader.onerror = function(){ t({kind:'error',title:'Could not read file'}); };
    if(isImage) reader.readAsDataURL(file);
    else if(isText) reader.readAsText(file);
    else { t({kind:'error',title:'Unsupported file'}); return; }
    /* Reset input so same file can be picked again */
    e.target.value = '';
  };

  var send = async function(){
    if((!input.trim() && !attach) || streaming) return;

    /* Build user message content (text or multimodal) */
    var displayText = input.trim();
    var apiContent;
    if(attach && attach.kind==='image') {
      /* Multimodal: text + image */
      apiContent = [
        {type:'text', text: displayText || 'Please describe this image.'},
        {type:'image_url', image_url:{url: attach.dataUrl}}
      ];
    } else if(attach && attach.kind==='text') {
      /* Inline text file content with the prompt */
      var label = 'Attached file: '+attach.name+'\n\n```\n'+attach.content+'\n```\n\n';
      apiContent = label + (displayText || 'Please analyze the attached file.');
    } else {
      apiContent = displayText;
    }

    var userMsg = {role:'user', content:apiContent, display:displayText, attachment:attach};
    var newMsgs = msgs.concat([userMsg]);
    setMsgs(newMsgs);
    setInput('');
    setAttach(null);
    setEstimate(null);
    setStreaming(true);
    setTimeout(scrollBottom, 50);

    /* Add placeholder assistant message */
    setMsgs(newMsgs.concat([{role:'assistant', content:'', meta:null}]));

    try {
      /* Build messages array for API */
      var apiMessages = newMsgs.map(function(m){return {role:m.role, content:m.content};});
      var modelParam = model==='auto' ? 'auto' : model;
      var strategyParam = strategy;

      var response = await NR_API.chatStream(apiMessages, modelParam, strategyParam);

      if(!response.ok) {
        var errBody = await response.json().catch(function(){return {error:{message:'Request failed ('+response.status+')'}};});
        setMsgs(function(prev){
          var copy=prev.slice();
          copy[copy.length-1] = {role:'assistant', content:'Error: '+(errBody.error?errBody.error.message:'Request failed'), error:true};
          return copy;
        });
        setStreaming(false);
        return;
      }

      /* Read response headers for routing metadata */
      var meta = {
        model: response.headers.get('X-Routed-Model') || model,
        cost: response.headers.get('X-Request-Cost'),
        requestId: response.headers.get('X-Request-Id'),
        strategy: response.headers.get('X-Routing-Strategy') || strategy,
      };

      /* Stream SSE response */
      var reader = response.body.getReader();
      streamReaderRef.current = reader;
      var decoder = new TextDecoder();
      var content = '';
      var buf = '';

      while(true) {
        var chunk = await reader.read();
        if(chunk.done) break;
        buf += decoder.decode(chunk.value, {stream:true});
        var lines = buf.split('\n');
        buf = lines.pop() || '';
        for(var li=0; li<lines.length; li++) {
          var line = lines[li];
          if(!line.startsWith('data: ')) continue;
          var payload = line.slice(6).trim();
          if(payload === '[DONE]') continue;
          try {
            var json = JSON.parse(payload);
            var delta = json.choices && json.choices[0] && json.choices[0].delta && json.choices[0].delta.content;
            if(delta) {
              content += delta;
              /* Update last message with streamed content */
              (function(c, m){
                setMsgs(function(prev){
                  var copy=prev.slice();
                  copy[copy.length-1] = {role:'assistant', content:c, meta:m};
                  return copy;
                });
              })(content, meta);
              scrollBottom();
            }
            /* Check for model info in the chunk */
            if(json.model && !meta.model) meta.model = json.model;
          } catch(pe){}
        }
      }

      /* Final update */
      setMsgs(function(prev){
        var copy=prev.slice();
        copy[copy.length-1] = {role:'assistant', content: content||'(empty response)', meta:meta};
        return copy;
      });

      /* Routing-decision cards (bottom of Playground) — populate from headers
         immediately, then enrich with task type + confidence from the explain
         endpoint (best-effort; ownership-checked server-side). */
      var dec = {model: meta.model, cost: meta.cost, strategy: meta.strategy, requestId: meta.requestId, taskType: '', confidence: 0};
      setDecision(dec);
      if(meta.requestId){
        NR_API.routingExplain(meta.requestId).then(function(res){
          if(!res) return;
          setDecision(Object.assign({}, dec, {
            taskType:   res.task_type || res.TaskType || '',
            confidence: res.confidence || res.Confidence || 0,
            strategy:   res.strategy || res.Strategy || dec.strategy,
            model:      res.selected_model || res.SelectedModel || dec.model,
          }));
        }).catch(function(){});
      }

    } catch(e) {
      setMsgs(function(prev){
        var copy=prev.slice();
        copy[copy.length-1] = {role:'assistant', content:'Error: '+e.message, error:true};
        return copy;
      });
    }
    streamReaderRef.current = null;
    setStreaming(false);
    setTimeout(scrollBottom, 50);
  };

  var handleFeedback = function(requestId, rating){
    if(!requestId) return;
    NR_API.feedback(requestId, rating);
  };

  /* RHS estimate card panel — empty state, loading state, or filled result */
  var estimatePanel = (function(){
    if(estimating){
      return (
        <div className="card card-pad" style={{textAlign:'center',padding:'40px 20px'}}>
          <div style={{display:'inline-block',width:32,height:32,border:'3px solid var(--line-2)',borderTopColor:'var(--blue)',borderRadius:'50%',animation:'spin 0.8s linear infinite'}}/>
          <div className="faint" style={{marginTop:14,fontSize:13}}>Estimating routing cost…</div>
        </div>
      );
    }
    if(!estimate){
      return (
        <div className="card card-pad" style={{textAlign:'center',padding:'32px 20px',background:'var(--bg-inset)'}}>
          <Icon name="estimator" size={26} style={{color:'var(--tx-2)',marginBottom:10}}/>
          <h3 style={{margin:'0 0 6px',fontSize:15}}>Cost estimate</h3>
          <p className="faint" style={{fontSize:12.5,lineHeight:1.55,margin:0}}>Type a prompt and click <b>Estimate</b> to preview the routed model, token counts, and savings before sending.</p>
        </div>
      );
    }
    var mInfo = D2.modelById(estimate.model||'');
    var prov = D2.providerById(mInfo.provider);
    var savings = (estimate.baseline||0) - (estimate.cost||0);
    var savingsPct = estimate.baseline>0 ? Math.round((1-estimate.cost/estimate.baseline)*100) : 0;
    return (
      <div className="grid" style={{gap:12}}>
        {/* Header card: routed model */}
        <div className="card card-pad" style={{borderLeft:'3px solid '+prov.color}}>
          <div className="row" style={{gap:10,marginBottom:10}}>
            <Icon name="estimator" size={15} style={{color:'var(--green)'}}/>
            <b style={{fontSize:13,letterSpacing:'.02em',textTransform:'uppercase'}} className="faint">Cost estimate</b>
            <Badge color="green" dot style={{marginLeft:'auto',fontSize:10}}>fresh</Badge>
          </div>
          <div className="row" style={{gap:11}}>
            <ProviderLogo id={mInfo.provider} size={36}/>
            <div className="stack" style={{flex:1,minWidth:0}}>
              <div style={{fontWeight:700,fontSize:14,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{mInfo.name}</div>
              <div className="row" style={{gap:6,marginTop:2}}>
                <span className="faint" style={{fontSize:11}}>{prov.name}</span>
                <Badge color="blue" style={{fontSize:10}}>{estimate.taskType}</Badge>
              </div>
            </div>
          </div>
        </div>

        {/* Tokens card */}
        <div className="card card-pad">
          <div className="faint" style={{fontSize:11.5,letterSpacing:'.04em',textTransform:'uppercase',marginBottom:8}}>Estimated tokens</div>
          <div className="grid" style={{gridTemplateColumns:'1fr 1fr',gap:10}}>
            <div style={{padding:'10px 12px',background:'var(--bg-inset)',borderRadius:8}}>
              <div className="faint" style={{fontSize:11}}>Input</div>
              <div className="mono" style={{fontSize:16,marginTop:2,fontWeight:700}}>{fmtNum(estimate.inputTokens)}</div>
              <div className="mono faint" style={{fontSize:10.5,marginTop:2}}>${(estimate.inputTokens*mInfo.in/1e6).toFixed(5)}</div>
            </div>
            <div style={{padding:'10px 12px',background:'var(--bg-inset)',borderRadius:8}}>
              <div className="faint" style={{fontSize:11}}>Output</div>
              <div className="mono" style={{fontSize:16,marginTop:2,fontWeight:700}}>{fmtNum(estimate.outputTokens)}</div>
              <div className="mono faint" style={{fontSize:10.5,marginTop:2}}>${(estimate.outputTokens*mInfo.out/1e6).toFixed(5)}</div>
            </div>
          </div>
        </div>

        {/* Cost card */}
        <div className="card card-pad" style={{background:'var(--bg-inset)'}}>
          <div className="spread" style={{marginBottom:10}}>
            <span className="faint" style={{fontSize:11.5,letterSpacing:'.04em',textTransform:'uppercase'}}>Total cost</span>
            <span className="mono" style={{fontSize:20,fontWeight:700,color:'var(--green)'}}>${estimate.cost.toFixed(6)}</span>
          </div>
          {estimate.baseline>0 && <div className="spread faint" style={{fontSize:12,paddingTop:8,borderTop:'1px solid var(--line)'}}>
            <span>vs GPT-4o baseline</span>
            <span className="mono">${estimate.baseline.toFixed(6)}</span>
          </div>}
        </div>

        {/* Savings card (only if positive) */}
        {savings>0 && <div className="card card-pad" style={{background:'var(--green-t)',borderColor:'rgba(16,185,129,.3)',textAlign:'center'}}>
          <div className="faint" style={{fontSize:11.5,letterSpacing:'.04em',textTransform:'uppercase',marginBottom:6}}>You save</div>
          <div className="display route-text" style={{fontSize:28,lineHeight:1.1}}>${savings.toFixed(5)}</div>
          <Badge color="green" style={{marginTop:8}}>{savingsPct}% cheaper</Badge>
        </div>}

        <button className="btn btn-grad btn-block" disabled={streaming} onClick={function(){send();}}>
          <Icon name="send" size={14}/>Send through this model
        </button>
      </div>
    );
  })();

  return (
    <div className="grid" style={{gap:18}}>
      <PageHead desc="Chat against the gateway. Every response streams from real models with routing metadata.">
        <select className="select" style={{width:180}} value={model} onChange={function(e){setModel(e.target.value); if(estimate) setEstimate(null);}}>
          <option value="auto">Auto (recommended)</option>
          {activeModels.map(function(m){return <option key={m.id} value={m.id}>{m.name}</option>;})}
        </select>
        <select className="select" style={{width:150}} value={strategy} onChange={function(e){setStrategy(e.target.value); if(estimate) setEstimate(null);}}>
          {D2.STRATEGIES.map(function(s){return <option key={s} value={s.toLowerCase().replace(/ /g,'-')}>{s}</option>;})}
        </select>
        <button className="btn btn-ghost btn-sm" onClick={clearChat}>Clear</button>
      </PageHead>
      <div className="grid playground-2col" style={{gridTemplateColumns:'minmax(0, 1fr) 340px',gap:14,alignItems:'start'}}>
      <div className="card" style={{display:'flex',flexDirection:'column',height:'62vh',minHeight:440}}>
        <div ref={chatRef} style={{flex:1,overflowY:'auto',padding:'22px'}}>
          <div className="grid" style={{gap:18,maxWidth:760,margin:'0 auto'}}>
            {msgs.length===0 && <div style={{display:'flex',alignItems:'center',justifyContent:'center',height:'100%',minHeight:300}}>
              <div style={{textAlign:'center',maxWidth:400}}>
                <Icon name="playground" size={32} style={{color:'var(--tx-2)',marginBottom:12}}/>
                <h3 style={{margin:'0 0 8px'}}>NeuroRoute Playground</h3>
                <p className="faint" style={{lineHeight:1.6}}>Send a message to chat with AI models through the NeuroRoute gateway. Responses stream in real-time from actual model APIs.</p>
              </div>
            </div>}

            {msgs.map(function(m,i){
              var mInfo = m.meta && m.meta.model ? D2.modelById(m.meta.model) : null;
              var isUser = m.role==='user';
              var displayContent = isUser ? (m.display||(typeof m.content==='string'?m.content:'')) : (m.content||'');
              /* renderMarkdown() sanitizes via DOMPurify before returning HTML — safe to inject */
              var sanitizedHtml = !isUser && displayContent ? renderMarkdown(displayContent) : (streaming && !isUser && i===msgs.length-1 ? '<span style="opacity:.6">Thinking…</span>' : '');
              return (
                <div key={i} style={{alignSelf:isUser?'flex-end':'flex-start',maxWidth:'82%'}}>
                  {isUser && m.attachment && m.attachment.kind==='image' && <div style={{marginBottom:8,textAlign:'right'}}>
                    <img src={m.attachment.dataUrl} alt={m.attachment.name} style={{maxWidth:260,maxHeight:200,borderRadius:10,border:'1px solid var(--line)'}}/>
                  </div>}
                  {isUser && m.attachment && m.attachment.kind==='text' && <div style={{marginBottom:8,textAlign:'right'}}>
                    <span className="chip"><Icon name="layers" size={13}/>{m.attachment.name}</span>
                  </div>}
                  {isUser ? (
                    <div style={{padding:'13px 16px',borderRadius:14,fontSize:14,lineHeight:1.6,whiteSpace:'pre-wrap',
                      background:'var(--grad-brand)', color:'#fff',
                      borderBottomRightRadius:4, borderBottomLeftRadius:14}}>
                      {displayContent || (m.attachment ? <span style={{opacity:.85}}>({m.attachment.kind==='image'?'image':'file'} attached)</span> : '')}
                    </div>
                  ) : (
                    <div className="md-content" style={{padding:'13px 16px',borderRadius:14,fontSize:14,lineHeight:1.6,
                      background: m.error?'var(--red-t)':'var(--bg-3)', color:'var(--tx-0)',
                      border:'1px solid var(--line)', borderBottomLeftRadius:4, borderBottomRightRadius:14}}
                      dangerouslySetInnerHTML={{__html: sanitizedHtml}}/>
                  )}
                  {m.meta && mInfo && !isUser && <div className="row" style={{gap:8,marginTop:8,flexWrap:'wrap'}}>
                    <span className="chip"><ProviderLogo id={mInfo.provider} size={16}/>{mInfo.name}</span>
                    {m.meta.cost && <span className="chip mono">${parseFloat(m.meta.cost).toFixed(4)}</span>}
                    {m.meta.requestId && <span className="chip mono" style={{cursor:'pointer'}} onClick={function(){go('explorer?req='+encodeURIComponent(m.meta.requestId));}} title="Trace this request in the Routing Explorer">{m.meta.requestId.slice(0,12)}…</span>}
                    <button className="iconbtn" style={{width:28,height:28}} onClick={function(){handleFeedback(m.meta.requestId,'thumbs_up');}} title="Good response"><Icon name="thumbup" size={13}/></button>
                    <button className="iconbtn" style={{width:28,height:28}} onClick={function(){handleFeedback(m.meta.requestId,'thumbs_down');}} title="Poor response"><Icon name="thumbdown" size={13}/></button>
                  </div>}
                </div>
              );
            })}
          </div>
        </div>
        <div style={{borderTop:'1px solid var(--line)',padding:16}}>
          <div style={{maxWidth:760,margin:'0 auto'}}>
            {attach && <div className="row" style={{gap:8,marginBottom:10,padding:'8px 10px',background:'var(--bg-inset)',borderRadius:10}}>
              {attach.kind==='image' ? <img src={attach.dataUrl} style={{width:36,height:36,objectFit:'cover',borderRadius:6}}/> : <Icon name="layers" size={18} style={{color:'var(--tx-2)'}}/>}
              <div className="stack" style={{flex:1,minWidth:0}}>
                <span style={{fontSize:13,fontWeight:600,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{attach.name}</span>
                <span className="faint" style={{fontSize:11}}>{attach.kind==='image' ? 'Image' : 'Text file'} · {(attach.size/1024).toFixed(1)} KB</span>
              </div>
              <button className="iconbtn" onClick={function(){setAttach(null);}} title="Remove attachment"><Icon name="x" size={14}/></button>
            </div>}
            <div className="row" style={{gap:10}}>
              <input ref={fileRef} type="file" style={{display:'none'}} onChange={handleFile} accept="image/*,.txt,.md,.csv,.json,.js,.jsx,.ts,.tsx,.py,.go,.java,.rb,.sh,.yaml,.yml,.xml,.html,.css,.sql,.log,text/*"/>
              <button className="iconbtn" onClick={function(){if(fileRef.current) fileRef.current.click();}} disabled={streaming} title="Attach file or image" style={{width:42,height:42}}>
                <Icon name="plus" size={18}/>
              </button>
              <input className="input" style={{flex:1}} placeholder="Message NeuroRoute…" value={input} onChange={function(e){setInput(e.target.value); if(estimate) setEstimate(null);}} onKeyDown={function(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send();}}} disabled={streaming}/>
              <button className="btn btn-soft" disabled={!input.trim()||estimating} onClick={runEstimate} title="Preview cost before sending — works even while streaming">
                <Icon name="estimator" size={15}/>{estimating?'Estimating…':'Estimate'}
              </button>
              {streaming ?
                <button className="btn btn-stop" onClick={stopStream} title="Stop streaming"><Icon name="x" size={15}/>Stop</button> :
                <button className="btn btn-grad" disabled={!input.trim()&&!attach} onClick={send}><Icon name="send" size={16}/>Send</button>
              }
            </div>
          </div>
        </div>
      </div>
      {/* RHS: estimate panel */}
      <div style={{position:'sticky',top:18}}>
        {estimatePanel}
      </div>
      </div>

      {/* Routing decision for the latest request — updates on every send. */}
      {decision && (function(){
        var mInfo = D2.modelById(decision.model||'');
        var hasCost = decision.cost != null && decision.cost !== '';
        return (
          <div className="grid" style={{gridTemplateColumns:'repeat(auto-fit, minmax(180px, 1fr))', gap:14}}>
            <div className="card card-pad">
              <div className="eyebrow">Task classification</div>
              <div style={{marginTop:8}}>{decision.taskType ? <Badge color="blue">{decision.taskType}</Badge> : <span className="faint">—</span>}</div>
              {decision.confidence>0 && <div className="faint mono" style={{fontSize:11,marginTop:6}}>{Math.round(decision.confidence*100)}% confidence</div>}
            </div>
            <div className="card card-pad">
              <div className="eyebrow">Strategy</div>
              <div style={{marginTop:8,fontWeight:700}}>{decision.strategy||'—'}</div>
            </div>
            <div className="card card-pad">
              <div className="eyebrow">Routed model</div>
              <div className="row" style={{gap:8,marginTop:8}}><ProviderLogo id={mInfo.provider} size={24}/><b>{mInfo.name}</b></div>
            </div>
            <div className="card card-pad">
              <div className="eyebrow">Request cost</div>
              <div className="mono" style={{fontSize:18,marginTop:8,color:'var(--green)'}}>{hasCost ? '$'+parseFloat(decision.cost).toFixed(6) : '—'}</div>
              {decision.requestId && <div className="mono faint" style={{fontSize:10.5,marginTop:6,cursor:'pointer'}} onClick={function(){go('explorer?req='+encodeURIComponent(decision.requestId));}} title="Open full trace in Routing Explorer">{decision.requestId.slice(0,12)}… ↗</div>}
            </div>
          </div>
        );
      })()}
    </div>
  );
}

window.PAGES = Object.assign(window.PAGES||{}, {apikeys:ApiKeysPage, estimator:EstimatorPage, explorer:ExplorerPage, playground:PlaygroundPage});
