// v-trail.jsx — Trail Studio: store challenge management, Lucky Strike control, top customers

// ─── Store challenges (public.lasso_vendor_challenges) ───────────────
// Completions come from public.lasso_vendor_challenge_events (live).
// Derived rates (completion %, uplift) have no view/job yet — shown pending.
const TRAIL_CHALLENGES = [
  { id:'tc1', title:'Three for the Trail', copy:'Order from Fire & Dough 3 times this week and bag bonus XP.', xp:250, status:'active', budget:120, spent:74,  completions:38, since:'12 Jun', type:'order',   mark:3 },
  { id:'tc2', title:'Weekend Wrangler',    copy:'One weekend order doubles your Trail stamps.',               xp:150, status:'active', budget:80,  spent:22,  completions:11, since:'20 Jun', type:'order',   mark:1 },
  { id:'tc4', title:'Wing Walker',         copy:'Five wing orders this month. Saddle up.',                    xp:300, status:'paused', budget:150, spent:96,  completions:19, since:'2 Jun',  type:'loyalty-repeat', mark:5 },
  { id:'tc3', title:'Slice Scout',         copy:'Try the new Nduja & Hot Honey — first 50 get XP.',           xp:120, status:'draft',  budget:60,  spent:0,   completions:0,  since:null,     type:'order',   mark:1 },
];
const TC_STATUS = {
  active: { tone:'green', label:'Active' },
  paused: { tone:'amber', label:'Paused' },
  draft:  { tone:'grey',  label:'Draft' },
};

// ─── Challenge types — what the DB can evaluate today, honestly ─────
// order/loyalty: order-backed, evaluated live via challenge events.
// review/referral/social: UI ready, verification backend PENDING — completions manual until contracts land.
// custom: manual by definition.
const CH_TYPES = {
  order:    { label:'Order',            icon:'receipt',   wire:'live',    badge:'Order-backed · live',   markLabel:'Value mark — orders',   unit:'orders',    proof:'Automatic — counted from order events',            hint:'Completes when the customer hits this many qualifying orders in the window. Evaluated live from challenge events.' },
  'loyalty-repeat':  { label:'Loyalty / repeat',  icon:'refresh',   wire:'live',    badge:'Order-backed · live',   markLabel:'Value mark — repeat visits', unit:'visits', proof:'Automatic — counted from repeat order events',      hint:'Counts repeat orders from the same customer. Evaluated live from order history.' },
  review:   { label:'Review',           icon:'star',      wire:'pending', badge:'Pending review backend', markLabel:'Value mark — reviews',  unit:'reviews',   proof:'Manual until review-persistence contract exists',    hint:'Asks for a review after ordering. Verified completions need the review contract — until then you confirm manually.' },
  referral: { label:'Referral',         icon:'users',     wire:'pending', badge:'Pending referral backend', markLabel:'Value mark — referrals', unit:'referrals', proof:'Manual until referral-event contract exists',     hint:'Counts friends who order via the customer’s link. Needs a referral-event table/RPC before it can auto-verify.' },
  social:   { label:'Social media',     icon:'megaphone', wire:'pending', badge:'Pending verification backend', markLabel:'Value mark — proof posts', unit:'posts', proof:'Manual until social-proof verification exists', hint:'Asks for a tagged post/story. No automated social verification exists — you confirm proof manually for now.' },
  custom:   { label:'Custom (manual)',  icon:'settings',  wire:'local',   badge:'Manual challenge',       markLabel:'Value mark — units',    unit:'units',     proof:'Manual — you confirm completions yourself',         hint:'Define any rule you can verify yourself. The value mark is whatever count you’ll check by hand.' },
};

