/* Bee Smart Operations Portal - vNext canon prototype: all in-scope screens.
   Composed from the proven ops-portal kit primitives (OpsKit.jsx, preserved
   verbatim) over our REAL fleet (OurData.jsx). Each screen carries a ReviewNote
   stating what is DS-faithful, what is a deliberate delta, and what renders `-`.
   Forks locked in s353: labeled-cluster Network, real MapLibre map kept, superset
   Devices columns, per-sensor detail route, MiniStat/KStat Growers, "Stale" label. */

const dash = v => (v==null||v==='') ? <span style={{fontFamily:O.mono,color:O.faint}}>-</span> : v;

// styles (lifted from the kit so the prototype is pixel-faithful)
const th={textAlign:'center',padding:'10px 24px',fontFamily:O.mono,fontSize:10,fontWeight:600,letterSpacing:'.06em',textTransform:'uppercase',color:O.fg3,whiteSpace:'nowrap'};
const td={padding:'11px 24px',verticalAlign:'middle',whiteSpace:'nowrap',textAlign:'center'};
const tdM={padding:'11px 24px',verticalAlign:'middle',whiteSpace:'nowrap',fontFamily:O.mono,fontSize:12,color:O.fg2,textAlign:'center'};
const chip={background:'#fff',borderWidth:1,borderStyle:'solid',borderColor:O.line,borderRadius:999,padding:'7px 13px',cursor:'pointer',fontFamily:O.sans,fontWeight:600,fontSize:12.5,color:O.fg2};
const chipOn={background:O.azure,borderColor:O.azure,color:'#fff'};
const primBtn={display:'inline-flex',alignItems:'center',gap:7,background:O.azure,color:'#fff',border:'none',borderRadius:9,padding:'9px 15px',cursor:'pointer',fontFamily:O.sans,fontWeight:700,fontSize:13};
const ghostBtn={display:'inline-flex',alignItems:'center',gap:7,background:'#fff',color:O.fg1,border:`1px solid ${O.lineS}`,borderRadius:9,padding:'9px 15px',cursor:'pointer',fontFamily:O.sans,fontWeight:600,fontSize:13};
const miniBtn={background:'#fff',border:`1px solid ${O.lineS}`,borderRadius:7,padding:'5px 11px',cursor:'pointer',fontFamily:O.sans,fontWeight:600,fontSize:12,color:O.fg2};
const linkBtn={background:'none',border:'none',cursor:'pointer',color:O.azure,fontFamily:O.sans,fontWeight:700,fontSize:12.5};

// ---- Review-note callout (NOT part of the DS; prototype chrome only) ----
function ReviewNote({ title, faithful=[], delta=[], absent=[], flag }){
  if(PROD_PORTAL) return null; // prototype-only chrome; hidden when served as production
  const Row=({label,items,c})=> items.length? <div style={{display:'flex',gap:8,marginTop:6}}>
    <span style={{flexShrink:0,fontFamily:O.mono,fontSize:9.5,fontWeight:700,letterSpacing:'.05em',textTransform:'uppercase',color:c,minWidth:78}}>{label}</span>
    <span style={{fontFamily:O.sans,fontSize:12.5,color:O.fg2}}>{items.join(' · ')}</span>
  </div> : null;
  return <div style={{border:`1px dashed ${O.azure}`,background:O.azure05,borderRadius:12,padding:'12px 15px',marginBottom:16}}>
    <div style={{display:'flex',alignItems:'center',gap:8,fontFamily:O.sans,fontWeight:700,fontSize:12.5,color:O.azure7}}>
      <OIcon name="clipboard-check" size={15}/>Review note - {title}
    </div>
    <Row label="DS-faithful" items={faithful} c={O.ok}/>
    <Row label="Delta" items={delta} c={O.azure7}/>
    <Row label="Renders -" items={absent} c={O.warn}/>
    {flag && <div style={{display:'flex',gap:8,marginTop:8,paddingTop:8,borderTop:`1px solid ${O.azure1}`}}>
      <span style={{flexShrink:0,fontFamily:O.mono,fontSize:9.5,fontWeight:700,letterSpacing:'.05em',textTransform:'uppercase',color:O.crit,minWidth:78}}>Decide</span>
      <span style={{fontFamily:O.sans,fontSize:12.5,color:O.fg1,fontWeight:600}}>{flag}</span>
    </div>}
  </div>;
}

function Meta({ label, v, c }){ return <div style={{textAlign:'center'}}><div style={{fontFamily:O.mono,fontSize:10,fontWeight:600,letterSpacing:'.05em',textTransform:'uppercase',color:O.fg3}}>{label}</div><div style={{fontFamily:O.sans,fontWeight:700,fontSize:13.5,color:c||O.fg1,marginTop:3}}>{v}</div></div>; }
function KStat({ n, l, c, onClick }){ return <div onClick={onClick} className={onClick?'clk':undefined} title={onClick?'View in Devices / Alerts':undefined} style={{border:`1px solid ${O.line}`,borderRadius:10,padding:'11px 12px',textAlign:'center',cursor:onClick?'pointer':'default'}}><div style={{fontFamily:O.disp,fontWeight:800,fontSize:22,color:c,lineHeight:1}}>{n}</div><div style={{fontFamily:O.mono,fontSize:9.5,color:O.fg1,textTransform:'uppercase',letterSpacing:'.05em',marginTop:4}}>{l}</div></div>; }
function MiniStat({ n, l, c }){ return <div style={{flex:1,border:`1px solid ${O.line}`,borderRadius:9,padding:'8px 4px',textAlign:'center'}}><div style={{fontFamily:O.disp,fontWeight:800,fontSize:16,color:c}}>{n}</div><div style={{fontFamily:O.mono,fontSize:11,color:O.fg3,textTransform:'uppercase',letterSpacing:'.04em',marginTop:2}}>{l}</div></div>; }

// ================= OVERVIEW =================
// Faithful reproduction of the deployed production Overview (app/page.tsx): same KPI
// set incl "Readings (24h)", FleetHealth donuts + offline/stale/low-battery/sites
// strip, active-alert feed, caption footer. s366: 24h sparklines added to the KPI tiles.

