// Student portal · Signals feed (read-only). window.SignalsFeed
// Shown at the top of the dashboard to PAID students (basic/inter/adv).
// Polls every 15s; chimes + toast on a fresh signal. Same signal rows the
// coaches see; students cannot edit — they just read and (if pushed) also
// get the Telegram DM from n8n.
(function(){
const { useState, useEffect, useRef } = React;
const SOUND_KEY='cg_signal_sound';
function soundOn(){ return localStorage.getItem(SOUND_KEY)!=='0'; }
function chime(){ if(!soundOn()) return; try{
  const AC=window.AudioContext||window.webkitAudioContext; if(!AC) return;
  const ac=chime._ac||(chime._ac=new AC()); if(ac.state==='suspended') ac.resume(); const now=ac.currentTime;
  [[784,0],[1046.5,0.1],[1318.5,0.2]].forEach(p=>{ const o=ac.createOscillator(),g=ac.createGain(); o.type='sine'; o.frequency.value=p[0]; o.connect(g); g.connect(ac.destination); const t=now+p[1]; g.gain.setValueAtTime(0.0001,t); g.gain.exponentialRampToValueAtTime(0.13,t+0.02); g.gain.exponentialRampToValueAtTime(0.0001,t+0.24); o.start(t); o.stop(t+0.26); });
}catch(e){} }
function toast(text){ const el=document.createElement('div'); el.textContent=text;
  el.style.cssText='position:fixed;left:50%;bottom:26px;transform:translateX(-50%);z-index:9999;background:var(--navy-900);color:var(--ink);border:1px solid var(--border-gold);border-radius:999px;padding:11px 20px;font-family:var(--font-mono);font-size:.82rem;box-shadow:0 12px 40px rgba(0,0,0,.5)';
  document.body.appendChild(el); setTimeout(()=>{ el.style.transition='opacity .5s'; el.style.opacity='0'; setTimeout(()=>el.remove(),500); },4200); }
const fmtWhen = d => d ? new Date(d).toLocaleString(undefined,{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}) : '';
const STATUS_META={ open:{label:'Open',color:'var(--gold-2)',bg:'var(--gold-soft)'}, tp1_hit:{label:'TP1 hit — running',color:'#4a9eda',bg:'rgba(74,158,218,.12)'}, tp2_hit:{label:'TP2 hit — full win ✓',color:'var(--green)',bg:'rgba(46,160,90,.12)'}, sl_hit:{label:'SL hit ✕',color:'var(--red)',bg:'rgba(214,69,69,.12)'}, cancelled:{label:'Cancelled',color:'var(--ink-faint)',bg:'var(--surface-2)'}, closed:{label:'Closed',color:'#7aa2f7',bg:'rgba(122,162,247,.12)'} };

// Signal price columns are text — a coach may type "3412.50", "3412.5 (zone)"
// or "3410 - 3414". Take the first number and ignore the rest; anything we
// cannot read comes through as null so the student fills it in themselves.
function firstNum(v){
  if(v===null||v===undefined) return null;
  const m = String(v).replace(/,/g,'').match(/-?\d+(\.\d+)?/);
  return m ? Number(m[0]) : null;
}
// PH-time date of the signal, so the journal entry is dated the trading day the
// student actually saw it, not the viewer's local calendar day.
function phDateOf(iso){
  const t = iso ? Date.parse(iso) : Date.now();
  return new Date((isNaN(t)?Date.now():t) + 8*3600000).toISOString().slice(0,10);
}
function signalToEntry(r){
  return {
    trade_date: phDateOf(r.created_at),
    pair: r.pair || '',
    direction: r.direction === 'sell' ? 'short' : 'long',
    entry_price: firstNum(r.entry),
    stop_loss:   firstNum(r.stop_loss),
    take_profit: firstNum(r.take_profit_1 || r.take_profit),
    signal_id: r.id,
    notes: 'From coach signal' + (r.coach_name ? ' · ' + r.coach_name : '')
         + (r.timeframe ? ' · ' + r.timeframe : ''),
  };
}

function Row({r, onImg, onLog}){ const sm=STATUS_META[r.status]||STATUS_META.open; const dc=r.direction==='buy'?'var(--green)':'var(--red)';
  const faded = r.status==='cancelled';
  return <div style={{padding:16,borderRadius:'var(--radius-sm)',border:'1px solid var(--border)',background:'var(--surface)',borderLeft:'3px solid '+dc,display:'flex',gap:14,flexWrap:'wrap',opacity:faded?.6:1}}>
    {r.photo_url && <img src={r.photo_url} alt="chart" onClick={()=>onImg(r.photo_url)} style={{width:120,height:84,objectFit:'cover',borderRadius:'var(--radius-sm)',border:'1px solid var(--border)',cursor:'zoom-in'}}/>}
    <div style={{flex:1,minWidth:210}}>
      <div style={{display:'flex',gap:10,alignItems:'center',flexWrap:'wrap'}}>
        <strong style={{fontFamily:'var(--font-mono)',letterSpacing:'.04em',fontSize:'1.02rem'}}>{r.pair}</strong>
        <span style={{fontFamily:'var(--font-mono)',fontSize:'.8rem',fontWeight:700,color:dc}}>{r.direction==='buy'?'▲ BUY':'▼ SELL'}</span>
        {r.timeframe && <span style={{fontSize:'.74rem',color:'var(--ink-faint)',fontFamily:'var(--font-mono)'}}>{r.timeframe}</span>}
        <span className="badge" style={{background:sm.bg,color:sm.color,border:'1px solid var(--border)',fontSize:'.68rem',padding:'4px 11px'}}>{sm.label}</span>
      </div>
      <div style={{fontSize:'.82rem',color:'var(--ink-dim)',fontFamily:'var(--font-mono)',marginTop:8}}>
        {['Entry '+(r.entry||'—'),'SL '+(r.stop_loss||'—'),'TP1 '+(r.take_profit_1||r.take_profit||'—'),r.take_profit_2?('TP2 '+r.take_profit_2):null,r.confidence?('Conf '+r.confidence.toUpperCase()):null,r.risk_note?('Risk '+r.risk_note):null].filter(Boolean).join('   ·   ')}
      </div>
      {r.message && <div style={{fontSize:'.9rem',color:'var(--ink)',marginTop:8,whiteSpace:'pre-wrap',lineHeight:1.5}}>{r.message}</div>}
      {r.result_note && <div style={{fontSize:'.8rem',color:'var(--ink-faint)',marginTop:4,fontStyle:'italic'}}>Note: {r.result_note}</div>}
      <div style={{display:'flex',alignItems:'center',gap:12,flexWrap:'wrap',marginTop:8}}>
        <div style={{fontSize:'.7rem',color:'var(--ink-faint)',fontFamily:'var(--font-mono)'}}>{fmtWhen(r.created_at)} · {r.coach_name||'Coach'}</div>
        {onLog && <button onClick={()=>onLog(signalToEntry(r))} title="Pre-fill a journal entry from this signal"
          style={{marginLeft:'auto',cursor:'pointer',padding:'6px 12px',borderRadius:8,
            border:'1px solid var(--border)',background:'var(--surface-2)',color:'var(--ink-dim)',
            fontSize:'.72rem',fontWeight:600,display:'inline-flex',alignItems:'center',gap:6}}>
          <svg viewBox="0 0 24 24" style={{width:13,height:13,fill:'none',stroke:'currentColor',strokeWidth:1.8,strokeLinecap:'round',strokeLinejoin:'round'}}>
            <path d="M5 4h13v16H7a2 2 0 0 1-2-2z"/><path d="M9 9h7M9 13h5"/></svg>
          Log this trade
        </button>}
      </div>
    </div>
  </div>;
}

function SignalsFeed({client, eligible, onLog}){
  const [rows,setRows]=useState(null); const [limit,setLimit]=useState(6); const [collapsed,setCollapsed]=useState(false);
  const [zoom,setZoom]=useState(null); const seen=useRef(new Set()); const first=useRef(true);

  async function load(){
    const { data } = await client.from('signals').select('*').order('created_at',{ascending:false}).limit(50);
    if(!data) return;
    if(first.current){ data.forEach(r=>seen.current.add(r.id)); first.current=false; }
    else { const fresh=data.filter(r=>!seen.current.has(r.id)); data.forEach(r=>seen.current.add(r.id));
      if(fresh.length){ chime(); toast('📊 New signal · '+fresh[0].pair+' '+fresh[0].direction.toUpperCase()); } }
    setRows(data);
  }
  useEffect(()=>{ if(!eligible) return; load(); const t=setInterval(load,15000); return ()=>clearInterval(t); },[eligible]);

  if(!eligible) return null;
  if(rows===null) return <div style={{padding:16,marginBottom:24,border:'1px solid var(--border)',borderRadius:'var(--radius)',background:'var(--surface)',color:'var(--ink-faint)',fontSize:'.85rem'}}><span className="cg-spin" style={{width:13,height:13}}></span> Loading signals…</div>;
  if(rows.length===0) return null;

  return <div style={{marginBottom:26,border:'1px solid var(--border-gold)',borderRadius:'var(--radius)',background:'linear-gradient(165deg,var(--surface-2),var(--surface))',overflow:'hidden'}}>
    <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',gap:12,padding:'16px 20px',borderBottom:collapsed?'none':'1px solid var(--border)'}}>
      <div style={{display:'flex',alignItems:'center',gap:13,minWidth:0}}>
        {/* Same VIP Signal mark that heads the Telegram broadcasts — students
            recognise it instantly, so the portal and the channel read as one product. */}
        <img src="assets/brand/vip-128.png" alt="VIP Signal" width="42" height="42" loading="lazy"
          style={{width:42,height:42,borderRadius:10,border:'1px solid var(--border-gold)',flexShrink:0,boxShadow:'0 2px 10px rgba(214,168,58,.22)'}}/>
        <div style={{minWidth:0}}>
          <div style={{fontWeight:700,fontSize:'1.02rem'}}>VIP Signals</div>
          <div style={{fontSize:'.78rem',color:'var(--ink-dim)'}}>Live calls from your coaches · also sent to your Telegram</div>
        </div>
      </div>
      <div style={{display:'flex',alignItems:'center',gap:6}}>
        <button onClick={()=>{ localStorage.setItem(SOUND_KEY, soundOn()?'0':'1'); setCollapsed(c=>c); setRows(r=>r?[...r]:r); }} title="Toggle sound" style={{background:'none',border:0,cursor:'pointer',color:'var(--ink-faint)',fontSize:'1rem'}}>{soundOn()?'🔔':'🔕'}</button>
        <button onClick={()=>setCollapsed(c=>!c)} className="btn btn-ghost" style={{width:'auto',padding:'6px 12px',fontSize:'.78rem'}}>{collapsed?'Show':'Hide'}</button>
      </div>
    </div>
    {!collapsed && <div style={{padding:16,display:'grid',gap:12}}>
      {rows.slice(0,limit).map(r=><Row key={r.id} r={r} onImg={setZoom} onLog={onLog}/>)}
      {rows.length>limit && <button className="btn btn-ghost" style={{width:'auto',justifySelf:'center',padding:'8px 18px',fontSize:'.82rem'}} onClick={()=>setLimit(l=>l+8)}>Show older signals ↓</button>}
    </div>}
    {zoom && <div onClick={()=>setZoom(null)} style={{position:'fixed',inset:0,zIndex:300,background:'rgba(4,7,14,.85)',backdropFilter:'blur(6px)',display:'grid',placeItems:'center',padding:24,cursor:'zoom-out'}}>
      <img src={zoom} alt="chart" style={{maxWidth:'92vw',maxHeight:'90vh',borderRadius:'var(--radius)',border:'1px solid var(--border-gold)'}}/>
    </div>}
  </div>;
}
window.SignalsFeed = SignalsFeed;
window.cgSignalToEntry = signalToEntry;
})();