// Customer-facing preview card (mirrors the mobile app challenge card)
function TrailPreview({ title, copy, xp }) {
  return (
    <div style={{ background:'var(--dark-chocolate)', color:'var(--dust-cream)', borderRadius:16, padding:'14px 15px', maxWidth:320 }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:10 }}>
        <span style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:13.5 }}>{title || 'Challenge title'}</span>
        <span style={{ fontFamily:'var(--font-display)', fontWeight:900, fontSize:11, color:'#1B120D', background:'linear-gradient(90deg,#F28C1B,#E9B97A)', borderRadius:999, padding:'3px 9px', whiteSpace:'nowrap' }}>+{xp} XP</span>
      </div>
      <div style={{ fontFamily:'var(--font-body)', fontSize:11.5, color:'#B6A595', marginTop:5, lineHeight:1.45 }}>{copy || 'Customer-facing copy appears here.'}</div>
      <div style={{ height:6, borderRadius:3, background:'rgba(255,255,255,0.1)', overflow:'hidden', marginTop:11 }}>
        <div style={{ height:'100%', width:'33%', background:'linear-gradient(90deg,#F28C1B,#D85A14)' }}/>
      </div>
      <div style={{ fontFamily:'var(--font-mono)', fontSize:9.5, color:'#8A786B', marginTop:6 }}>1 of 3 · how it looks in the customer app</div>
    </div>
  );
}

// ─── Challenge editor drawer ─────────────────────────────────────────
const TR_FLD = { width:'100%', boxSizing:'border-box', background:'rgba(255,255,255,0.55)', border:'1px solid var(--soft-line)', borderRadius:10, padding:'10px 12px', fontFamily:'var(--font-body)', fontSize:13, color:'var(--fg-primary)', outline:'none' };
const TR_LBL = { fontFamily:'var(--font-display)', fontWeight:700, fontSize:10.5, letterSpacing:'0.06em', textTransform:'uppercase', color:'var(--warm-stone)', marginBottom:7, display:'block' };