// s366: Overview KPI sparkline. Ports lib/ui/Spark.tsx + chart-math.sparkPath into the
// canon (browser-Babel, no bundler). 24h trend from the listSeries 30m buckets. A flat
// baseline renders for <2 points or a constant series (placeholder where no history).
function sparkPath(data,w,h){
  if(!data||data.length<2){const y=h/2;return {line:`M0 ${y} L${w} ${y}`,area:`M0 ${y} L${w} ${y} L${w} ${h} L0 ${h} Z`};}
  const max=Math.max(...data),min=Math.min(...data);
  if(max===min){const y=h/2;return {line:`M0 ${y} L${w} ${y}`,area:`M0 ${y} L${w} ${y} L${w} ${h} L0 ${h} Z`};}
  const rng=max-min;
  const pts=data.map((v,i)=>[(i/(data.length-1))*w, h-((v-min)/rng)*(h-4)-2]);
  const line=pts.map((p,i)=>(i?'L':'M')+p[0].toFixed(1)+' '+p[1].toFixed(1)).join(' ');
  return {line, area:`${line} L${w} ${h} L0 ${h} Z`};
}
function Spark({ data, color }){
  const w=120,h=30; const c=color||O.azure; const flat=!data||data.length<2;
  const {line,area}=sparkPath(data,w,h);
  const gid='sk'+React.useId().replace(/:/g,'');
  return <svg width="100%" height={h} viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{display:'block',overflow:'visible',marginTop:10,opacity:flat?0.4:1}} aria-hidden="true">
    <defs><linearGradient id={gid} x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor={c} stopOpacity="0.22"/><stop offset="1" stopColor={c} stopOpacity="0"/></linearGradient></defs>
    {!flat&&<path d={area} fill={`url(#${gid})`}/>}
    <path d={line} fill="none" stroke={c} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" strokeDasharray={flat?'3 3':undefined}/>
  </svg>;
}
function KpiTile({ label, value, unit, accent, big, onClick, spark }){
  return <Card pad={16} onClick={onClick} className={onClick?'clk':undefined} style={{cursor:'pointer'}}>
    <div style={{fontFamily:O.mono,fontSize:10.5,fontWeight:600,letterSpacing:'.08em',textTransform:'uppercase',color:O.fg3,textAlign:'center'}}>{label}</div>
    <div style={{display:'flex',alignItems:'baseline',justifyContent:'center',gap:5,marginTop:big?14:10}}><span style={{fontFamily:O.disp,fontWeight:800,fontSize:big?44:32,letterSpacing:'-0.02em',color:accent||O.fg1,lineHeight:1}}>{value}</span>{unit&&<span style={{fontFamily:O.sans,fontWeight:600,fontSize:14,color:O.fg3}}>{unit}</span>}</div>
    {spark!==undefined&&<Spark data={spark} color={accent||O.azure}/>}
  </Card>;
}
function Overview({ onNav, onDevices }){
  const rows=DEVICES;
  const on=rows.filter(d=>d.status==='online').length;
  const tier=k=>{const a=rows.filter(d=>d.kind===k);return {on:a.filter(d=>d.status==='online').length,total:a.length};};
  const g=tier('gateway'),r=tier('router'),s=tier('sensor');
  const uptime=(on/rows.length*100).toFixed(1);
  const offline=rows.filter(d=>d.status==='offline').length;
  const stale=rows.filter(d=>d.status==='stale').length;
  const low=rows.filter(d=>d.battery!=null&&d.battery>0&&d.battery<=25).length;
  const gateways=rows.filter(d=>d.kind==='gateway').length;
  // s366: 24h KPI trends from the listSeries 30m buckets (window.SERIES = [{messages,devices}]).
  // Real for Readings (volume), Devices Online (distinct devices/bucket), Uptime (devices/total);
  // Active Alerts has no stored history -> no sparkline (s368 review), just the number.
  const series=(typeof SERIES!=='undefined'&&Array.isArray(SERIES))?SERIES:[];
  const tReadings=series.map(p=>p.messages);
  const tOnline=series.map(p=>p.devices);
  const tUptime=series.map(p=>rows.length?Math.min(100,p.devices/rows.length*100):0);
  return <div>
    <PageTitle title="Operations Overview"/>
    {!PROD_PORTAL && <div style={{border:`1px dashed ${O.idle}`,background:O.idleBg,borderRadius:12,padding:'10px 14px',marginBottom:16,fontFamily:O.sans,fontSize:12.5,color:O.fg2,display:'flex',alignItems:'center',gap:8}}><OIcon name="mouse-pointer-click" size={14}/>Production Overview layout - KPI + Fleet-Health cards are now <b style={{color:O.fg1,margin:'0 4px'}}>clickable + floating</b> (deep-link to Devices / Alerts / Growers) per user direction.</div>}
    {/* KPI row - exact production labels + order */}
    <div style={{display:'grid',gridTemplateColumns:'repeat(4,1fr)',gap:14,marginBottom:14}} className="ops-kpi">
      <KpiTile label="24HR Uptime" value={uptime} unit="%" accent={parseFloat(uptime)>=99.9?O.ok:O.crit} spark={tUptime} onClick={()=>onDevices('all')}/>
      <KpiTile label="Devices Online" value={on} accent={O.ok} spark={tOnline} onClick={()=>onDevices('online')}/>
      <KpiTile label="Readings (24h)" value={READINGS24H!=null?READINGS24H.toLocaleString():'-'} accent={READINGS24H!=null?O.azure7:O.faint} spark={tReadings} onClick={()=>onNav('topology')}/>
      <KpiTile label="Active Alerts" value={activeAlerts.length} accent={activeAlerts.length>0?O.crit:O.ok} big onClick={()=>onNav('alerts')}/>
    </div>
    <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:14}} className="ops-2col">
      <Card>
        <CardHead title="Fleet Health"/>
        <div style={{display:'grid',gridTemplateColumns:'repeat(3,1fr)',gap:14}}>
          {[['Gateways',g,'router','gateway'],['Routers',r,'share-2','router'],['Sensors',s,'radio','sensor']].map(([lbl,d,ic,kind])=>(
            <div key={lbl} onClick={()=>onDevices({kind})} className="clk" title={`View ${lbl} in Devices`} style={{border:`1px solid ${O.line}`,borderRadius:11,padding:'14px 14px 12px',display:'flex',flexDirection:'column',alignItems:'center',gap:8,cursor:'pointer'}}>
              <Donut value={d.on} total={d.total} color={d.on===d.total?O.leaf:d.on/d.total>0.5?O.warn:O.crit} size={88}/>
              <div style={{textAlign:'center'}}><div style={{fontFamily:O.sans,fontWeight:700,fontSize:13.5,color:O.fg1,display:'flex',alignItems:'center',gap:6,justifyContent:'center'}}><OIcon name={ic} size={15} color={O.azure}/>{lbl}</div><div style={{fontFamily:O.mono,fontSize:13,color:O.fg1,marginTop:4}}>{d.on} / {d.total}</div></div>
            </div>
          ))}
        </div>
        <div style={{display:'flex',gap:10,marginTop:14}}>
          {[[SITES.length,'sites',O.ok,()=>onNav('growers')],[offline,'offline',offline>0?O.crit:O.leaf,()=>onDevices({stat:'offline'})],[stale,'stale',stale>0?O.warn:O.leaf,()=>onDevices({stat:'stale'})],[low,'low battery',low>0?O.warn:O.leaf,()=>onDevices({stat:'low'})]].map(([n,l,c,go],i)=>(
            <div key={i} onClick={go} className="clk" style={{flex:1,border:`1px solid ${O.line}`,borderRadius:9,padding:'8px 10px',textAlign:'center',cursor:'pointer'}}><div style={{fontFamily:O.disp,fontWeight:800,fontSize:19,color:c}}>{n}</div><div style={{fontFamily:O.mono,fontSize:9.5,color:O.fg3,textTransform:'uppercase',letterSpacing:'.05em',marginTop:1}}>{l}</div></div>
          ))}
        </div>
      </Card>
      <Card onClick={()=>onNav('alerts')} className="clk" style={{display:'flex',flexDirection:'column',cursor:'pointer'}}>
        <CardHead title="Active Alerts" sub={`${activeAlerts.length} need attention`} right={<span style={{display:'inline-flex',alignItems:'center',gap:3,fontFamily:O.sans,fontWeight:700,fontSize:11.5,color:O.azure,whiteSpace:'nowrap'}}>View all<OIcon name="chevron-right" size={14}/></span>}/>
        {activeAlerts.length===0
          ? <div style={{flex:1,display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',gap:8,padding:'24px 10px'}}><span style={{color:O.leaf}}><OIcon name="shield-check" size={34}/></span><div style={{fontFamily:O.sans,fontWeight:700,fontSize:14,color:O.fg1}}>All clear</div><div style={{fontFamily:O.sans,fontSize:12,color:O.fg3,textAlign:'center'}}>No active alerts. Mesh fresh; batteries healthy.</div></div>
          : <div style={{display:'flex',flexDirection:'column',gap:8}}>{activeAlerts.slice(0,4).map(a=>{ const s=SEV[a.severity]||SEV.info;
              return <div key={a.id} style={{display:'flex',alignItems:'center',gap:10,padding:'9px 11px',border:`1px solid ${O.line}`,borderLeft:`3px solid ${s.c}`,borderRadius:9}}>
                <span style={{color:s.c,flexShrink:0}}><OIcon name={s.icon} size={17}/></span>
                <div style={{flex:1,minWidth:0}}><div style={{fontFamily:O.sans,fontWeight:700,fontSize:12.5,color:O.fg1}}>{a.title}</div><div style={{fontFamily:O.mono,fontSize:10,color:O.fg3}}>{siteName(a.site)}{a.node?' · '+a.node:''}</div></div>
                <span style={{fontFamily:O.mono,fontSize:9.5,fontWeight:700,letterSpacing:'.05em',textTransform:'uppercase',color:s.c,background:s.bg,padding:'2px 7px',borderRadius:999,flexShrink:0}}>{s.label}</span>
              </div>;})}</div>}
      </Card>
    </div>
  </div>;
}

// ================= DEVICES (kit columns superset + reading columns) =================
function Devices({ onDevice, initial }){
  const init=typeof initial==='string'?{stat:initial}:(initial||{});
  const [kind,setKind]=React.useState(init.kind||'all');
  const [stat,setStat]=React.useState(init.stat||'all');
  const [site,setSite]=React.useState(init.site||'all');
  const [addDev,setAddDev]=React.useState(false);
  const [removing,setRemoving]=React.useState(null);
  const matchStat=d=> stat==='all'?true : stat==='low'?(d.battery!=null&&d.battery>0&&d.battery<=25) : d.status===stat;
  const filtered=DEVICES.filter(d=>(kind==='all'||d.kind===kind)&&(site==='all'||d.site===site)&&matchStat(d));
  const tabs=[['all','All',DEVICES.length],['gateway','Gateways',DEVICES.filter(d=>d.kind==='gateway').length],['router','Routers',DEVICES.filter(d=>d.kind==='router').length],['sensor','Sensors',DEVICES.filter(d=>d.kind==='sensor').length]];
  return <div>
    <PageTitle title="Devices" sub={`${DEVICES.length} devices across ${SITES.length} site${SITES.length===1?'':'s'}`}
      right={<div style={{display:'flex',gap:8}}><button className="clk" style={ghostBtn}><OIcon name="download" size={15}/>Export</button><button onClick={()=>setAddDev(true)} className="clk" style={primBtn}><OIcon name="plus" size={15}/>Add Device</button></div>}/>
    <ReviewNote title="Devices" faithful={["kit columns (Device/Type/Site/Status/Battery/Last seen)","filter chips (closes PM-158)","kit header bg + paddings","Add Device + Export buttons"]} delta={["SUPERSET: real per-channel reading columns (Temp/Humidity/Baro) kept after the kit columns","whole-row drill-in","Add/Remove device (reversible front-end unlink, PM-184)","Low-battery filter chip + site-scoped deep-link from the Grower-detail tiles (NEW)","device column left-aligned + indented by tier (Gateway > Router > Sensors) for clear level separation (s356)","Signal column HIDDEN from the table (s356) -- the RSSI data field is RETAINED in the data layer; re-add the column when PM-160 lands real data","Last seen column moved next to Status (left of Battery) (s359)"]} absent={["Signal/RSSI not collected yet (PM-160)"]}/>
    <div style={{display:'flex',gap:8,marginBottom:14,flexWrap:'wrap',alignItems:'center'}}>
      {tabs.map(([k,l,n])=><button key={k} className="clk" onClick={()=>setKind(k)} style={{...chip,...(kind===k?chipOn:{})}}>{l}<span style={{fontFamily:O.mono,fontSize:10.5,opacity:.7,marginLeft:6}}>{n}</span></button>)}
      <span style={{width:1,height:22,background:O.line,margin:'0 4px'}}/>
      {[['all','All'],['online','Online'],['stale','Stale'],['offline','Offline'],['low','Low battery']].map(([k,l])=><button key={k} className="clk" onClick={()=>setStat(k)} style={{...chip,...(stat===k?chipOn:{})}}>{l}</button>)}
      {site!=='all'&&<button className="clk" onClick={()=>setSite('all')} title="Clear site filter" style={{...chip,background:O.leaf,borderColor:O.leaf,color:'#fff',display:'inline-flex',alignItems:'center',gap:6}}>{siteName(site)}<OIcon name="x" size={13}/></button>}
    </div>
    <Card pad={0} style={{overflow:'hidden'}}>
      <div style={{overflowX:'auto'}}>
      <table style={{width:'100%',borderCollapse:'collapse',minWidth:900}}>
        <thead><tr style={{background:O.sunken}}>
          {['Device','Type','Site','Status','Last seen','Battery','Temp','Humidity','Baro','',''].map((h,i)=><th key={i} style={i===0?{...th,textAlign:'left',paddingLeft:24}:th}>{h}</th>)}
        </tr></thead>
        <tbody>
          {filtered.map(d=>{ const rd=d.reading||{}; const lvl=d.kind==='gateway'?0:d.kind==='router'?1:2; // device hierarchy indent: Gateway > Router > Sensors
            return <tr key={d.id} onClick={()=>onDevice(d)} style={{cursor:'pointer',borderTop:`1px solid ${O.line}`}} className="ops-row">
              <td style={{...td,textAlign:'left',paddingLeft:24+lvl*34}}><div style={{display:'flex',alignItems:'center',justifyContent:'flex-start',gap:10}}><span style={{width:28,height:28,borderRadius:7,background:O.azure05,color:O.azure,display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}><OIcon name={KIND[d.kind].icon} size={15}/></span><div style={{textAlign:'left'}}><div style={{fontFamily:O.sans,fontWeight:700,fontSize:13,color:O.fg1}}>{d.name}</div><div style={{fontFamily:O.mono,fontSize:10.5,color:O.fg3}}>{d.node}{d.uuid?' · '+d.uuid.slice(0,8):''}</div></div></div></td>
              <td style={td}><span style={{fontFamily:O.sans,fontSize:12.5,color:O.fg2}}>{KIND[d.kind].label}</span></td>
              <td style={td}><span style={{fontFamily:O.sans,fontSize:12.5,color:O.fg1}}>{siteName(d.site)}</span></td>
              <td style={td}><StatusPill status={d.status} sm/></td>
              <td style={tdM}><span style={{color:d.lastSeen!=null&&d.lastSeen>120?O.crit:O.fg1}}>{fmtAgo(d.lastSeen)}</span></td>
              <td style={td}>{d.battery!=null?<Battery pct={d.battery}/>:dash(null)}</td>
              <td style={tdM}>{dash(rd.tempF!=null?rd.tempF+'°':null)}</td>
              <td style={tdM}>{dash(rd.humidity!=null?rd.humidity+'%':null)}</td>
              <td style={tdM}>{dash(rd.pressure!=null?rd.pressure:null)}</td>
              <td style={td}><button onClick={e=>{e.stopPropagation();setRemoving(d);}} title="Remove from view" style={{background:'none',border:'none',cursor:'pointer',color:O.faint,display:'flex'}}><OIcon name="trash-2" size={15}/></button></td>
              <td style={td}><OIcon name="chevron-right" size={16} color={O.faint}/></td>
            </tr>;
          })}
        </tbody>
      </table>
      </div>
    </Card>
    {addDev&&<AddDeviceModal onClose={()=>setAddDev(false)}/>}
    {removing&&<RemoveDialog kind="Device" name={removing.name} onClose={()=>setRemoving(null)}/>}
  </div>;
}

// ================= DEVICE DETAIL (per-sensor route + mesh pill) =================
function DeviceDetail({ device, onDevice }){
  const d=device;
  const parent=DEVICES.find(x=>x.id===d.parent);
  const gw=d.kind==='gateway'?d:DEVICES.find(x=>x.kind==='gateway'&&x.site===d.site);
  const children=DEVICES.filter(x=>x.parent===d.id);
  const meshSensors = d.kind==='gateway' ? DEVICES.filter(x=>x.kind==='sensor'&&x.site===d.site) : []; // s359: gateway Connected Devices shows downstream sensors as small status squares (DS pattern OpsScreens2)
  const rd=d.reading||(d.kind==='gateway'?(d.weather?{tempF:d.weather.tempF,pressure:d.weather.pressure,humidity:d.weather.humidity}:{tempF:null,pressure:null,humidity:null}):null); // s359: a gateway (incl. offline standalone) always shows the weather tiles -- '-' when no data
  const metrics=[];
  if(rd){
    metrics.push(['Temperature',rd.tempF!=null?rd.tempF:null,'°F','thermometer']);
    metrics.push(['Pressure',rd.pressure!=null?rd.pressure:null,'inHg','gauge']);
    metrics.push(['Humidity',rd.humidity!=null?rd.humidity:null,'%','droplets']);
    if(d.kind==='sensor'){ metrics.push(['Noise',rd.noise!=null?rd.noise:null,'dB','volume-2']); metrics.push(['Air quality',null,'AQI','wind']); } // s358: Noise is live (sensors only); Air quality stays '-' (gas frozen, PM-180)
  }
  const stale = d.lastSeen!=null&&d.lastSeen>120;
  const leaf = d.kind!=='gateway'; // sensor + router share the title-row tile treatment (s357)
  // s357: on the sensor + router drill-downs, Battery + Last seen become their own tiles on the
  // title row alongside any readings (readings to the right); they leave the meta grid below.
  const tileSty={border:`1px solid ${O.line}`,borderRadius:10,padding:'7px 12px',minWidth:76,textAlign:'center'};
  const tileLbl={display:'flex',alignItems:'center',justifyContent:'center',gap:5,color:O.fg3,fontFamily:O.mono,fontSize:9,fontWeight:600,letterSpacing:'.04em',textTransform:'uppercase'};
  const tileVal={fontFamily:O.disp,fontWeight:800,fontSize:17,marginTop:3,display:'flex',alignItems:'center',justifyContent:'center',minHeight:21};
  const isGw = d.kind==='gateway'; // s359: gateway gets the tile-row treatment too
  const tileCount=2+metrics.length; // leaf title row: Battery + Last seen + readings (gateway uses a fixed 3-col grid below)
  // s368 PM-184 review (owner direction): reusable tile renderers so the gateway and leaf
  // rows compose in their own order. Gateway row reordered -- weather (Temp/Humidity/Pressure)
  // on top, Battery/Last seen/Elevation below; the GPS tile is dropped (Lat/Long lives on the
  // map); the Last seen value renders lighter than the metric values. leaf/isGw exclusive.
  const wxTile=(lbl,val,unit,ic)=>(
    <div key={lbl} style={tileSty}>
      <div style={tileLbl}><OIcon name={ic} size={12}/>{lbl}</div>
      <div style={{...tileVal,color:val==null?O.faint:O.fg1}}>{val==null?'-':val}{val!=null&&<span style={{fontFamily:O.sans,fontSize:10,fontWeight:600,color:O.fg3}}> {unit}</span>}</div>
    </div>
  );
  const batteryTile=(
    <div key="battery" style={tileSty}>
      <div style={tileLbl}><OIcon name="battery" size={12}/>Battery</div>
      <div style={{...tileVal,color:O.fg1}}>{d.battery!=null?<Battery pct={d.battery}/>:dash(null)}</div>
    </div>
  );
  // s368 review (owner direction): the Last seen value renders lighter (600) than the
  // metric values (800) on every device detail -- gateway and leaf alike.
  const lastSeenTile=()=>(
    <div key="lastseen" style={{...tileSty,containerType:'inline-size'}}>
      <div style={tileLbl}><OIcon name="clock" size={12}/><span className="ls-full">Last seen</span><span className="ls-short">Last</span></div>
      <div style={{...tileVal,fontWeight:600,color:stale?O.crit:O.fg1,whiteSpace:'nowrap'}}>{fmtAgo(d.lastSeen)}</div>
    </div>
  );
  const elevationTile=(
    <div key="elevation" style={tileSty}>
      <div style={tileLbl}><OIcon name="mountain" size={12}/>Elevation</div>
      <div style={{...tileVal,color:O.fg1}}>{d.elevationFt!=null?d.elevationFt.toLocaleString()+' ft':'-'}</div>
    </div>
  );
  const tileEls = isGw ? <>
    {wxTile('Temp', rd?rd.tempF:null, '°F', 'thermometer')}
    {wxTile('Humidity', rd?rd.humidity:null, '%', 'droplets')}
    {wxTile('Pressure', rd?rd.pressure:null, 'inHg', 'gauge')}
    {batteryTile}
    {lastSeenTile()}
    {elevationTile}
  </> : <>
    {batteryTile}
    {lastSeenTile()}
    {metrics.map(([lbl,val,unit,ic])=>wxTile(lbl,val,unit,ic))}
  </>;
  const headerBody = <>
    <div style={{display:'flex',gap:16,alignItems:'center',flexWrap:'wrap'}}>
      <span style={{width:52,height:52,borderRadius:12,background:O.azure05,color:O.azure,display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}><OIcon name={KIND[d.kind].icon} size={26}/></span>
      <div style={{minWidth:200,flexShrink:0}}>
        <div style={{display:'flex',alignItems:'center',gap:10,flexWrap:'wrap'}}><h1 style={{fontFamily:O.disp,fontWeight:800,fontSize:23,letterSpacing:'-0.01em',color:O.fg1,margin:0}}>{d.name}</h1><StatusPill status={d.status}/></div>
        <div style={{fontFamily:O.mono,fontSize:12,color:O.fg3,marginTop:4}}>{d.kind==='gateway' ? siteName(d.site) : <>{d.uuid||d.node} · {KIND[d.kind].label} · {siteName(d.site)}</>}</div>
      </div>
      {leaf&&<div style={{display:'grid',gridTemplateColumns:`repeat(${tileCount},minmax(0,1fr))`,gap:8,flex:1,marginLeft:16,alignItems:'stretch'}}>{tileEls}</div>}
    </div>
    {/* s359: gateway tiles render in a 3-column grid BELOW the title (Last seen/Battery/GPS on top,
        weather below) -- wider cards, and the gateway card is narrow (shares the row with the map)
        so a single row beside the title overlapped. No second meta-grid section: faint divider +
        Firmware + Signal were removed per owner direction. */}
    {isGw&&<div style={{display:'grid',gridTemplateColumns:'repeat(3,minmax(0,1fr))',gap:8,marginTop:14}}>{tileEls}</div>}
  </>;
  return <div>
    <ReviewNote title={`Device detail - ${d.name}`} faithful={["header card + meta strip","Mesh Path pill (sensor)","Event Log card"]} delta={["NEW per-sensor route /devices/[gw]/[node] (closes the s338 gap)","real readings where collected","gateway detail shows a live MapLibre map (real GPS, OpenStreetMap tiles) (s366 PM-192)","readings moved INLINE next to the title; Live Readings card removed (s356)","meta strip reordered Battery > Last seen > Mesh hops > Firmware > Signal; Link dropped (s356)","sensor + router drill-downs share the same header look: Battery + Last seen are their own tiles on the title row beside any readings (readings to the right), dropped from the meta grid below; router gains the Mesh Path card; leaf header is now a SINGLE row of equal-width, evenly-spaced tiles (second meta-grid section removed -- Mesh hops/Firmware/Signal dropped from the leaf view) (s357)","gateway drill-down now shows a 3-column tile grid below the title: Last seen / Battery / GPS (stacked lat over long, 2 dp) on top, weather (Temp/Pressure/Humidity) below; its second meta-grid section + faint divider + Firmware + Signal removed; the meta line under the name is the location only (id + device-type dropped); the standalone retail gateway shows the same Last seen/Battery/weather tiles, rendered '-' while offline (s359)","gateway Connected Devices card adds the downstream sensors as small status squares (green online / red offline, DS cluster pattern) that drill into the sensor detail; the router card is unchanged (s359)"]} absent={["Signal (PM-160)","Firmware (PM-175)","sparkline 24h trend (real listChannelSeries wired in prod; single snapshot here)","Logs/Ping/Configure (no device-command backend)"]} flag={null}/>
    {d.kind==='gateway'
      ? <div style={{display:'grid',gridTemplateColumns:'1.4fr 1fr',gap:14,marginBottom:14,alignItems:'stretch'}} className="ops-2col">
          {/* s358: mesh path merged INTO the top gateway card. Standalone gateways (no mesh) drop
              the "Mesh Path" label and show just the pills (Gateway -> Cloud). */}
          <Card>
            {headerBody}
            <div style={{marginTop:14,paddingTop:14,borderTop:`1px solid ${O.line}`}}>
              {!isStandalone(d.site)&&<div style={{display:'flex',alignItems:'center',gap:6,fontFamily:O.mono,fontSize:9.5,fontWeight:600,letterSpacing:'.06em',textTransform:'uppercase',color:O.fg3,marginBottom:9}}><OIcon name="share-2" size={12}/>Mesh Path</div>}
              <MeshPath siteId={d.site} icons/>
            </div>
          </Card>
          <Card style={{display:'flex',flexDirection:'column'}}>
            <CardHead title="Location" icon="map-pin" sub={d.gps?'Gateway GPS · '+d.gps:'No GPS fix reported'}/>
            <div style={{flex:1,minHeight:200}}><RealMapPanel gps={d.gps} elevationFt={d.elevationFt} label={siteName(d.site)}/></div>
          </Card>
        </div>
      : <Card style={{marginBottom:14}}>{headerBody}</Card>}
    {/* s358: leaf devices (sensor + router) keep the standalone Mesh Path card; gateway now carries it inline above */}
    {(d.kind==='sensor'||d.kind==='router')&&<Card style={{marginBottom:14}}>
      <CardHead title="Mesh Path" icon="share-2"/>
      <MeshPath siteId={d.site} icons/>
    </Card>}
    {children.length>0
      ? <div style={{display:'grid',gridTemplateColumns:'1.4fr 1fr',gap:14,alignItems:'start'}} className="ops-2col">
          <Card>
            {/* s365: "Connected Devices" header + icon removed (user direction). The Router card is
                unchanged; the sensors now sit in their OWN card styled like the Router card, with the
                small status squares (green online / red offline) inside it. */}
            <div style={{display:'flex',gap:14,alignItems:'stretch',flexWrap:'wrap'}}>
              {children.map(c=><button key={c.id} className="clk" onClick={()=>onDevice(c)} style={{display:'flex',alignItems:'center',gap:10,padding:'9px 11px',border:`1px solid ${O.line}`,borderRadius:9,cursor:'pointer',background:'#fff',textAlign:'left',minWidth:200,flexShrink:0}}><span style={{color:O.azure}}><OIcon name={KIND[c.kind].icon} size={15}/></span><div style={{flex:1,minWidth:0}}><div style={{fontFamily:O.sans,fontWeight:600,fontSize:12.5,color:O.fg1}}>{c.name}</div><div style={{fontFamily:O.mono,fontSize:10,color:O.fg3}}>{c.node}</div></div><StatusPill status={c.status} sm/></button>)}
              {meshSensors.length>0&&<div style={{display:'flex',flexDirection:'column',gap:9,padding:'9px 11px',border:`1px solid ${O.line}`,borderRadius:9,background:'#fff',minWidth:200,flexShrink:0,justifyContent:'center'}}>
                <span style={{fontFamily:O.mono,fontSize:9.5,fontWeight:600,letterSpacing:'.06em',textTransform:'uppercase',color:O.fg3}}>Sensors</span>
                <div style={{display:'flex',gap:7,alignItems:'center',flexWrap:'wrap'}}>
                  {meshSensors.map(s=><button key={s.id} onClick={()=>onDevice(s)} title={`${s.name} · ${STATUS[s.status].label}${s.battery!=null?' · '+s.battery+'%':''}`} style={{width:26,height:26,borderRadius:6,border:`1px solid ${STATUS[s.status].c}33`,background:STATUS[s.status].bg,color:STATUS[s.status].c,cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center'}}><OIcon name="radio" size={12}/></button>)}
                </div>
              </div>}
            </div>
          </Card>
          <Card>
            <CardHead title="Event Log" icon="list"/>
            <div style={{fontFamily:O.sans,fontSize:12.5,color:O.fg3,padding:'8px 0'}}>Wired to real <span style={{fontFamily:O.mono,fontSize:11}}>deviceEvents</span> in production. No events in this point-in-time snapshot.</div>
          </Card>
        </div>
      : <Card>
          <CardHead title="Event Log" icon="list"/>
          <div style={{fontFamily:O.sans,fontSize:12.5,color:O.fg3,padding:'8px 0'}}>Wired to real <span style={{fontFamily:O.mono,fontSize:11}}>deviceEvents</span> in production. No events in this point-in-time snapshot.</div>
        </Card>}
  </div>;
}

// ================= NETWORK (labeled-cluster hybrid) =================
function Topology({ onDevice, onGrower }){
  return <div>
    <PageTitle title="Network Operations"/>
    <ReviewNote title="Network" faithful={["status palette + legend (Online / Offline / Stale)","clickable site header -> grower"]} delta={["each site card now shows the deployment Mesh Path spine (Sensors > Router > Gateway > Cloud) - the same reusable component as the Growers cards (s356 user direction)","legend moved to the top, trimmed to the 3 node states + card sized to content"]} absent={[]}/>
    <div style={{display:'inline-flex',gap:18,alignItems:'center',flexWrap:'wrap',background:O.raised,border:`1px solid ${O.line}`,borderRadius:14,boxShadow:'0 1px 2px rgba(22,32,43,0.04)',padding:'12px 18px',marginBottom:14}}>
      {['online','offline','stale'].map(k=>{const s=STATUS[k];return <span key={k} style={{display:'flex',alignItems:'center',gap:7,fontFamily:O.sans,fontWeight:600,fontSize:12.5,color:O.fg2}}><span style={{width:13,height:13,borderRadius:4,background:s.bg,border:`1px solid ${s.c}`}}/>{s.label}</span>;})}
    </div>
    <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(360px,1fr))',gap:14}}>
      {SITES.map(st=>{
        const gw=DEVICES.find(d=>d.kind==='gateway'&&d.site===st.id);
        const sensors=DEVICES.filter(d=>d.kind==='sensor'&&d.site===st.id);
        const son=sensors.filter(s=>s.status==='online').length;
        const dark=gw&&gw.status!=='online';
        return <Card key={st.id} pad={16} onClick={()=>onGrower&&onGrower(st.id)} className="clk" style={{borderColor:O.line,display:'flex',flexDirection:'column',minHeight:176,cursor:'pointer'}}>
          <div style={{display:'flex',alignItems:'center',gap:9,marginBottom:12,width:'100%',textAlign:'left'}}>
            <div style={{flex:1,minWidth:0}}><div style={{fontFamily:O.sans,fontWeight:700,fontSize:14,color:O.fg1}}>{st.name}</div><div style={{fontFamily:O.mono,fontSize:10.5,color:O.fg3}}>{son}/{sensors.length} Sensors online</div></div>
            <span style={{display:'inline-flex',alignItems:'center',gap:3,fontFamily:O.sans,fontWeight:700,fontSize:11.5,color:O.azure,whiteSpace:'nowrap'}}>All data<OIcon name="chevron-right" size={14}/></span>
          </div>
          <div style={{display:'flex',gap:8}}>
            <MiniStat n={gw&&gw.status==='online'?'●':'○'} l="gateway" c={dark?O.crit:O.leaf}/>
            <MiniStat n={st.acres!=null?st.acres:'-'} l="acres" c={st.acres!=null?O.fg1:O.faint}/>
            <div style={{flex:1,border:`1px solid ${O.line}`,borderRadius:9,padding:'8px 4px',textAlign:'center'}}>
              <div style={{fontFamily:O.disp,fontWeight:800,fontSize:16}}><span style={{color:son<sensors.length?O.crit:O.ok}}>{son}</span><span style={{color:O.ok}}>/{sensors.length}</span></div>
              <div style={{fontFamily:O.mono,fontSize:11,color:O.fg3,textTransform:'uppercase',letterSpacing:'.04em',marginTop:2}}>Sensors</div>
            </div>
          </div>
          <div style={{marginTop:10}}>
            <MeshPath siteId={st.id}/>
          </div>
        </Card>;
      })}
    </div>
  </div>;
}

// ================= ALERTS (derived; honest empty) =================
function Alerts({ onDevice, extra=[] }){
  const [f,setF]=React.useState('active');
  const [sev,setSev]=React.useState(null); // s358: severity filter driven by the clickable top summary cards
  const allAlerts=[...extra,...ALERTS];      // extra = client-side synthetic alerts (e.g. Portal data delayed)
  const allActive=[...extra,...activeAlerts];
  const list=allAlerts.filter(a=>(f==='all'||a.status===f)&&(!sev||a.severity===sev));
  return <div>
    <PageTitle title="Alerts & Notifications" sub="Fleet-wide events from the live snapshot"
      right={<button className="clk" style={ghostBtn} disabled><OIcon name="settings-2" size={15}/>Alert rules</button>}/>
    <ReviewNote title="Alerts" faithful={["3-card severity summary","filter chips","alert list cards"]} delta={["wired to real deriveAlerts(snapshot): offline device -> Critical, <=25% battery -> Warning (s356)","severity summary cards are clickable filters (toggle); alert cards open the alerting device's drill-down (s358)"]} absent={["Info severity / Ack / Resolve lifecycle (PM-161)"]}/>
    <div style={{display:'grid',gridTemplateColumns:'repeat(3,1fr)',gap:14,marginBottom:16}} className="ops-kpi">
      {[['critical','Critical',O.crit,'alert-octagon'],['warning','Warning',O.warn,'alert-triangle'],['info','Info',O.info,'info']].map(([k,l,c,ic])=>(
        <Card key={l} pad={15} onClick={()=>setSev(sev===k?null:k)} className="clk" title={sev===k?'Clear '+l+' filter':'Filter to '+l} style={{borderLeft:`3px solid ${c}`,cursor:'pointer',...(sev===k?{outline:`2px solid ${c}`,outlineOffset:'-1px'}:{})}}><div style={{display:'flex',justifyContent:'space-between',alignItems:'center'}}><div><div style={{fontFamily:O.disp,fontWeight:800,fontSize:28,color:c,lineHeight:1}}>{allActive.filter(a=>a.severity===k).length}</div><div style={{fontFamily:O.sans,fontWeight:600,fontSize:12.5,color:O.fg3,marginTop:3}}>{l} · active</div></div><span style={{color:c}}><OIcon name={ic} size={26}/></span></div></Card>
      ))}
    </div>
    <div style={{display:'flex',gap:8,marginBottom:14}}>
      {[['active','Active'],['all','All']].map(([k,l])=><button key={k} className="clk" onClick={()=>setF(k)} style={{...chip,...(f===k?chipOn:{})}}>{l}<span style={{fontFamily:O.mono,fontSize:10.5,opacity:.7,marginLeft:6}}>{k==='active'?allActive.length:allAlerts.length}</span></button>)}
    </div>
    {list.length===0
      ? <Card style={{textAlign:'center',padding:'48px 20px'}}><span style={{color:O.leaf}}><OIcon name="shield-check" size={40}/></span><div style={{fontFamily:O.disp,fontWeight:800,fontSize:18,color:O.fg1,marginTop:10}}>All clear</div><div style={{fontFamily:O.sans,fontSize:13,color:O.fg3,marginTop:4}}>No active alerts. The G1 mesh is fresh and batteries are healthy.</div></Card>
      : <div style={{display:'flex',flexDirection:'column',gap:10}}>
          {list.map(a=>{ const s=SEV[a.severity]||SEV.info; const dev=DEVICES.find(d=>d.node===a.node&&d.site===a.site); // s358: alert -> alerting device drill-down
            return <Card key={a.id} pad={14} onClick={dev&&onDevice?()=>onDevice(dev):undefined} className={dev&&onDevice?'clk':undefined} style={{borderLeft:`3px solid ${s.c}`,cursor:dev&&onDevice?'pointer':'default'}}>
              <div style={{display:'flex',alignItems:'flex-start',gap:12}}>
                <span style={{width:34,height:34,borderRadius:9,background:s.bg,color:s.c,display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}><OIcon name={s.icon} size={18}/></span>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{display:'flex',alignItems:'center',gap:8,flexWrap:'wrap'}}><span style={{fontFamily:O.sans,fontWeight:700,fontSize:13.5,color:O.fg1}}>{a.title}</span><span style={{fontFamily:O.mono,fontSize:9.5,fontWeight:700,letterSpacing:'.05em',textTransform:'uppercase',color:s.c,background:s.bg,padding:'2px 7px',borderRadius:999}}>{s.label}</span></div>
                  <div style={{fontFamily:O.sans,fontSize:12.5,color:O.fg2,marginTop:3}}>{a.detail}</div>
                  <div style={{fontFamily:O.mono,fontSize:10.5,color:O.fg3,marginTop:5}}>{siteName(a.site)}{a.node?' · '+a.node:''}</div>
                </div>
              </div>
            </Card>;})}
        </div>}
  </div>;
}

// ================= INTEGRATIONS =================
function Integrations(){
  return <div>
    <PageTitle title="Integrations" sub="Connected infrastructure & external operations consoles"/>
    <ReviewNote title="Integrations" faithful={["kit card order (UpCloud/InfluxDB/Soracom/nRF Cloud/Grafana)","role line under name","roadmap footer banner"]} delta={["whole card opens the vendor console in a new tab; per-card Open-console button removed + external-link icon added (s366)","console URLs wired: UpCloud Hub / nRF Cloud / Soracom Console / Grafana; InfluxDB (self-hosted) has no public console -> non-clickable (s366)","per-component health: UpCloud/InfluxDB/Grafana shown Operational (verified via the live data path); Soracom/nRF Cloud await live probes (s358)","the sidebar platform pill derives from these (green unless degraded/down)"]} absent={["live per-component health for Soracom + nRF Cloud (PM-159)"]}/>
    <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(260px,1fr))',gap:14}}>
      {INTEGRATIONS.map(it=>(
        // s366: whole card opens the console in a new tab (the per-card "Open console" button
        // was removed). Cards without a console URL (InfluxDB - self-hosted) stay non-clickable.
        <Card key={it.id} onClick={it.url?()=>window.open(it.url,'_blank','noopener,noreferrer'):undefined} className={it.url?'clk':undefined} style={{display:'flex',flexDirection:'column',gap:12,cursor:it.url?'pointer':'default'}}>
          <div style={{display:'flex',alignItems:'center',gap:11}}>
            <span style={{width:40,height:40,borderRadius:10,background:O.azure05,color:O.azure,display:'flex',alignItems:'center',justifyContent:'center'}}><OIcon name={it.icon} size={20}/></span>
            <div style={{flex:1}}><div style={{fontFamily:O.sans,fontWeight:700,fontSize:15,color:O.fg1}}>{it.name}</div><div style={{fontFamily:O.sans,fontSize:12,color:O.fg3}}>{it.role}</div></div>
            {it.url&&<OIcon name="external-link" size={15} color={O.azure}/>}
          </div>
          {(()=>{ const up=it.status==='up'; const c=up?O.leaf:it.status==='degraded'?O.goldDeep:it.status==='down'?O.crit:O.faint;
            const lbl=up?'Operational':it.status==='degraded'?'Degraded':it.status==='down'?'Down':'Health -';
            return <div style={{display:'flex',alignItems:'center',gap:7}}><span style={{width:8,height:8,borderRadius:'50%',background:c}}/><span style={{fontFamily:O.sans,fontWeight:600,fontSize:12.5,color:c}}>{lbl}</span><span style={{fontFamily:O.mono,fontSize:11,color:O.faint,marginLeft:'auto'}} title={it.note||undefined}>{it.status?'verified':'PM-159'}</span></div>; })()}
        </Card>
      ))}
    </div>
    <Card style={{marginTop:14,display:'flex',alignItems:'center',gap:13,background:O.azure05,borderColor:O.azure1}}>
      <span style={{color:O.azure}}><OIcon name="info" size={20}/></span>
      <div style={{fontFamily:O.sans,fontSize:13,color:O.fg2}}>These consoles open in their native dashboards. Deep metric/log federation into this portal (per-card live health) is planned - see PM-159.</div>
    </Card>
  </div>;
}

// ---- Mesh Path (reusable site spine: Sensors -> Router -> Gateway -> Cloud) ----
// A labeled, status-coloured chain rendered at the deployment level. No icons; it
// ALWAYS lays out as a single row (.mesh-path CSS: flex + container-query font/pill
// sizing) so it fits any container - narrow grower card or wide detail card alike.
// Standalone gateways (no router/sensors) collapse to Gateway -> Cloud.
function MeshPath({ siteId, h, icons }){
  const gw=DEVICES.find(d=>d.kind==='gateway'&&d.site===siteId);
  const router=DEVICES.find(d=>d.kind==='router'&&d.site===siteId);
  const sensors=DEVICES.filter(d=>d.kind==='sensor'&&d.site===siteId);
  const son=sensors.filter(s=>s.status==='online').length;
  const hops=[];
  if(sensors.length){ const s=son===sensors.length?STATUS.online:STATUS.offline; // any sensor not online -> the aggregate card goes red (no amber middle state)
    hops.push(['Sensors',son+'/'+sensors.length+' online',s.c,s.bg,'radio']); }
  if(router){ const s=STATUS[router.status]; hops.push(['Router',router.node,s.c,s.bg,'share-2']); }
  if(gw){ const s=STATUS[gw.status]; hops.push(['Gateway',gw.node,s.c,s.bg,'router']); }
  const cloudOk=gw&&gw.status==='online';
  hops.push(['Cloud','LTE',cloudOk?O.leaf:O.fg3,cloudOk?O.okBg:O.sunken,'cloud']);
  return <div className="mesh-path" style={h?{height:h}:undefined}>
    {hops.map((n,i,arr)=><React.Fragment key={i}>
      <div className="mp-pill" style={{border:`1.5px solid ${n[2]}55`,background:n[3]}}>
        {icons&&<span style={{color:n[2],marginBottom:3,display:'flex'}}><OIcon name={n[4]} size={16}/></span>}
        <span className="mp-t" style={{fontFamily:O.sans,color:O.fg1}}>{n[0]}</span>
        <span className="mp-s" style={{fontFamily:O.mono,color:O.fg3}}>{n[1]}</span>
      </div>
      {i<arr.length-1&&<span className="mp-sep" style={{color:O.fg3}}>{'>'}</span>}
    </React.Fragment>)}
  </div>;
}

// ================= GROWERS (list) =================
function Growers({ onGrower, onSensors, onSaved }){
  const [add,setAdd]=React.useState(false);
  const [addDev,setAddDev]=React.useState(false); // after Add Grower -> immediately open Add Device
  const [removing,setRemoving]=React.useState(null);
  const [editing,setEditing]=React.useState(null);
  return <div>
    <PageTitle title="Growers" sub={`${SITES.length} Grower Deployments`}
      right={<button onClick={()=>setAdd(true)} className="clk" style={primBtn}><OIcon name="plus" size={15}/>Add Grower</button>}/>
    <ReviewNote title="Growers list" faithful={["kit MiniStat cards (gateway dot · acres · Sensors on/total)","card grid","chevron drill-in"]} delta={["grower/retail type chip kept (real)","Add/Remove grower (reversible ownership soft-flag, PM-183)","Mesh Path on each grower card (NEW)"]} absent={["acres / crop / region (no tenant-metadata store)"]}/>
    <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(310px,1fr))',gap:14}}>
      {SITES.map(st=>{
        const ds=DEVICES.filter(d=>d.site===st.id);
        const sensors=ds.filter(d=>d.kind==='sensor');
        const son=sensors.filter(s=>s.status==='online').length;
        const gw=ds.find(d=>d.kind==='gateway');
        const dark=gw&&gw.status!=='online';
        return <div key={st.id} role="button" tabIndex={0} className="clk" onClick={()=>onGrower(st.id)} style={{textAlign:'left',background:O.raised,border:`1px solid ${O.line}`,borderRadius:14,padding:16}}>
          <div style={{display:'flex',alignItems:'flex-start',gap:9,marginBottom:12}}>
            <div style={{flex:1,minWidth:0}}>
              <div style={{display:'flex',alignItems:'center',gap:8}}><span style={{fontFamily:O.sans,fontWeight:700,fontSize:15,color:O.fg1}}>{st.name}</span><span style={{fontFamily:O.mono,fontSize:9,fontWeight:700,letterSpacing:'.05em',textTransform:'uppercase',color:st.kind==='commercial'?O.leaf:O.azure,background:st.kind==='commercial'?O.leaf05:O.azure05,padding:'2px 7px',borderRadius:999}}>{st.kind}</span></div>
              <div style={{fontFamily:O.sans,fontSize:12,color:O.fg3,marginTop:2}}>{dash(st.crop)} · {dash(st.region)} · {st.site}</div>
            </div>
            <button onClick={e=>{e.stopPropagation();setEditing(st);}} title="Edit grower" style={{background:'none',border:'none',cursor:'pointer',color:O.faint,display:'flex'}}><OIcon name="pencil" size={15}/></button>
            <button onClick={e=>{e.stopPropagation();setRemoving(st);}} title="Remove from view" style={{background:'none',border:'none',cursor:'pointer',color:O.faint,display:'flex'}}><OIcon name="trash-2" size={15}/></button>
            <OIcon name="chevron-right" size={17} color={O.faint}/>
          </div>
          <div style={{display:'flex',gap:8}}>
            <MiniStat n={gw&&gw.status==='online'?'●':'○'} l="gateway" c={dark?O.crit:O.leaf}/>
            <MiniStat n={st.acres!=null?st.acres:'-'} l="acres" c={st.acres!=null?O.fg1:O.faint}/>
            <div onClick={e=>{e.stopPropagation();onSensors&&onSensors(st.id);}} title="View sensors" className="clk" style={{flex:1,border:`1px solid ${O.line}`,borderRadius:9,padding:'8px 4px',textAlign:'center'}}>
              <div style={{fontFamily:O.disp,fontWeight:800,fontSize:16}}><span style={{color:son<sensors.length?O.crit:O.ok}}>{son}</span><span style={{color:O.ok}}>/{sensors.length}</span></div>
              <div style={{fontFamily:O.mono,fontSize:11,color:O.fg3,textTransform:'uppercase',letterSpacing:'.04em',marginTop:2}}>Sensors</div>
            </div>
          </div>
          <div style={{marginTop:10}}>
            <MeshPath siteId={st.id}/>
          </div>
        </div>;
      })}
    </div>
    {add&&<AddGrowerModal onClose={()=>setAdd(false)} onAdded={()=>{ setAdd(false); setAddDev(true); }}/>}
    {addDev&&<AddDeviceModal onClose={()=>setAddDev(false)}/>}
    {removing&&<RemoveDialog kind="Grower" name={removing.name} onClose={()=>setRemoving(null)}/>}
    {editing&&<EditGrowerModal site={editing} onClose={()=>setEditing(null)} onSaved={onSaved}/>}
  </div>;
}

// ================= GROWER DETAIL =================
function GrowerDetail({ siteId, onDevice, onDevices, onAlerts, onSaved }){
  const st=SITES.find(s=>s.id===siteId)||SITES[0];
  const ds=DEVICES.filter(d=>d.site===siteId);
  const gw=ds.find(d=>d.kind==='gateway');
  const router=ds.find(d=>d.kind==='router');
  const sensors=ds.filter(d=>d.kind==='sensor');
  const son=sensors.filter(s=>s.status==='online').length;
  const devOn=ds.filter(d=>d.status==='online').length;
  const devOff=ds.filter(d=>d.status!=='online').length;
  const devLow=ds.filter(d=>d.battery!=null&&d.battery>0&&d.battery<=25).length;
  const siteAlerts=activeAlerts.filter(a=>a.site===siteId).length;
  const dark=gw&&gw.status!=='online';
  const [edit,setEdit]=React.useState(false);
  return <div>
    <ReviewNote title={`Grower detail - ${st.name}`} faithful={["grower Card + KStat 4-tiles","gateway + router kit detail cards","full-width per-sensor table + Export CSV"]} delta={["4 tiles = Online/Offline/Low Battery/Alerts, each deep-links to Devices (site+status scoped) or Alerts (NEW)","Mesh Path at the bottom of the grower card (NEW)","live MapLibre map (real GPS, OpenStreetMap tiles) (s366 PM-192)","real reading columns","gateway + router cards are whole-card clickable -> device detail; Open Device pill removed + card height tightened (s357)","Signal column removed from the Sensor Readings table (s357)"]} absent={["acres/crop/region","Air-qual per-sensor (gas frozen, PM-180)"]}/>
    <div style={{display:'grid',gridTemplateColumns:'1.6fr 1fr',gap:14,marginBottom:14}} className="ops-2col">
      <Card>
        <div style={{display:'flex',alignItems:'center',gap:13,marginBottom:14}}>
          <span style={{width:46,height:46,borderRadius:11,background:O.leaf05,color:O.leaf,display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}><OIcon name="sprout" size={24}/></span>
          <div style={{flex:1}}><div style={{display:'flex',alignItems:'center',gap:10,flexWrap:'wrap'}}><h1 style={{fontFamily:O.disp,fontWeight:800,fontSize:23,letterSpacing:'-0.01em',color:O.fg1,margin:0}}>{st.name}</h1><span style={{marginLeft:'auto',display:'flex',alignItems:'center',gap:8}}>
                  <button className="clk" onClick={()=>setEdit(true)} title="Edit grower" style={{display:'inline-flex',alignItems:'center',gap:6,background:'#fff',border:`1px solid ${O.lineS}`,borderRadius:8,padding:'6px 11px',cursor:'pointer',fontFamily:O.sans,fontWeight:600,fontSize:12,color:O.fg1}}><OIcon name="pencil" size={13}/>Edit</button>
                  <StatusPill status={dark?'offline':son===sensors.length&&sensors.length>0?'online':sensors.length===0?'offline':'stale'}/>
                </span></div><div style={{fontFamily:O.sans,fontSize:13,color:O.fg3,marginTop:3}}>{dash(st.crop)} · {dash(st.region)} · {st.site} · acres {dash(st.acres)}</div></div>
        </div>
        <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fit,minmax(110px,1fr))',gap:10}}>
          <KStat n={devOn} l="Online" c={devOn>0?O.leaf:O.fg3} onClick={()=>onDevices&&onDevices({site:siteId,stat:'online'})}/>
          <KStat n={devOff} l="Offline" c={devOff>0?O.crit:O.leaf} onClick={()=>onDevices&&onDevices({site:siteId,stat:'offline'})}/>
          <KStat n={devLow} l="Low Battery" c={devLow>0?O.warn:O.leaf} onClick={()=>onDevices&&onDevices({site:siteId,stat:'low'})}/>
          <KStat n={siteAlerts} l="Alerts" c={siteAlerts>0?O.crit:O.leaf} onClick={()=>onAlerts&&onAlerts()}/>
        </div>
        <div style={{marginTop:14,paddingTop:14,borderTop:`1px solid ${O.line}`}}>
          <MeshPath siteId={siteId} icons/>
        </div>
      </Card>
      <RealMapPanel gps={gw&&gw.gps} elevationFt={gw&&gw.elevationFt} label={st.site}/>
    </div>
    {(gw||router)&&<div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:14,marginBottom:14}} className="ops-2col">
      {[gw,router].filter(Boolean).map(d=>(
        <Card key={d.id} pad={18} onClick={()=>onDevice(d)} className="clk" style={{display:'flex',flexDirection:'column',cursor:'pointer'}}>
          <div style={{display:'flex',alignItems:'center',gap:11,marginBottom:14}}>
            <span style={{width:30,height:30,borderRadius:8,background:O.azure05,color:O.azure,display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}><OIcon name={KIND[d.kind].icon} size={16}/></span>
            <div><div style={{fontFamily:O.sans,fontWeight:700,fontSize:15,color:O.fg1}}>{d.name}</div><div style={{fontFamily:O.mono,fontSize:11.5,color:O.fg3,marginTop:1}}>{d.node}</div></div>
            {d.battery!=null&&<span style={{marginLeft:14}}><Battery pct={d.battery}/></span>}
            <div style={{marginLeft:'auto',display:'flex',flexDirection:'column',alignItems:'center',gap:4}}><StatusPill status={d.status} sm/><span style={{fontFamily:O.mono,fontSize:10.5,color:O.fg1}}>{fmtAgo(d.lastSeen)}</span></div>
          </div>
          {d.kind==='gateway'&&d.weather&&<div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:10,marginBottom:4}}>
            <Meta label="Temp" v={d.weather.tempF+'°F'}/><Meta label="Pressure" v={d.weather.pressure+' inHg'}/><Meta label="Humidity" v={d.weather.humidity+'%'}/>
          </div>}
          {d.kind!=='gateway'&&<div style={{display:'grid',gridTemplateColumns:'repeat(auto-fit,minmax(90px,1fr))',gap:10}}>
            <Meta label="Link" v={dash(d.link)}/><Meta label="Signal" v={dash(null)}/><Meta label="Firmware" v={dash(null)}/>
          </div>}
        </Card>
      ))}
    </div>}
    {sensors.length>0&&<Card pad={0} style={{overflow:'hidden'}}>
      <div style={{padding:'14px 16px',borderBottom:`1px solid ${O.line}`,display:'flex',alignItems:'center',justifyContent:'space-between',flexWrap:'wrap',gap:10}}>
        <div style={{fontFamily:O.sans,fontWeight:700,fontSize:15,color:O.fg1}}>Sensor Readings <span style={{fontFamily:O.mono,fontWeight:500,fontSize:12,color:O.fg3}}>· {sensors.length} Sensors</span></div>
        <button className="clk" style={ghostBtn}><OIcon name="download" size={15}/>Export CSV</button>
      </div>
      <div style={{overflowX:'auto'}}>
        <table style={{width:'100%',borderCollapse:'collapse',minWidth:880}}>
          <thead><tr style={{background:O.sunken}}>{['Sensor','Status','Battery','Last seen','Temp','Baro','Humidity','Noise','Air qual.'].map((h,i)=><th key={i} style={{...th,textAlign:'center'}}>{h}</th>)}</tr></thead>
          <tbody>
            {sensors.map(s=>{ const r=s.reading||{};
              return <tr key={s.id} onClick={()=>onDevice(s)} style={{cursor:'pointer',borderTop:`1px solid ${O.line}`}} className="ops-row">
                <td style={{...td,textAlign:'center'}}><div style={{fontFamily:O.sans,fontWeight:700,fontSize:12.5,color:O.fg1}}>{s.name}</div><div style={{fontFamily:O.mono,fontSize:10,color:O.fg3}}>{s.node}</div></td>
                <td style={{...td,textAlign:'center'}}><StatusPill status={s.status} sm/></td>
                <td style={{...td,textAlign:'center'}}>{s.battery!=null?<span style={{display:'inline-flex'}}><Battery pct={s.battery}/></span>:dash(null)}</td>
                <td style={{...tdM,textAlign:'center'}}>{fmtAgo(s.lastSeen)}</td>
                <td style={{...tdM,textAlign:'center'}}>{dash(r.tempF!=null?r.tempF+'°':null)}</td>
                <td style={{...tdM,textAlign:'center'}}>{dash(r.pressure)}</td>
                <td style={{...tdM,textAlign:'center'}}>{dash(r.humidity!=null?r.humidity+'%':null)}</td>
                <td style={{...tdM,textAlign:'center'}}>{dash(r.noise!=null?r.noise:null)}</td>
                <td style={{...tdM,textAlign:'center'}}>{dash(null)}</td>
              </tr>;
            })}
          </tbody>
        </table>
      </div>
    </Card>}
    {edit && <EditGrowerModal site={st} onClose={()=>setEdit(false)} onSaved={onSaved}/>}
  </div>;
}

// s366 PM-192: real live MapLibre map (OSM raster tiles, no API key -- mirrors the real Next app's
// components/GatewayMap.tsx, Port-18). Replaces the s353-s365 SVG stand-in. Centers on the gateway's
// GPS with a single pin; keeps the Lat/Long + ELE overlay pills. No fix -> neutral placeholder.
function RealMapPanel({ gps, label, elevationFt }){
  const ll = gps ? gps.split(',').map(s=>parseFloat(s)) : null;
  const hasFix = !!(ll && ll.length>=2 && !isNaN(ll[0]) && !isNaN(ll[1]));
  const gpsLabel = hasFix ? `${ll[0].toFixed(2)}, ${ll[1].toFixed(2)}` : (gps||'no GPS fix');
  const ref = React.useRef(null);
  const mapRef = React.useRef(null);
  React.useEffect(()=>{
    if(!hasFix || !ref.current || mapRef.current || !window.maplibregl) return;
    const lat=ll[0], lon=ll[1];
    const map = new window.maplibregl.Map({
      container: ref.current,
      style: { version:8,
        sources:{ osm:{ type:'raster', tiles:['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'], tileSize:256, attribution:'(c) OpenStreetMap contributors' } },
        layers:[{ id:'osm', type:'raster', source:'osm' }] },
      center:[lon,lat], zoom:13, scrollZoom:false, attributionControl:{ compact:true },
    });
    map.addControl(new window.maplibregl.NavigationControl({ showCompass:false }), 'bottom-right');
    const el=document.createElement('div');
    el.style.cssText=`width:18px;height:18px;border-radius:50%;background:${O.azure};border:3px solid #fff;box-shadow:0 2px 7px rgba(31,111,224,0.55)`;
    new window.maplibregl.Marker({ element:el }).setLngLat([lon,lat]).addTo(map);
    mapRef.current=map;
    return ()=>{ map.remove(); mapRef.current=null; };
  }, [gps]);
  if(!hasFix) return <div style={{position:'relative',borderRadius:12,overflow:'hidden',border:`1px solid ${O.line}`,background:'#EAF1F8',minHeight:200,height:'100%',display:'flex',alignItems:'center',justifyContent:'center'}}>
    <div style={{display:'flex',flexDirection:'column',alignItems:'center',gap:7,color:O.fg3,fontFamily:O.mono,fontSize:11}}><OIcon name="map-pin" size={20} color={O.fg3}/>No GPS fix reported</div>
  </div>;
  return <div style={{position:'relative',borderRadius:12,overflow:'hidden',border:`1px solid ${O.line}`,minHeight:200,height:'100%'}}>
    <div ref={ref} style={{position:'absolute',inset:0}}/>
    <div style={{position:'absolute',left:10,bottom:10,zIndex:2,background:'rgba(255,255,255,0.95)',borderRadius:8,padding:'7px 11px',display:'flex',alignItems:'center',gap:7,boxShadow:'0 1px 4px rgba(22,32,43,0.12)'}}>
      <OIcon name="map-pin" size={15} color={O.azure}/><div><div style={{fontFamily:O.mono,fontWeight:600,fontSize:12,color:O.fg1}}>{gpsLabel}</div><div style={{fontFamily:O.mono,fontSize:8.5,color:O.fg3,letterSpacing:'.08em',textTransform:'uppercase'}}>Lat / Long</div></div>
    </div>
    {/* s365: elevation pill (PM-192 ELE). s368 (PM-184 review, owner direction): moved to the map top-left. */}
    <div style={{position:'absolute',left:10,top:10,zIndex:2,background:'rgba(255,255,255,0.95)',borderRadius:8,padding:'7px 11px',display:'flex',alignItems:'center',gap:7,boxShadow:'0 1px 4px rgba(22,32,43,0.12)'}}>
      <OIcon name="mountain" size={15} color={O.leaf}/><div><div style={{fontFamily:O.mono,fontWeight:600,fontSize:12,color:O.fg1}}>{elevationFt!=null?elevationFt+' ft':'-'}</div><div style={{fontFamily:O.mono,fontSize:8.5,color:O.fg3,letterSpacing:'.08em',textTransform:'uppercase'}}>ELE</div></div>
    </div>
  </div>;
}

// ================= Modals (Add / Remove) =================
const inp={width:'100%',boxSizing:'border-box',height:34,padding:'6px 10px',borderRadius:8,border:`1px solid ${O.lineS}`,background:'#fff',fontFamily:O.sans,fontSize:13.5,color:O.fg1,outline:'none'};
const lab={fontFamily:O.mono,fontSize:10,fontWeight:600,letterSpacing:'.06em',textTransform:'uppercase',color:O.fg3,marginBottom:3,display:'block'};
function Modal({ title, onClose, children, footer, headerActions }){
  return <div onClick={onClose} style={{position:'fixed',inset:0,background:'rgba(14,28,51,0.45)',zIndex:60,display:'flex',alignItems:'center',justifyContent:'center',padding:20}}>
    <div onClick={e=>e.stopPropagation()} style={{background:O.raised,borderRadius:16,boxShadow:'0 20px 60px rgba(14,28,51,0.4)',width:520,maxWidth:'100%',maxHeight:'90vh',overflowY:'auto'}}>
      {/* sticky: the modal box scrolls as one piece, so the header (and any
          headerActions, e.g. Edit-Grower Save) stays visible while fields scroll. */}
      <div style={{padding:'14px 22px',borderBottom:`1px solid ${O.line}`,display:'flex',alignItems:'center',justifyContent:'space-between',position:'sticky',top:0,background:O.raised,zIndex:1,borderRadius:'16px 16px 0 0'}}>
        <div style={{fontFamily:O.disp,fontWeight:800,fontSize:19,textTransform:'uppercase',letterSpacing:'-0.01em',color:O.fg1}}>{title}</div>
        <div style={{display:'flex',alignItems:'center',gap:12}}>
          {headerActions}
          <button onClick={onClose} style={{background:'none',border:'none',cursor:'pointer',color:O.fg3}}><OIcon name="x" size={20}/></button>
        </div>
      </div>
      <div style={{padding:'12px 22px',display:'flex',flexDirection:'column',gap:12}}>{children}</div>
      {footer && <div style={{padding:'16px 22px',borderTop:`1px solid ${O.line}`,display:'flex',justifyContent:'flex-end',gap:10}}>{footer}</div>}
    </div>
  </div>;
}
function AddDeviceModal({ onClose }){
  const [kind,setKind]=React.useState('sensor');
  const [siteId,setSiteId]=React.useState((SITES[0]||{}).id||'');
  // s379 (owner direction): the attach list is the selected grower's REAL fleet -- its
  // gateway + any routers under it -- not generic placeholders. Router attaches ONLY to
  // the gateway; a sensor attaches to a router OR directly to the gateway (the edge
  // router is a range-extending relay, not a required hop -- a routerless sensor/gateway
  // mesh is a valid topology).
  const siteDevs=DEVICES.filter(d=>d.site===siteId);
  const gw=siteDevs.find(d=>d.kind==='gateway');
  const routers=siteDevs.filter(d=>d.kind==='router');
  const attach=kind==='sensor'
    ? [...routers.map(r=>r.name), ...(gw?[gw.name+' (direct)']:[])]
    : (gw?[gw.name]:[]);
  return <Modal title="Add Device" onClose={onClose} footer={<><button onClick={onClose} className="clk" style={ghostBtn}>Cancel</button><button className="clk" style={primBtn}><OIcon name="check" size={15}/>Add Device</button></>}>
    <div><label style={lab}>Device type</label><div style={{display:'flex',gap:8}}>{[['gateway','Gateway'],['router','Router'],['sensor','Sensor']].map(([k,l])=><button key={k} className="clk" onClick={()=>setKind(k)} style={{flex:1,padding:'9px 4px',borderRadius:9,cursor:'pointer',fontFamily:O.sans,fontWeight:700,fontSize:13,border:`1px solid ${kind===k?O.azure:O.line}`,background:kind===k?O.azure:'#fff',color:kind===k?'#fff':O.fg2}}>{l}</button>)}</div></div>
    <div><label style={lab}>Grower / site</label><select style={inp} value={siteId} onChange={e=>setSiteId(e.target.value)}>{SITES.map(s=><option key={s.id} value={s.id}>{s.name}</option>)}</select></div>
    {kind!=='gateway'
      ? <div><label style={lab}>{kind==='sensor'?'Attach to':'Attach to Gateway'}</label>
          {attach.length
            ? <select style={inp}>{attach.map(a=><option key={a}>{a}</option>)}</select>
            : <select style={{...inp,color:O.fg3}} disabled><option>No gateway on this grower yet</option></select>}
        </div>
      : <div><label style={lab}>Uplink</label><select style={{...inp,color:O.fg3}} disabled><option>LTE to nRF Cloud (direct; no mesh parent)</option></select></div>}
    {/* s368 review (owner direction): no Node address field -- the mesh assigns the node
        address at provisioning; the operator never sets it. Only a human Label is entered. */}
    <div><label style={lab}>Label</label><input style={inp} placeholder="Block A"/></div>
    {!PROD_PORTAL && <div style={{fontFamily:O.sans,fontSize:11.5,color:O.fg3,background:O.azure05,borderRadius:8,padding:'9px 11px'}}>UI shown for the Page-Preview Gate. The reversible ownership write-back rides the auth work (PM-184; admin / AUTH_MODE=real).</div>}
  </Modal>;
}
function AddGrowerModal({ onClose, onAdded }){
  return <Modal title="Add Grower" onClose={onClose} footer={<><button onClick={onClose} className="clk" style={ghostBtn}>Cancel</button><button onClick={onAdded} className="clk" style={primBtn}><OIcon name="check" size={15}/>Add Grower</button></>}>
    <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:12}}>
      <div style={{gridColumn:'1 / -1'}}><label style={lab}>Grower / orchard name</label><input style={inp} placeholder="North Ridge Orchards"/></div>
      <div><label style={lab}>Type</label><select style={inp}><option>Grower</option><option>Consumer</option></select></div>
      <div><label style={lab}>Site name</label><input style={inp} placeholder="Orchard"/></div>
    </div>
    {!PROD_PORTAL && <div style={{fontFamily:O.sans,fontSize:11.5,color:O.fg3,background:O.azure05,borderRadius:8,padding:'9px 11px'}}>UI shown for the Page-Preview Gate. Writes to the reversible ownership store (PM-183; admin / AUTH_MODE=real).</div>}
  </Modal>;
}
// Admin token kept client-side only (operator-entered; never baked). PM-184.
function getAdminToken(){ try{ return localStorage.getItem('bs_admin_token')||''; }catch{ return ''; } }
function setAdminToken(t){ try{ if(t) localStorage.setItem('bs_admin_token',t); }catch{} }
function clearAdminToken(){ try{ localStorage.removeItem('bs_admin_token'); }catch{} }