function TrailEditor({ app, ch, onClose, onSave }) {
  const isNew = !ch.id;
  const [type, setType] = React.useState(ch.type || 'order');
  const [title, setTitle] = React.useState(ch.title || '');
  const [copy, setCopy] = React.useState(ch.copy || '');
  const [mark, setMark] = React.useState(ch.mark || 3);
  const [xp, setXp] = React.useState(ch.xp || 150);
  const [budget, setBudget] = React.useState(String(ch.budget || 100));
  const [days, setDays] = React.useState(ch.days || 7);
  const [status, setStatus] = React.useState(ch.status || 'draft');
  const T = CH_TYPES[type];
  const rulesJson = { type, value_mark: mark, unit: T.unit, proof: T.wire==='live' ? 'auto_order_events' : 'manual_pending_contract', reward_xp: xp, budget_cap: +budget||0, duration_days: days };
  const save = () => {
    if (!title.trim()) { app.toast('Give the challenge a title'); return; }
    onSave({ ...ch, id: ch.id || 'tc-'+Math.random().toString(16).slice(2,6), type, mark, days, title:title.trim(), copy:copy.trim(), xp, budget:+budget||0, status, spent:ch.spent||0, completions:ch.completions||0, since:ch.since||'today' });
  };
  return ReactDOM.createPortal(
    <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:300, background:'rgba(27,18,13,0.46)', backdropFilter:'blur(3px)', display:'flex', justifyContent:'flex-end', animation:'v-fade 200ms' }}>
      <div data-vendor-trail-challenge-editor="" onClick={e=>e.stopPropagation()} style={{ width:'100%', maxWidth:460, height:'100%', background:'var(--bg-surface)', boxShadow:'var(--shadow-lg)', display:'flex', flexDirection:'column', animation:'v-slidein 280ms var(--ease-out)' }}>
        <div style={{ padding:'18px 22px 15px', borderBottom:'1px solid var(--soft-line)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <div>
            <Eyebrow>{isNew?'New challenge':'Edit challenge'}</Eyebrow>
            <div style={{ fontFamily:'var(--font-display)', fontWeight:900, fontSize:19, color:'var(--fg-primary)', marginTop:5 }}>{isNew?'Set a new Trail challenge':title||ch.title}</div>
          </div>
          <button onClick={onClose} style={{ width:32, height:32, borderRadius:9, border:'1px solid var(--soft-line)', background:'var(--bg-app)', display:'flex', alignItems:'center', justifyContent:'center', cursor:'pointer' }}><I.x c="var(--warm-stone)" s={16}/></button>
        </div>
        <div style={{ flex:1, minHeight:0, overflowY:'auto', padding:'18px 22px', display:'flex', flexDirection:'column', gap:16 }}>
          {/* type selector */}
          <div>
            <span style={TR_LBL}>Challenge type</span>
            <div data-challenge-type="" style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:8 }}>
              {Object.keys(CH_TYPES).map(k=>{
                const t = CH_TYPES[k]; const TIcon = I[t.icon] || I.grid; const on = type===k;
                return (
                  <button key={k} onClick={()=>setType(k)} title={t.hint} style={{ padding:'10px 8px', borderRadius:11, border:'1.5px solid', borderColor: on?'var(--brand-orange)':'var(--soft-line)', background: on?'rgba(242,140,27,0.08)':'var(--bg-app)', cursor:'pointer', textAlign:'center' }}>
                    <TIcon c={on?'var(--brand-orange)':'var(--warm-stone)'} s={16}/>
                    <div style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:11, color:'var(--fg-primary)', marginTop:5 }}>{t.label}</div>
                    <div style={{ marginTop:4 }}><Wire kind={t.wire} style={{ transform:'scale(0.86)' }}/></div>
                  </button>
                );
              })}
            </div>
            <div style={{ marginTop:9, display:'flex', gap:8, alignItems:'flex-start', padding:'9px 11px', background:'var(--bg-app)', border:'1px solid var(--soft-line)', borderRadius:10 }}>
              <I.info c="var(--warm-stone)" s={13}/>
              <span style={{ fontFamily:'var(--font-body)', fontSize:11, lineHeight:1.5, color:'var(--warm-stone)' }}>{T.hint}</span>
            </div>
          </div>
          <div>
            <span style={TR_LBL}>Title</span>
            <input value={title} onChange={e=>setTitle(e.target.value)} maxLength={40} placeholder="Three for the Trail" style={TR_FLD}/>
          </div>
          <div>
            <span style={TR_LBL}>Customer-facing copy</span>
            <textarea value={copy} onChange={e=>setCopy(e.target.value)} rows={2} maxLength={120} placeholder="What the customer sees…" style={{ ...TR_FLD, resize:'vertical', lineHeight:1.45 }}></textarea>
          </div>
          {/* value mark + reward */}
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:14 }}>
            <div>
              <span style={TR_LBL}>{T.markLabel}</span>
              <div style={{ display:'flex', alignItems:'center', gap:8 }}>
                <input data-challenge-value-mark="" value={mark} onChange={e=>setMark(Math.max(1, parseInt(e.target.value.replace(/[^0-9]/g,''))||1))} inputMode="numeric" style={{ ...TR_FLD, width:76, textAlign:'center', fontFamily:'var(--font-mono)' }}/>
                <span style={{ fontFamily:'var(--font-body)', fontSize:12, color:'var(--warm-stone)' }}>{T.unit} to complete</span>
              </div>
            </div>
            <div>
              <span style={TR_LBL}>XP reward · {xp}</span>
              <Slider value={xp} min={50} max={500} step={10} onChange={setXp}/>
            </div>
          </div>
          {/* budget + duration */}
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:14 }}>
            <div>
              <span style={TR_LBL}>Budget cap</span>
              <div style={{ position:'relative' }}>
                <span style={{ position:'absolute', left:12, top:'50%', transform:'translateY(-50%)', fontFamily:'var(--font-display)', fontWeight:800, fontSize:13, color:'var(--warm-stone)' }}>£</span>
                <input value={budget} onChange={e=>setBudget(e.target.value.replace(/[^0-9.]/g,''))} inputMode="decimal" style={{ ...TR_FLD, paddingLeft:26 }}/>
              </div>
            </div>
            <div>
              <span style={TR_LBL}>Runs for · {days} days</span>
              <Slider value={days} min={1} max={30} step={1} onChange={setDays}/>
            </div>
          </div>
          {/* proof / validation */}
          <div>
            <span style={TR_LBL}>Proof &amp; validation</span>
            <div data-challenge-proof="" style={{ display:'flex', alignItems:'center', gap:9, padding:'10px 12px', background: T.wire==='live'?'rgba(46,139,78,0.07)':'rgba(224,153,42,0.08)', border:'1px solid', borderColor: T.wire==='live'?'rgba(46,139,78,0.25)':'rgba(224,153,42,0.25)', borderRadius:11 }}>
              {T.wire==='live' ? <I.check c="#247a41" s={15}/> : <I.clock c="#a86d12" s={15}/>}
              <span style={{ fontFamily:'var(--font-body)', fontSize:12, color:'var(--fg-primary)' }}>{T.proof}</span>
              <span style={{ marginLeft:'auto' }}><Wire kind={T.wire}/></span>
            </div>
          </div>
          {/* how it stores */}
          <div>
            <span style={TR_LBL}>How this saves &amp; evaluates</span>
            <div style={{ background:'var(--dark-chocolate)', borderRadius:12, padding:'11px 13px' }}>
              <div data-challenge-rules-preview="" style={{ fontFamily:'var(--font-mono)', fontSize:10.5, lineHeight:1.7, color:'#D8CCC0', whiteSpace:'pre-wrap', wordBreak:'break-word' }}>{'rules = '+JSON.stringify(rulesJson)}</div>
              <div style={{ fontFamily:'var(--font-body)', fontSize:10.5, lineHeight:1.55, color:'#9F8979', marginTop:8, paddingTop:8, borderTop:'1px solid rgba(255,255,255,0.1)' }}>
                Stored as structured JSON in the challenges table’s <span style={{ fontFamily:'var(--font-mono)' }}>rules</span> field. Order &amp; loyalty marks are evaluated live against challenge events; review, referral and social marks stay manual until their verification contracts exist (review persistence · referral events · social proof · value-mark evaluator).
              </div>
            </div>
          </div>
          <div>
            <span style={TR_LBL}>Status</span>
            <Segmented options={[{value:'active',label:'Active'},{value:'paused',label:'Paused'},{value:'draft',label:'Draft'}]} value={status} onChange={setStatus}/>
          </div>
          <div>
            <span style={TR_LBL}>Customer preview</span>
            <TrailPreview title={title} copy={copy} xp={xp}/>
          </div>
        </div>
        <div style={{ display:'flex', gap:10, padding:'14px 22px', borderTop:'1px solid var(--soft-line)', background:'var(--bg-app)' }}>
          <Btn kind="white" onClick={onClose}>Cancel</Btn>
          <Btn kind="accent" full icon={<I.check c="#fff" s={15}/>} onClick={save}>{isNew?'Create challenge':'Save changes'}</Btn>
        </div>
      </div>
    </div>,
    document.body
  );
}

// ─── Lucky Strike control (no DB contract yet — honest shell) ────────
function LuckyStrikePanel({ app }) {
  const [copy, setCopy] = React.useState('Every order this month is a lasso throw. One lucky regular wins a month of free Fire & Dough.');
  return (
    <Card pad={0} data-vendor-lucky-strike-control="" style={{ overflow:'hidden' }}>
      <div style={{ padding:'15px 20px 13px', borderBottom:'1px solid var(--soft-line)', display:'flex', alignItems:'center', justifyContent:'space-between', gap:12 }}>
        <div>
          <div style={{ display:'flex', alignItems:'center', gap:9 }}>
            <Eyebrow><I.sparkle c="var(--brand-orange)" s={13} f/> &nbsp;Lucky Strike</Eyebrow>
            <Wire kind="pending"/>
          </div>
          <div style={{ fontFamily:'var(--font-body)', fontSize:11.5, color:'var(--warm-stone)', marginTop:4 }}>Prize-draw programme for your regulars. Not connected to a draw engine yet.</div>
        </div>
        <Pill tone="grey" dot>Not live</Pill>
      </div>
      <div style={{ padding:'16px 20px 18px' }}>
        {/* status + master toggle (disabled) */}
        <div style={{ display:'flex', alignItems:'center', gap:12, padding:'12px 14px', background:'var(--bg-app)', border:'1px solid var(--soft-line)', borderRadius:12, marginBottom:14, opacity:0.75 }}>
          <Toggle on={false} onChange={()=>app.toast('Lucky Strike can\u2019t go live until the draw contract exists')}/>
          <div style={{ flex:1 }}>
            <div style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:13, color:'var(--fg-primary)' }}>Run Lucky Strike for this store</div>
            <div style={{ fontFamily:'var(--font-body)', fontSize:11, color:'var(--warm-stone)' }}>Disabled — subscriptions &amp; draws have no database contract</div>
          </div>
          <I.lock c="var(--warm-stone)" s={15}/>
        </div>

        {/* customer copy — editable locally */}
        <div style={{ marginBottom:14 }}>
          <span style={TR_LBL}>Customer-facing explanation</span>
          <textarea value={copy} onChange={e=>setCopy(e.target.value)} rows={2} maxLength={140} style={{ ...TR_FLD, resize:'vertical', lineHeight:1.45 }}></textarea>
        </div>

        {/* eligibility + reward — pending contract */}
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginBottom:14 }}>
          {[['Eligibility','e.g. 2+ orders this month'],['Reward / offer','e.g. a month of free pizza']].map(([l,ph])=>(
            <div key={l}>
              <span style={TR_LBL}>{l}</span>
              <input disabled placeholder={ph} title="Ships with the Lucky Strike contract" style={{ ...TR_FLD, opacity:0.55, cursor:'not-allowed' }}/>
            </div>
          ))}
        </div>

        {/* preview */}
        <div style={{ marginBottom:14 }}>
          <span style={TR_LBL}>Customer preview</span>
          <div style={{ background:'var(--dark-chocolate)', color:'var(--dust-cream)', borderRadius:16, padding:'14px 15px', maxWidth:320 }}>
            <div style={{ display:'flex', alignItems:'center', gap:8 }}>
              <I.sparkle c="#E9B97A" s={15} f/>
              <span style={{ fontFamily:'var(--font-display)', fontWeight:900, fontSize:13.5 }}>Lucky Strike</span>
            </div>
            <div style={{ fontFamily:'var(--font-body)', fontSize:11.5, color:'#B6A595', marginTop:6, lineHeight:1.5 }}>{copy || 'Customer copy appears here.'}</div>
            <div style={{ marginTop:10, display:'inline-flex', padding:'7px 13px', borderRadius:10, background:'var(--brand-orange)', fontFamily:'var(--font-display)', fontWeight:800, fontSize:11.5, color:'#fff' }}>Join the draw</div>
          </div>
        </div>

        {/* missing contract doc */}
        <div style={{ padding:'11px 13px', background:'rgba(224,153,42,0.08)', border:'1px solid rgba(224,153,42,0.25)', borderRadius:11 }}>
          <div style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:11.5, color:'#a86d12', marginBottom:5 }}>Missing contract — documented, not faked</div>
          <div style={{ fontFamily:'var(--font-mono)', fontSize:10.5, color:'var(--warm-stone)', lineHeight:1.7 }}>
            lasso_lucky_strike_config · lasso_lucky_strike_subscriptions · draw/settlement RPC.<br/>No subscribers, winners, redemptions or budget impact are shown until these exist.
          </div>
        </div>
      </div>
    </Card>
  );
}