// Edit-Grower: pre-filled from the grower's current record (the widened CanonSite).
// PATCHes the editable whitelist (name, business, owner, email, phone, address, region,
// crop, acres, notes, kind, + the location name), then onSaved() soft-refreshes.
// Target id = site.growerId (the customer_id). PM-184.
function EditGrowerModal({ site, onClose, onSaved }){
  const TEXT=['name','businessName','ownerName','email','phone','address','region','crop','acres','notes'];
  const init={ kind: site.kind||'commercial', locationName: site.site||'' };
  TEXT.forEach(k=>{ const v=site[k]; init[k]= v==null?'':String(v); });
  const [f,setF]=React.useState(init);
  const [busy,setBusy]=React.useState(false);
  const [err,setErr]=React.useState(null);
  const set=(k)=>(e)=>setF(s=>({...s,[k]:e.target.value}));
  const save=async()=>{
    setErr(null);
    let token=getAdminToken();
    if(!token){ token=window.prompt('Admin token (required to edit)')||''; if(!token) return; setAdminToken(token); }
    const fields={ kind: f.kind, locationName: (f.locationName||'').trim() };
    TEXT.forEach(k=>{ const v=(f[k]||'').trim(); if(k==='acres') fields[k]= v===''?null:Number(v); else fields[k]= v===''?null:v; });
    if(!fields.name){ setErr('Grower Name is required.'); return; }
    setBusy(true);
    try{
      await window.patchGrower(site.growerId, fields, token);
      setBusy(false); onClose(); if(onSaved) onSaved();
    }catch(e){
      setBusy(false);
      if(e&&e.code===401){ clearAdminToken(); setErr('Admin token invalid -- re-enter on next save.'); }
      else setErr('Save failed ('+((e&&e.message)||'error')+'). The record was not changed.');
    }
  };
  const field=(k,label,opts={})=>(
    <div style={opts.full?{gridColumn:'1 / -1'}:undefined}>
      <label style={lab}>{label}</label>
      {opts.area
        ? <textarea style={{...inp,minHeight:64,resize:'vertical'}} value={f[k]} onChange={set(k)} placeholder={opts.ph||''}/>
        : <input style={inp} value={f[k]} onChange={set(k)} placeholder={opts.ph||''}/>}
    </div>
  );
  return <Modal title="Edit Grower" onClose={onClose}
    headerActions={<button onClick={save} className="clk" style={{...primBtn,opacity:busy?0.6:1}} disabled={busy}><OIcon name="check" size={15}/>{busy?'Saving...':'Save'}</button>}>
    <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:10}}>
      {field('name','Grower Name',{full:true,ph:'Farm Among Us'})}
      <div><label style={lab}>Type</label><select style={inp} value={f.kind} onChange={set('kind')}><option value="commercial">Commercial</option><option value="consumer">Consumer</option></select></div>
      {field('locationName','Location name',{ph:'Orchard'})}
      {field('businessName','Business name',{ph:'North Ridge Orchards LLC'})}
      {field('ownerName','Owner / contact',{ph:'Jane Grower'})}
      {field('email','Email',{ph:'jane@example.com'})}
      {field('phone','Phone',{ph:'(555) 010-1234'})}
      {field('acres','Acres',{ph:'2'})}
      {field('region','Region',{ph:'Yakima Valley'})}
      {field('crop','Crop',{full:true,ph:'Cherries'})}
      {field('address','Address',{full:true,ph:'123 Orchard Rd, ...'})}
      {field('notes','Notes',{full:true})}
    </div>
    {err && <div style={{fontFamily:O.sans,fontSize:12,color:O.crit,background:O.azure05,borderRadius:8,padding:'9px 11px'}}>{err}</div>}
    {!PROD_PORTAL && <div style={{fontFamily:O.sans,fontSize:11.5,color:O.fg3,background:O.azure05,borderRadius:8,padding:'9px 11px'}}>UI shown for the Page-Preview Gate. Writes the reversible ownership store via the admin-gated PATCH (PM-184).</div>}
  </Modal>;
}
function RemoveDialog({ kind, name, onClose }){
  return <Modal title={`Remove ${kind}`} onClose={onClose} footer={<><button onClick={onClose} className="clk" style={ghostBtn}>Cancel</button><button className="clk" style={{...primBtn,background:O.crit}}><OIcon name="eye-off" size={15}/>Remove from view</button></>}>
    <div style={{fontFamily:O.sans,fontSize:14,color:O.fg1}}>Remove <b>{name}</b> from the portal view?</div>
    <div style={{display:'flex',gap:10,background:O.leaf05,border:`1px solid ${O.leaf1}`,borderRadius:10,padding:'12px 14px'}}>
      <span style={{color:O.leaf,flexShrink:0}}><OIcon name="rotate-ccw" size={18}/></span>
      <div style={{fontFamily:O.sans,fontSize:12.5,color:O.fg2}}><b style={{color:O.fg1}}>Reversible &amp; non-destructive.</b> This unlinks {name} at the ownership/registry layer only - a front-end soft-flag. <b>No telemetry is deleted</b> from InfluxDB (Project Law 6). Restore re-links it anytime.</div>
    </div>
  </Modal>;
}

Object.assign(window,{ Overview, Devices, DeviceDetail, Topology, Alerts, Integrations, Growers, GrowerDetail, AddDeviceModal, AddGrowerModal, EditGrowerModal, RemoveDialog });