// ─── Top customers (vendor-safe view required — pending) ─────────────
function TopCustomersPanel() {
  return (
    <Card pad={0} data-vendor-top-customers="" style={{ overflow:'hidden' }}>
      <div style={{ padding:'15px 20px 13px', borderBottom:'1px solid var(--soft-line)', display:'flex', alignItems:'center', justifyContent:'space-between', gap:12 }}>
        <div>
          <div style={{ display:'flex', alignItems:'center', gap:9 }}>
            <Eyebrow><I.crown c="var(--brand-orange)" s={13}/> &nbsp;Top customers</Eyebrow>
            <Wire kind="pending"/>
          </div>
          <div style={{ fontFamily:'var(--font-body)', fontSize:11.5, color:'var(--warm-stone)', marginTop:4 }}>Your 10 most valuable regulars — privacy-safe, never addresses.</div>
        </div>
      </div>
      <div style={{ padding:'16px 20px 18px' }}>
        {/* ghost rows — clearly placeholders, no fabricated people */}
        {[0,1,2].map(i=>(
          <div key={i} aria-hidden="true" style={{ display:'flex', alignItems:'center', gap:12, padding:'9px 0', opacity:0.45-i*0.12 }}>
            <div style={{ width:34, height:34, borderRadius:'50%', background:'repeating-linear-gradient(135deg,#EDE3D6,#EDE3D6 5px,#E3D7C6 5px,#E3D7C6 10px)' }}/>
            <div style={{ flex:1 }}>
              <div style={{ width:'46%', height:10, borderRadius:5, background:'rgba(27,18,13,0.08)' }}/>
              <div style={{ width:'30%', height:8, borderRadius:4, background:'rgba(27,18,13,0.05)', marginTop:6 }}/>
            </div>
            <div style={{ width:52, height:10, borderRadius:5, background:'rgba(27,18,13,0.07)' }}/>
          </div>
        ))}
        <div style={{ marginTop:12, padding:'12px 14px', background:'var(--bg-app)', border:'1px solid var(--soft-line)', borderRadius:12 }}>
          <div style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:12.5, color:'var(--fg-primary)', marginBottom:4 }}>Waiting on a vendor-safe view</div>
          <div style={{ fontFamily:'var(--font-body)', fontSize:11.5, lineHeight:1.55, color:'var(--warm-stone)' }}>
            This panel fills from a <span style={{ fontFamily:'var(--font-mono)', fontSize:10.5 }}>top_customers</span> view/RPC exposing only initials, order count, last activity, XP engagement and lifetime spend. Until it exists, nothing is shown — no made-up names, no made-up numbers.
          </div>
        </div>
      </div>
    </Card>
  );
}

// ─── Screen ──────────────────────────────────────────────────────────
function TrailScreen({ app }) {
  const [items, setItems] = React.useState(TRAIL_CHALLENGES);
  const [editing, setEditing] = React.useState(null); // challenge object or {} for new
  const upsert = (ch) => {
    setItems(list => {
      const i = list.findIndex(x=>x.id===ch.id);
      return i>=0 ? list.map(x=>x.id===ch.id?ch:x) : [ch, ...list];
    });
    setEditing(null);
    app.toast(`\u201C${ch.title}\u201D saved to your store challenges`);
  };
  const setStatus = (id, status) => {
    setItems(list=>list.map(x=>x.id===id?{...x,status}:x));
    app.toast(status==='active'?'Challenge resumed':'Challenge paused');
  };
  const active = items.filter(c=>c.status==='active').length;

  return (
    <div data-vendor-trail-management="">
      <PageHead eyebrow="Trail Studio" title="Trail challenges &amp; draws"
        sub={`${active} active challenge${active===1?'':'s'} running for Fire & Dough`}
        right={<Btn kind="accent" icon={<I.plus c="#fff" s={16}/>} onClick={()=>setEditing({})}>New challenge</Btn>}/>

      <div style={{ display:'grid', gridTemplateColumns:'1.5fr 1fr', gap:16, alignItems:'start' }}>
        {/* challenge list */}
        <Card pad={0} data-vendor-trail-challenge-list="" style={{ overflow:'hidden' }}>
          <div style={{ padding:'15px 20px 13px', borderBottom:'1px solid var(--soft-line)', display:'flex', alignItems:'center', gap:9 }}>
            <Eyebrow><I.target c="var(--brand-orange)" s={13}/> &nbsp;Store challenges</Eyebrow>
            <Wire kind="partial"/>
            <span style={{ marginLeft:'auto', fontFamily:'var(--font-body)', fontSize:11, color:'var(--warm-stone)' }}>Completions read from challenge events</span>
          </div>
          {items.map((c,i)=>{
            const st = TC_STATUS[c.status];
            const spentPct = c.budget ? Math.round(c.spent/c.budget*100) : 0;
            return (
              <div key={c.id} style={{ padding:'14px 20px', borderBottom: i<items.length-1?'1px solid var(--soft-line)':'none' }}>
                <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:8, flexWrap:'wrap' }}>
                  <span style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:14, color:'var(--fg-primary)' }}>{c.title}</span>
                  <Pill tone={st.tone} dot>{st.label}</Pill>
                  {(()=>{ const t = CH_TYPES[c.type||'custom']; return <span title={t.hint+' · '+t.proof} style={{ cursor:'help' }}><Pill tone={t.wire==='live'?'blue':'amber'} style={{ fontSize:9 }}>{t.badge}</Pill></span>; })()}
                  <span style={{ marginLeft:'auto', fontFamily:'var(--font-display)', fontWeight:900, fontSize:12, color:'var(--brand-orange)' }}>+{c.xp} XP</span>
                </div>
                <div style={{ fontFamily:'var(--font-body)', fontSize:12, color:'var(--warm-stone)', marginBottom:11 }}>{c.copy}</div>
                <div style={{ display:'flex', alignItems:'center', gap:16, flexWrap:'wrap' }}>
                  <div style={{ flex:'1 1 160px', minWidth:140 }}>
                    <div style={{ display:'flex', justifyContent:'space-between', fontFamily:'var(--font-mono)', fontSize:10, color:'var(--warm-stone)', marginBottom:4 }}>
                      <span>Budget £{c.spent} / £{c.budget}</span><span>{spentPct}%</span>
                    </div>
                    <Bar pct={spentPct} h={5}/>
                  </div>
                  <span title="Live count from the challenge-events table" style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:12, color:'var(--fg-primary)', cursor:'help' }}>{c.completions} completions</span>
                  <span title="Needs a derived view — not computed yet" style={{ fontFamily:'var(--font-body)', fontSize:11, color:'var(--warm-stone)', cursor:'help' }}>rate: <Pill tone="amber" style={{ fontSize:9 }}>pending</Pill></span>
                  <div style={{ marginLeft:'auto', display:'flex', gap:7 }}>
                    {c.status==='active' && <Btn kind="white" size="sm" icon={<I.pause c="var(--fg-primary)" s={13}/>} onClick={()=>setStatus(c.id,'paused')}>Pause</Btn>}
                    {c.status==='paused' && <Btn kind="white" size="sm" icon={<I.play c="var(--fg-primary)" s={13}/>} onClick={()=>setStatus(c.id,'active')}>Resume</Btn>}
                    {c.status==='draft' && <Btn kind="white" size="sm" icon={<I.rocket c="var(--fg-primary)" s={13}/>} onClick={()=>setStatus(c.id,'active')}>Launch</Btn>}
                    <Btn kind="primary" size="sm" onClick={()=>setEditing(c)}>Edit</Btn>
                  </div>
                </div>
              </div>
            );
          })}
        </Card>

        {/* right rail */}
        <div style={{ display:'flex', flexDirection:'column', gap:16 }}>
          <LuckyStrikePanel app={app}/>
          <TopCustomersPanel/>
        </div>
      </div>

      {editing && <TrailEditor app={app} ch={editing} onClose={()=>setEditing(null)} onSave={upsert}/>}
    </div>
  );
}

Object.assign(window, { TrailScreen, TrailPreview, TRAIL_CHALLENGES, TC_STATUS });
