/* Report Builder — main tab =============================================
   home (saved reports + template picker) → setup (scope + preflight +
   brand kit) → builder (3-zone canvas: block library | 16:9 preview +
   inspector | slide rail). Autosave: localStorage instantly, server on
   idle. reportDef is the single source of truth (see spec).
======================================================================== */

const R_LS_DRAFT = 'relay_report_draft_';

function rISO(ms){ const d=new Date(ms); return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); }
function rCompareRange(mode, start, end){
  const s = new Date(start+'T00:00:00'), e = new Date(end+'T00:00:00');
  const days = Math.round((e-s)/86400000)+1;
  if (mode==='same_period_last_year'){
    const cs = new Date(s); cs.setFullYear(cs.getFullYear()-1);
    const ce = new Date(e); ce.setFullYear(ce.getFullYear()-1);
    return { start: rISO(+cs), end: rISO(+ce) };
  }
  const ce = new Date(s); ce.setDate(ce.getDate()-1);
  const cs = new Date(ce); cs.setDate(cs.getDate()-days+1);
  return { start: rISO(+cs), end: rISO(+ce) };
}

/* ---------- Data hooks -------------------------------------------------- */
function useReportDataset(def){
  const [ds, setDs] = useState(null);
  const [loading, setLoading] = useState(false);
  const [err, setErr] = useState('');
  const scope = def && def.scope;
  const key = scope ? [def.accountId, (scope.platforms||[]).join(','), scope.start, scope.end,
    scope.source || 'system',
    scope.compare && scope.compare.mode, scope.compare && scope.compare.start].join('|') : '';
  useEffect(()=>{
    if (!def || !def.accountId || !scope.start) return;
    let dead = false;
    setLoading(true); setErr('');
    const qs = new URLSearchParams({ account:def.accountId, start:scope.start, end:scope.end,
      src: scope.source === 'manual' ? 'manual' : 'system' });
    if (scope.platforms && scope.platforms.length) qs.set('platforms', scope.platforms.join(','));
    if (scope.compare && scope.compare.mode !== 'prev_period' && scope.compare.start){
      qs.set('cstart', scope.compare.start); qs.set('cend', scope.compare.end);
    }
    RelayAPI.get('/report/dataset?' + qs.toString())
      .then(d=>{ if(!dead){ setDs(d); setLoading(false); } })
      .catch(e=>{ if(!dead){ setErr(e.message||'Failed to load data'); setLoading(false); } });
    return ()=>{ dead = true; };
  }, [key]);
  return { ds, loading, err };
}
function useBrandKits(accountId){
  const [kits, setKits] = useState([]);
  const reload = ()=> RelayAPI.get('/report/brandkits?account='+encodeURIComponent(accountId||''))
    .then(r=>setKits(r.kits||[])).catch(()=>setKits([]));
  useEffect(()=>{ if (accountId) reload(); }, [accountId]);
  return { kits, reload };
}

/* ---------- Brand kit editor (Phase 7) ---------------------------------- */
function BrandKitEditor({ open, onClose, kit, accountId, onSaved }){
  const blank = { name:'', account_id:accountId, colors:['#233433','#45464B','#488D82'],
    logo_path:null, logo_dark_path:null, bg_path:null, bg_path_flipped:null, font:'', rtl:false };
  const [k, setK] = useState(blank);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState('');
  useEffect(()=>{ if (open) setK(kit ? JSON.parse(JSON.stringify(kit)) : { ...blank, account_id:accountId }); }, [open, kit]);
  if (!open) return null;
  const setColor = (i, v)=> setK(x=>({ ...x, colors: x.colors.map((c,j)=>j===i?v:c) }));
  const upload = async (field, file)=>{
    if (!file) return;
    setBusy(true); setError('');
    try {
      const fd = new FormData(); fd.append('file', file);
      const res = await fetch(API_BASE + '/report/asset', { method:'POST', credentials:'include', body:fd });
      const j = await res.json();
      if (!res.ok) throw new Error(j.error || 'Upload failed');
      setK(x=>({ ...x, [field]: j.path }));
    } catch(e){ setError(e.message); }
    setBusy(false);
  };
  const save = async ()=>{
    if (!k.name.trim()){ setError('Give the kit a name'); return; }
    setBusy(true); setError('');
    try {
      const res = await RelayAPI.post('/report/brandkit', {
        id:k.id, account_id:k.account_id, name:k.name.trim(),
        logo_path:k.logo_path, logo_dark_path:k.logo_dark_path,
        bg_path:k.bg_path, bg_path_flipped:k.bg_path_flipped,
        color_main:k.colors[0], color_secondary:k.colors[1], color_tertiary:k.colors[2],
        font:k.font || null, rtl:!!k.rtl,
      });
      onSaved && onSaved(res.id);
      onClose();
    } catch(e){ setError(e.message); }
    setBusy(false);
  };
  const FileBtn = ({ field, label })=>(
    <label className="block cursor-pointer">
      <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-1">{label}</div>
      <div className="h-16 rounded-lg border border-dashed border-ink-200 dark:border-white/15 grid place-items-center text-[11px] text-ink-400 hover:border-accent-400 overflow-hidden"
        style={ k[field] ? { backgroundImage:`url(${k[field]})`, backgroundSize:'contain',
          backgroundRepeat:'no-repeat', backgroundPosition:'center',
          backgroundColor: field==='logo_dark_path'||field.startsWith('bg') ? k.colors[0] : '#fff' } : {} }>
        {!k[field] && 'Upload PNG/JPG'}
      </div>
      <input type="file" accept="image/png,image/jpeg,image/webp" className="hidden"
        onChange={e=>upload(field, e.target.files[0])}/>
    </label>
  );
  const contrast = k.colors.map(c=>rContrastText(c));
  return (
    <Modal open={open} onClose={busy?()=>{}:onClose} width={620}>
      <div className="p-5">
        <div className="text-[15px] font-bold mb-3">{kit ? 'Edit brand kit' : 'New brand kit'}</div>
        <div className="grid sm:grid-cols-2 gap-3">
          <label className="block sm:col-span-2">
            <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-1">Kit name</div>
            <input value={k.name} onChange={e=>setK(x=>({...x,name:e.target.value}))}
              className="w-full h-9 px-3 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[12.5px]"/>
          </label>
          {['Main','Secondary','Tertiary'].map((lbl,i)=>(
            <label key={i} className="block">
              <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-1">{lbl} color</div>
              <div className="flex items-center gap-2">
                <input type="color" value={k.colors[i]} onChange={e=>setColor(i, e.target.value.toUpperCase())}
                  className="w-9 h-9 rounded-lg border border-ink-200 dark:border-white/10 cursor-pointer"/>
                <input value={k.colors[i]} onChange={e=>setColor(i, e.target.value)}
                  className="flex-1 h-9 px-2 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[12px] font-mono"/>
                <span className="text-[9.5px] px-1.5 py-0.5 rounded font-bold"
                  style={{background:k.colors[i], color:contrast[i]}}>Aa</span>
              </div>
            </label>
          ))}
          <label className="block">
            <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-1">Font (optional)</div>
            <input value={k.font||''} onChange={e=>setK(x=>({...x,font:e.target.value}))} placeholder="e.g. Sakkal Majalla"
              className="w-full h-9 px-3 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[12.5px]"/>
          </label>
          <FileBtn field="logo_path" label="Logo (light backgrounds)"/>
          <FileBtn field="logo_dark_path" label="Logo — white / knockout"/>
          <FileBtn field="bg_path" label="Branded background (covers)"/>
          <FileBtn field="bg_path_flipped" label="Background — mirrored variant"/>
          <label className="flex items-center gap-2 text-[12.5px] cursor-pointer sm:col-span-2">
            <Toggle checked={!!k.rtl} onChange={v=>setK(x=>({...x,rtl:v}))} size="sm"/>
            Right-to-left (Arabic) brand
          </label>
        </div>
        {/* palette ramp preview */}
        <div className="mt-3 flex gap-1">
          {rRamp(k.colors, 8).map((c,i)=><span key={i} className="flex-1 h-4 rounded" style={{background:c}}/>)}
        </div>
        {error && <div className="mt-3 text-[12px] text-rose-600 dark:text-rose-300">{error}</div>}
        <div className="mt-4 flex justify-end gap-2">
          <Btn variant="ghost" onClick={onClose}>Cancel</Btn>
          <Btn variant="accent" onClick={save} className={busy?'opacity-60 pointer-events-none':''}>
            {busy?'Saving…':'Save kit'}</Btn>
        </div>
      </div>
    </Modal>
  );
}

/* ---------- Followers quick entry --------------------------------------- */
function FollowersEntry({ accountId, platforms, endDate, onSaved }){
  const [openIt, setOpenIt] = useState(false);
  const [rows, setRows] = useState({});
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const plats = rSortPlatforms(platforms||[]);
  const save = async ()=>{
    const payload = plats.filter(p=>rows[p] && (rows[p].g || rows[p].t)).map(p=>({
      platform:p, date:endDate,
      followers: rows[p].t ? +rows[p].t : undefined,
      followers_gained: rows[p].g ? +rows[p].g : undefined,
    }));
    if (!payload.length) return;
    setBusy(true); setErr('');
    try {
      await RelayAPI.post('/report/brandmetrics', { account_id:accountId, rows:payload });
      onSaved && onSaved();
      setOpenIt(false);
    } catch(e){ setErr(e.message || 'Save failed'); }   /* audit: was silently swallowed */
    setBusy(false);
  };
  return (
    <div className="border border-ink-200 dark:border-white/10 rounded-xl p-3">
      <button onClick={()=>setOpenIt(o=>!o)} className="w-full flex items-center gap-2 text-[12.5px] font-semibold">
        <Icon name="users" size={14} className="text-ink-400"/>
        Followers (for the Executive Summary)
        <span className="text-[10.5px] text-ink-400 font-normal">captured automatically from uploaded files & connected social sources — enter manually only if missing</span>
        <Icon name="chev-down" size={13} className={`ml-auto transition-transform ${openIt?'rotate-180':''}`}/>
      </button>
      {openIt && (
        <div className="mt-3 space-y-2">
          {plats.map(p=>(
            <div key={p} className="flex items-center gap-2">
              <span className="inline-flex w-5 justify-center"><BrandIcon name={rPlatform(p).icon} size={15}/></span>
              <span className="text-[12px] font-medium w-24">{rPlatform(p).label}</span>
              <input placeholder="Gained this period" type="number"
                value={(rows[p]&&rows[p].g)||''} onChange={e=>setRows(r=>({...r,[p]:{...(r[p]||{}),g:e.target.value}}))}
                className="flex-1 h-8 px-2 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[12px]"/>
              <input placeholder="Total now" type="number"
                value={(rows[p]&&rows[p].t)||''} onChange={e=>setRows(r=>({...r,[p]:{...(r[p]||{}),t:e.target.value}}))}
                className="flex-1 h-8 px-2 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[12px]"/>
            </div>
          ))}
          {err && <div className="text-[11px] text-rose-600 dark:text-rose-300">{err}</div>}
          <div className="flex justify-end"><Btn size="sm" variant="subtle" onClick={save}
            className={busy?'opacity-50 pointer-events-none':''}>{busy?'Saving…':'Save followers'}</Btn></div>
        </div>
      )}
    </div>
  );
}

/* ---------- Block inspector ---------------------------------------------- */
function BlockInspector({ def, slide, block, onPatchBinding, onPatchOptions, onRemove }){
  if (!block) return (
    <div className="text-[11.5px] text-ink-400 px-1 py-6 text-center">
      Select a block on the slide to edit it — or add one from the library below.
    </div>
  );
  const b = block.binding || {}, o = block.options || {};
  const meta = R_BLOCKS[block.type] || {};
  const metricChoices = rAllMetrics().filter(m=>m.tier!==3 && m.agg!=='last');
  const scopePlats = def.scope.platforms || [];
  const Sel = ({ label, value, onChange, children })=>(
    <label className="block mb-2.5">
      <div className="text-[10px] uppercase font-semibold text-ink-400 mb-1">{label}</div>
      <select value={value} onChange={e=>onChange(e.target.value)}
        className="w-full h-8 px-2 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[12px]">
        {children}
      </select>
    </label>
  );
  const multiMetrics = ['scorecard','metric_grid','trend','table'].includes(block.type);
  return (
    <div>
      <div className="flex items-center gap-2 mb-3">
        <Icon name={meta.icon||'columns'} size={14} className="text-accent-500"/>
        <span className="text-[12.5px] font-bold">{meta.label||block.type}</span>
        <button onClick={onRemove} title="Remove block"
          className="ml-auto text-ink-400 hover:text-rose-500"><Icon name="trash" size={14}/></button>
      </div>

      {['scorecard','metric_grid','trend','bar','donut','table','heatmap','ranked_list','perf_flags'].includes(block.type) && (
        <Sel label="Platform" value={b.platform||''} onChange={v=>onPatchBinding({ platform:v||undefined })}>
          <option value="">All (report scope)</option>
          {scopePlats.map(p=><option key={p} value={p}>{rPlatform(p).label}</option>)}
        </Sel>
      )}
      {['table','ranked_list','bar','perf_flags'].includes(block.type) && (
        <Sel label="Level" value={b.level||'campaign'} onChange={v=>onPatchBinding({ level:v })}>
          <option value="campaign">Campaigns</option>
          <option value="adset">Ad sets</option>
          <option value="ad">Ads</option>
        </Sel>
      )}
      {['bar','donut','heatmap','ranked_list'].includes(block.type) && (
        <Sel label="Metric" value={b.metric||'spend'} onChange={v=>onPatchBinding({ metric:v })}>
          {metricChoices.map(m=><option key={m.key} value={m.key}>{m.label}</option>)}
        </Sel>
      )}
      {block.type==='ranked_list' && (
        <Sel label="Secondary metric" value={b.secondary||'ctr'} onChange={v=>onPatchBinding({ secondary:v })}>
          {metricChoices.map(m=><option key={m.key} value={m.key}>{m.label}</option>)}
        </Sel>
      )}
      {multiMetrics && (
        <div className="mb-2.5">
          <div className="text-[10px] uppercase font-semibold text-ink-400 mb-1">Metrics</div>
          <div className="flex flex-wrap gap-1">
            {metricChoices.map(m=>{
              const on = (b.metrics||[]).includes(m.key);
              return (
                <button key={m.key} onClick={()=>{
                  const cur = new Set(b.metrics||[]);
                  on ? (cur.size>1 && cur.delete(m.key)) : cur.add(m.key);
                  onPatchBinding({ metrics:[...cur] });
                }} className={`text-[10.5px] px-1.5 py-0.5 rounded-full border transition ${on
                  ? 'border-accent-400 bg-accent-50 dark:bg-accent-500/10 text-accent-700 dark:text-accent-300 font-semibold'
                  : 'border-ink-200 dark:border-white/10 text-ink-500'}`}>{m.label}</button>
              );
            })}
          </div>
        </div>
      )}
      {['table','ranked_list'].includes(block.type) && (
        <div className="grid grid-cols-2 gap-2">
          <Sel label="Sort by" value={b.sortKey||b.metric||'spend'} onChange={v=>onPatchBinding({ sortKey:v })}>
            {metricChoices.map(m=><option key={m.key} value={m.key}>{m.label}</option>)}
          </Sel>
          <Sel label="Top N" value={String(b.topN||10)} onChange={v=>onPatchBinding({ topN:+v })}>
            {[5,8,10,12,15,20].map(n=><option key={n} value={n}>{n}</option>)}
          </Sel>
        </div>
      )}
      {block.type==='table' && (
        <div className="mb-2.5">
          <div className="text-[10px] uppercase font-semibold text-ink-400 mb-1">Heatmap columns</div>
          <div className="flex flex-wrap gap-1">
            {(b.metrics||[]).map(k=>{
              const on = (o.heatmap||[]).includes(k);
              return <button key={k} onClick={()=>{
                const cur = new Set(o.heatmap||[]); on ? cur.delete(k) : cur.add(k);
                onPatchOptions({ heatmap:[...cur] });
              }} className={`text-[10.5px] px-1.5 py-0.5 rounded-full border ${on
                ? 'border-amber-400 bg-amber-50 dark:bg-amber-500/10 text-amber-700 font-semibold'
                : 'border-ink-200 dark:border-white/10 text-ink-500'}`}>{rMetricLabel(k,'en')}</button>;
            })}
          </div>
        </div>
      )}
      {block.type==='trend' && (
        <label className="flex items-center gap-2 text-[12px] mb-2.5 cursor-pointer">
          <Toggle checked={o.showCompare!==false} onChange={v=>onPatchOptions({ showCompare:v })} size="sm"/>
          Show comparison period (ghost line)
        </label>
      )}
      {block.type==='text' && (
        <div>
          <label className="flex items-center gap-2 text-[12px] mb-2 cursor-pointer">
            <Toggle checked={!!o.autoInsights} onChange={v=>onPatchOptions({ autoInsights:v })} size="sm"/>
            Auto-insights from the data
          </label>
          <div className="text-[10px] uppercase font-semibold text-ink-400 mb-1">Bullets (one per line — overrides auto)</div>
          <textarea rows={5} value={(o.bullets||[]).join('\n')}
            onChange={e=>onPatchOptions({ bullets: e.target.value.split('\n').filter(x=>x.trim()) })}
            className="w-full px-2 py-1.5 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[12px]"/>
        </div>
      )}
      {block.type==='image' && (
        <label className="block mb-2.5 cursor-pointer">
          <div className="text-[10px] uppercase font-semibold text-ink-400 mb-1">Image</div>
          <div className="h-14 rounded-lg border border-dashed border-ink-200 dark:border-white/15 grid place-items-center text-[11px] text-ink-400 hover:border-accent-400"
            style={o.src?{backgroundImage:`url(${o.src})`,backgroundSize:'contain',backgroundRepeat:'no-repeat',backgroundPosition:'center'}:{}}>
            {!o.src && 'Upload'}
          </div>
          <input type="file" accept="image/png,image/jpeg,image/webp" className="hidden" onChange={async e=>{
            const file = e.target.files[0]; if (!file) return;
            try {                                       /* audit: was an unhandled rejection on network failure */
              const fd = new FormData(); fd.append('file', file);
              const res = await fetch(API_BASE + '/report/asset', { method:'POST', credentials:'include', body:fd });
              const j = await res.json().catch(()=>({}));
              if (res.ok) onPatchOptions({ src:j.path });
              else console.warn('[report] image upload failed:', j.error || res.status);
            } catch(err){ console.warn('[report] image upload failed:', err.message); }
          }}/>
        </label>
      )}
      <Sel label="Width" value={String(o.span || (R_BLOCKS[block.type]||{}).span || 12)}
        onChange={v=>onPatchOptions({ span:+v })}>
        <option value="12">Full width</option>
        <option value="6">Half width</option>
      </Sel>
    </div>
  );
}

/* ---------- Slide rail (drag reorder) ------------------------------------ */
function SlideRail({ def, current, onSelect, onMove, onRemove, onAdd }){
  const dragFrom = useRef(null);
  const bounds = rSlideBounds(def);
  return (
    <div className="w-[168px] flex-shrink-0 border-l border-ink-200 dark:border-white/10 overflow-y-auto scroll-soft p-2 space-y-2">
      {def.slides.map((s,i)=>{
        const movable = i >= bounds.firstMovable && i <= bounds.lastMovable;
        return (
          <div key={s.id}
            draggable={movable}
            onDragStart={()=>{ dragFrom.current = i; }}
            onDragOver={e=>{ if (movable) e.preventDefault(); }}
            onDrop={e=>{ e.preventDefault(); if (dragFrom.current!=null && movable) onMove(dragFrom.current, i); dragFrom.current=null; }}
            onClick={()=>onSelect(i)}
            className={`relative rounded-lg border-2 transition cursor-pointer group ${i===current
              ? 'border-accent-500' : 'border-transparent hover:border-ink-300 dark:hover:border-white/20'}`}>
            <div className={`rounded-md overflow-hidden text-[8px] leading-tight h-[86px] p-1.5 flex flex-col
              ${['cover','divider','thank_you'].includes(s.type) ? 'text-white' : 'bg-white dark:bg-ink-800 border border-ink-100 dark:border-white/10'}`}
              style={['cover','divider','thank_you'].includes(s.type)?{background:'#233433'}:{}}>
              <div className="font-bold text-[9px] truncate" dir="auto">{s.title || s.type}</div>
              <div className={`mt-auto text-[7.5px] opacity-60 ${s.locked?'':''}`}>
                {s.locked ? '🔒 ' : ''}{s.type.replace('_',' ')} · {(s.blocks||[]).length||'—'}
              </div>
            </div>
            <span className="absolute -top-1.5 -left-1.5 w-5 h-5 rounded-full bg-ink-900 text-white dark:bg-white dark:text-ink-900 text-[9px] font-bold grid place-items-center">{i+1}</span>
            {rCanRemove(s) && (
              <button onClick={e=>{ e.stopPropagation(); onRemove(i); }}
                className="absolute -top-1.5 -right-1.5 w-5 h-5 rounded-full bg-rose-500 text-white grid place-items-center opacity-0 group-hover:opacity-100 transition">
                <Icon name="x" size={10}/>
              </button>
            )}
          </div>
        );
      })}
      <button onClick={onAdd}
        className="w-full h-[52px] rounded-lg border-2 border-dashed border-ink-200 dark:border-white/15 text-ink-400 hover:border-accent-400 hover:text-accent-500 transition grid place-items-center text-[11px] font-semibold">
        + Add slide
      </button>
    </div>
  );
}

/* ---------- The builder ---------------------------------------------------- */
function ReportBuilderView({ initialDef, savedId, onExit, currentUser, accounts }){
  const [def, setDefRaw] = useState(initialDef);
  const [reportId, setReportId] = useState(savedId || null);
  const [cur, setCur] = useState(0);
  const [selBlock, setSelBlock] = useState(null);
  const [showExport, setShowExport] = useState(false);
  const [showKitEditor, setShowKitEditor] = useState(false);
  const [saveState, setSaveState] = useState('saved');   // saved | dirty | saving
  const history = useRef({ past:[], future:[] });
  const { ds, loading, err } = useReportDataset(def);
  const { kits, reload:reloadKits } = useBrandKits(def.accountId);
  const kit = kits.find(k=>k.id===def.brandKitId) || kits.find(k=>k.account_id===def.accountId) || kits[0] || null;
  const theme = rTheme(kit, def.lang);
  const currency = (ds && ds.account.currency) || 'USD';
  useEffect(()=>{ rLoadCustomMetrics(def.accountId); }, [def.accountId]);

  const setDef = (next, recordHistory)=>{
    setDefRaw(prev=>{
      const v = typeof next === 'function' ? next(prev) : next;
      if (recordHistory !== false){
        history.current.past.push(prev);
        if (history.current.past.length > 40) history.current.past.shift();
        history.current.future = [];
      }
      try { localStorage.setItem(R_LS_DRAFT + (v.accountId||''), JSON.stringify({ id:reportId, def:v, at:Date.now() })); } catch(e){}
      setSaveState('dirty');
      return v;
    });
  };
  const undo = ()=>{ const p = history.current.past.pop(); if (p){ history.current.future.push(def); setDefRaw(p); setSaveState('dirty'); } };
  const redo = ()=>{ const f = history.current.future.pop(); if (f){ history.current.past.push(def); setDefRaw(f); setSaveState('dirty'); } };
  useEffect(()=>{
    const onKey = (e)=>{
      if ((e.metaKey||e.ctrlKey) && e.key.toLowerCase()==='z'){ e.preventDefault(); e.shiftKey ? redo() : undo(); }
    };
    window.addEventListener('keydown', onKey);
    return ()=>window.removeEventListener('keydown', onKey);
  });

  /* server autosave on idle — mounted guard stops setState-after-unmount
     when the user exits the builder while a save is in flight (audit fix) */
  const mountedRef = useRef(true);
  useEffect(()=>{ mountedRef.current = true; return ()=>{ mountedRef.current = false; }; }, []);
  useEffect(()=>{
    if (saveState !== 'dirty') return;
    const t = setTimeout(async ()=>{
      if (!mountedRef.current) return;
      setSaveState('saving');
      try {
        const res = await RelayAPI.post('/report/save', {
          id: reportId || undefined, account_id: def.accountId, name: def.name,
          def, brand_kit_id: def.brandKitId || (kit && kit.id) || null,
          /* DB column is ENUM('en','ar'); 'both' lives in the def JSON */
          lang: def.lang === 'en' ? 'en' : 'ar' });
        if (!mountedRef.current) return;
        if (res.id) setReportId(res.id);
        setSaveState('saved');
      } catch(e){ if (mountedRef.current) setSaveState('dirty'); }
    }, 2500);
    return ()=>clearTimeout(t);
  }, [def, saveState]);

  const slide = def.slides[Math.min(cur, def.slides.length-1)];
  const patchSlide = (i, patch)=> setDef(d=>({ ...d, slides: d.slides.map((s,j)=>j===i?{...s,...patch}:s) }));
  const patchBlockBinding = (blockId, patch)=> setDef(d=>({ ...d, slides: d.slides.map((s,j)=> j!==cur ? s :
    { ...s, blocks: s.blocks.map(b=> b.id===blockId ? { ...b, binding:{ ...b.binding, ...patch } } : b) }) }));
  const patchBlockOptions = (blockId, patch)=> setDef(d=>({ ...d, slides: d.slides.map((s,j)=> j!==cur ? s :
    { ...s, blocks: s.blocks.map(b=> b.id===blockId ? { ...b, options:{ ...b.options, ...patch } } : b) }) }));
  const addBlock = (type)=>{
    const blk = rBlock(type, type==='trend' ? { metrics:['spend'] } :
      type==='table' ? { level:'campaign', metrics:['spend','impressions','clicks','ctr','conversions','cpa'], sortKey:'spend', sortDir:'desc', topN:10 } :
      ['scorecard','metric_grid'].includes(type) ? { metrics:['spend','conversions','cpa','roas'] } : {});
    setDef(d=>({ ...d, slides: d.slides.map((s,j)=> j===cur ? { ...s, blocks:[...(s.blocks||[]), blk] } : s) }));
    setSelBlock(blk.id);
  };
  const removeBlock = (blockId)=>{
    setDef(d=>({ ...d, slides: d.slides.map((s,j)=> j===cur ? { ...s, blocks:s.blocks.filter(b=>b.id!==blockId) } : s) }));
    setSelBlock(null);
  };
  const addSlide = ()=>{
    const s = rSlide('content', def.lang==='ar'?'شريحة جديدة':'New slide', []);
    setDef(d=>{
      const idx = d.slides.findIndex(x=>x.type==='thank_you');
      const slides = [...d.slides]; slides.splice(idx, 0, s);
      return { ...d, slides };
    });
    setCur(def.slides.findIndex(x=>x.type==='thank_you'));
  };
  const canvasW = 780;
  const selectedBlock = slide && (slide.blocks||[]).find(b=>b.id===selBlock);

  return (
    <div className="flex flex-col h-full min-h-0" style={{minHeight:'calc(100vh - 130px)'}}>
      {/* toolbar */}
      <div className="flex items-center gap-2 pb-3 flex-wrap">
        <Btn size="sm" variant="ghost" icon="chev-left" onClick={onExit}>Reports</Btn>
        <input value={def.name} onChange={e=>setDef(d=>({...d, name:e.target.value}))}
          className="h-9 px-3 rounded-lg border border-transparent hover:border-ink-200 dark:hover:border-white/10 bg-transparent text-[13.5px] font-bold min-w-[240px] flex-1 max-w-[420px]"/>
        <span className={`text-[10.5px] font-mono ${saveState==='saved'?'text-emerald-600':'text-ink-400'}`}>
          {saveState==='saved'?'✓ saved':saveState==='saving'?'saving…':'…'}
        </span>
        <div className="flex-1"/>
        <select value={def.brandKitId || (kit&&kit.id) || ''}
          onChange={e=>setDef(d=>({...d, brandKitId:e.target.value}))}
          className="h-9 px-2 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[12px]">
          {kits.map(k=><option key={k.id} value={k.id}>{k.name}</option>)}
        </select>
        <Btn size="sm" variant="ghost" icon="palette" onClick={()=>setShowKitEditor(true)}>Brand</Btn>
        <Btn size="sm" variant="accent" icon="download" onClick={()=>setShowExport(true)}>Export</Btn>
      </div>

      {err && <div className="mb-3 px-3 py-2 rounded-xl bg-rose-50 dark:bg-rose-500/10 text-[12px] text-rose-600 dark:text-rose-300">{err}</div>}

      <div className="flex flex-1 min-h-0 rounded-2xl border border-ink-200 dark:border-white/10 bg-ink-50/50 dark:bg-ink-900/40 overflow-hidden">
        {/* left: block library + inspector */}
        <div className="w-[224px] flex-shrink-0 border-r border-ink-200 dark:border-white/10 overflow-y-auto scroll-soft p-3">
          <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-2">Add block</div>
          <div className="grid grid-cols-2 gap-1.5 mb-4">
            {Object.entries(R_BLOCKS).filter(([t])=>t!=='metric_grid').map(([t,meta])=>(
              <button key={t} onClick={()=>slide && !slide.locked!==false && addBlock(t)}
                disabled={!slide || ['cover','agenda','thank_you','divider'].includes(slide.type)}
                className="px-2 py-2 rounded-lg border border-ink-200 dark:border-white/10 text-left hover:border-accent-400 transition disabled:opacity-30 disabled:pointer-events-none">
                <Icon name={meta.icon} size={13} className="text-accent-500"/>
                <div className="text-[10.5px] font-semibold mt-1 leading-tight">{meta.label}</div>
              </button>
            ))}
          </div>
          <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-2 pt-2 border-t border-ink-100 dark:border-white/10">
            Block settings
          </div>
          <BlockInspector def={def} slide={slide} block={selectedBlock}
            onPatchBinding={(p)=>patchBlockBinding(selBlock, p)}
            onPatchOptions={(p)=>patchBlockOptions(selBlock, p)}
            onRemove={()=>removeBlock(selBlock)}/>
        </div>

        {/* center: canvas */}
        <div className="flex-1 min-w-0 overflow-auto scroll-soft p-5 grid place-items-start justify-center"
          onClick={()=>setSelBlock(null)}>
          <div>
            {slide && !slide.locked && (
              <input value={slide.title} onChange={e=>patchSlide(cur, { title:e.target.value })}
                dir="auto" placeholder="Slide title"
                className="mb-2 w-full h-8 px-2 rounded-lg border border-transparent hover:border-ink-200 dark:hover:border-white/10 bg-transparent text-[13px] font-bold"/>
            )}
            {loading && <div className="mb-2 text-[11px] text-ink-400 font-mono animate-pulse">loading data…</div>}
            {slide && ds && (
              <RSlideScaled width={canvasW}>
                <RSlide def={def} slide={slide} ds={ds} theme={theme} currency={currency}
                  editable selectedBlockId={selBlock} onSelectBlock={setSelBlock}
                  onBinding={patchBlockBinding}/>
              </RSlideScaled>
            )}
            {slide && !ds && !loading && (
              <div style={{width:canvasW}} className="aspect-video grid place-items-center rounded-xl border border-dashed border-ink-300 dark:border-white/20 text-ink-400 text-[13px]">
                {err || 'No data loaded'}
              </div>
            )}
            {slide && slide.locked && (
              <div className="mt-2 text-[10.5px] text-ink-400 flex items-center gap-1.5">
                <Icon name="lock" size={11}/> Fixed slide — content adapts automatically; title editable in the rail.
              </div>
            )}
          </div>
        </div>

        {/* right: slide rail */}
        <SlideRail def={def} current={cur}
          onSelect={(i)=>{ setCur(i); setSelBlock(null); }}
          onMove={(f,t)=>setDef(d=>rMoveSlide(d, f, t))}
          onRemove={(i)=>{ setDef(d=>({ ...d, slides:d.slides.filter((_,j)=>j!==i) })); if (cur>=i) setCur(c=>Math.max(0,c-1)); }}
          onAdd={addSlide}/>
      </div>

      <ReportExportModal open={showExport} onClose={()=>setShowExport(false)} def={def} ds={ds} kit={kit}/>
      <BrandKitEditor open={showKitEditor} onClose={()=>setShowKitEditor(false)} kit={kit}
        accountId={def.accountId} onSaved={(id)=>{ reloadKits(); setDef(d=>({...d, brandKitId:id})); }}/>
    </div>
  );
}

/* ---------- Setup (scope + preflight) ------------------------------------- */
function ReportSetup({ template, accounts, currentUser, onBack, onLaunch, globalAccountId, globalRange }){
  /* Client templates carry their ad account(s): filter + default + lock. */
  const clientIds = template.clientAccounts || null;
  const selectable = clientIds ? accounts.filter(a=>clientIds.includes(a.id)) : accounts;
  const lockedToClient = !!clientIds && selectable.length <= 1;
  const [accountId, setAccountId] = useState(
    clientIds ? ((selectable[0]||{}).id || '') :
    (accounts.some(a=>a.id===globalAccountId) ? globalAccountId : (accounts[0]||{}).id));
  const [source, setSource] = useState('system');   // 'system' | 'manual' — hard wall
  const [langSel, setLangSel] = useState(template.lang || 'en');   // 'en' | 'ar' | 'both'
  const [plats, setPlats] = useState([]);
  const [start, setStart] = useState(rISO(globalRange ? globalRange.start : Date.now()-29*86400000));
  const [end, setEnd] = useState(rISO(globalRange ? globalRange.end : Date.now()));
  const [cmpMode, setCmpMode] = useState('prev_period');
  const [brandName, setBrandName] = useState('');
  const [showUpload, setShowUpload] = useState(false);
  /* agency credit — the workspace (FLNT) as report author; separate from the
     client brand. Not offered on the Al Munawarah template (client decision). */
  const agencyBrand = typeof loadBranding==='function' ? loadBranding() : { name:'FLNT', logo:null };
  const showAgency = template.id !== 'tpl_almunawarah';
  const [agencyOn, setAgencyOn] = useState(showAgency);
  const acc = accounts.find(a=>a.id===accountId);
  /* preflight dataset probe */
  const probeDef = useMemo(()=>({ accountId, lang: template.lang||'en',
    scope:{ platforms:plats, start, end, source,
      compare:{ mode:cmpMode, ...rCompareRange(cmpMode, start, end) } } }),
    [accountId, plats.join(','), start, end, cmpMode, source]);
  const { ds, loading } = useReportDataset(probeDef);
  const avail = ds ? rDatasetPlatforms(ds) : [];
  const perPlat = ds ? rByPlatform(ds) : [];
  const fresh = (ds && ds.freshness) || {};
  useEffect(()=>{ if (ds && !plats.length && avail.length) setPlats(avail); }, [ds ? avail.join(',') : '']);

  const launch = ()=>{
    const ctx = { accountId, lang: langSel,
      brandName: brandName.trim() || (acc?acc.name:''),
      brandKitId: template.brandKitId || null,
      agency: showAgency && agencyOn
        ? { enabled:true, name:agencyBrand.name || 'FLNT', logo:agencyBrand.logo || null }
        : null,
      scope:{ platforms: plats.length?plats:avail, start, end, source,
        compare:{ mode:cmpMode, ...rCompareRange(cmpMode, start, end) } } };
    const def = template.build(ctx);
    def.accountId = accountId;
    onLaunch(def);
  };
  const fmtLag = (s)=> s==null ? 'no live feed' : s<3600 ? Math.round(s/60)+'m ago' : s<86400 ? Math.round(s/3600)+'h ago' : Math.round(s/86400)+'d ago';

  return (
    <div className="max-w-[860px] mx-auto">
      <Btn size="sm" variant="ghost" icon="chev-left" onClick={onBack}>Templates</Btn>
      <div className="mt-3 mb-5">
        <div className="text-[19px] font-bold">{template.name}</div>
        <div className="text-[12.5px] text-ink-500 dark:text-ink-300 mt-0.5">{template.desc}</div>
      </div>

      <div className="space-y-4">
        <div className="grid sm:grid-cols-3 gap-3">
          <label className="block">
            <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-1">
              Ad account{clientIds ? ' — filtered to this client' : ''}
            </div>
            <select value={accountId} disabled={lockedToClient}
              onChange={e=>{ setAccountId(e.target.value); setPlats([]); }}
              className={`w-full h-10 px-2 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[13px] ${lockedToClient?'opacity-70 cursor-not-allowed':''}`}>
              {selectable.length===0 && <option value="">— no access to this client's accounts —</option>}
              {selectable.map(a=><option key={a.id} value={a.id}>{a.name}</option>)}
            </select>
            {lockedToClient && selectable.length===1 &&
              <div className="text-[10.5px] text-ink-400 mt-1">🔒 This template belongs to {selectable[0].name} — account locked.</div>}
          </label>
          <label className="block">
            <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-1">Brand name (on the cover)</div>
            <input value={brandName} onChange={e=>setBrandName(e.target.value)} placeholder={acc?acc.name:''}
              className="w-full h-10 px-3 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[13px]"/>
          </label>
          <label className="block">
            <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-1">Report language</div>
            <select value={langSel} onChange={e=>setLangSel(e.target.value)}
              className="w-full h-10 px-2 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[13px]">
              <option value="en">English</option>
              <option value="ar">العربية (RTL)</option>
              <option value="both">Bilingual — عربي · English (RTL)</option>
            </select>
          </label>
        </div>

        {/* Data source — hard wall: a report reads EXACTLY one source */}
        <div className="border border-ink-200 dark:border-white/10 rounded-xl p-3.5">
          <div className="text-[12.5px] font-semibold mb-2">Data source</div>
          <div className="grid grid-cols-2 gap-2">
            {[['system','Internal system','Live data synced from Meta / Google / TikTok connections'],
              ['manual','Manual uploads','Data imported from CSV / Excel files only']].map(([id,label,desc])=>(
              <button key={id} onClick={()=>{ setSource(id); setPlats([]); }}
                className={`text-left px-3 py-2.5 rounded-xl border transition ${source===id
                  ? 'border-accent-400 bg-accent-50/60 dark:bg-accent-500/10'
                  : 'border-ink-200 dark:border-white/10 opacity-70 hover:opacity-100'}`}>
                <div className="text-[12.5px] font-bold flex items-center gap-2">
                  <Icon name={id==='manual'?'upload':'zap'} size={13} className="text-accent-500"/>{label}
                  {source===id && <Icon name="check" size={13} className="ml-auto text-accent-600"/>}
                </div>
                <div className="text-[10.5px] text-ink-500 dark:text-ink-300 mt-0.5">{desc}</div>
              </button>
            ))}
          </div>
          <div className="text-[10.5px] text-ink-400 mt-2">
            The two sources never mix — every number in this report comes only from the selected source.
          </div>
        </div>

        <div className="grid sm:grid-cols-3 gap-3">
          <label className="block">
            <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-1">From</div>
            <input type="date" value={start} onChange={e=>setStart(e.target.value)}
              className="w-full h-10 px-3 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[13px]"/>
          </label>
          <label className="block">
            <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-1">To</div>
            <input type="date" value={end} onChange={e=>setEnd(e.target.value)}
              className="w-full h-10 px-3 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[13px]"/>
          </label>
          <label className="block">
            <div className="text-[10.5px] uppercase font-semibold text-ink-400 mb-1">Compare against</div>
            <select value={cmpMode} onChange={e=>setCmpMode(e.target.value)}
              className="w-full h-10 px-2 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/60 text-[13px]">
              <option value="prev_period">Previous period (same length)</option>
              <option value="same_period_last_year">Same period last year</option>
            </select>
          </label>
        </div>

        {/* preflight: platform picker with data-health */}
        <div className="border border-ink-200 dark:border-white/10 rounded-xl p-3.5">
          <div className="text-[12.5px] font-semibold mb-2 flex items-center gap-2">
            Platforms
            {loading && <span className="text-[10.5px] text-ink-400 font-mono animate-pulse">checking data…</span>}
          </div>
          {!loading && avail.length===0 && (
            <div className="text-[12px] text-amber-600 dark:text-amber-300">
              No ad data in this range for this account. Pick another range, or import files below.
            </div>
          )}
          <div className="grid sm:grid-cols-2 gap-2">
            {rSortPlatforms([...new Set([...avail, ...plats])]).map(p=>{
              const row = perPlat.find(x=>x.platform===p);
              const spend = row ? rMetricValue(row.cur,'spend') : 0;
              const on = plats.includes(p);
              const lag = fresh[p];
              const cur = (ds && ds.account.currency) || 'USD';
              return (
                <button key={p} onClick={()=>setPlats(cur2=> on ? cur2.filter(x=>x!==p) : [...cur2, p])}
                  className={`flex items-center gap-2.5 px-3 py-2.5 rounded-xl border text-left transition ${on
                    ? 'border-accent-400 bg-accent-50/60 dark:bg-accent-500/10'
                    : 'border-ink-200 dark:border-white/10 opacity-60 hover:opacity-100'}`}>
                  <BrandIcon name={rPlatform(p).icon} size={18}/>
                  <span className="min-w-0 flex-1">
                    <span className="block text-[12.5px] font-bold">{rPlatform(p).label}</span>
                    <span className="block text-[10.5px] text-ink-500 dark:text-ink-300 font-mono">
                      {row ? rFmt(spend,'currency',cur,'en') + ' spend' : 'no rows'} · {fmtLag(lag)}
                    </span>
                  </span>
                  {on && <Icon name="check" size={15} className="text-accent-600"/>}
                </button>
              );
            })}
          </div>
          <button onClick={()=>setShowUpload(true)}
            className="mt-3 text-[12px] text-accent-600 dark:text-accent-300 font-semibold">
            ⤒ Import CSV / Excel files (Snapchat, X, LinkedIn or any export)
          </button>
        </div>

        {showAgency && (
          <label className="flex items-center gap-2.5 border border-ink-200 dark:border-white/10 rounded-xl p-3 cursor-pointer">
            <Toggle checked={agencyOn} onChange={setAgencyOn} size="sm"/>
            <span className="text-[12.5px] font-semibold flex items-center gap-2">
              {agencyBrand.logo && <img src={agencyBrand.logo} className="h-5 max-w-[60px] object-contain"/>}
              Add “Prepared by {agencyBrand.name || 'FLNT'}” agency credit
            </span>
            <span className="text-[10.5px] text-ink-400">the agency logo/name — separate from the client's brand kit</span>
          </label>
        )}

        <FollowersEntry accountId={accountId} platforms={plats.length?plats:avail} endDate={end}/>

        <div className="flex justify-end gap-2 pb-8">
          <Btn variant="ghost" onClick={onBack}>Back</Btn>
          <Btn variant="accent" icon="chev-right" onClick={launch}
            className={(!accountId || loading) ? 'opacity-50 pointer-events-none' : ''}>
            Build report
          </Btn>
        </div>
      </div>

      <ReportUploadWizard open={showUpload} onClose={()=>setShowUpload(false)}
        accountId={accountId} accounts={accounts} currentUser={currentUser}
        onDone={(res)=>{
          setShowUpload(false);
          setSource('manual');   // imported files live behind the manual wall
          if (res.accId !== accountId) setAccountId(res.accId);
          if (res.brandName) setBrandName(res.brandName);
          if (res.start){ setStart(res.start); setEnd(res.end); }
          setPlats(rSortPlatforms(res.platforms || []));
        }}/>
    </div>
  );
}

/* ---------- Home: saved reports + template picker -------------------------- */
function ReportsHome({ accounts, currentUser, onOpenTemplate, onOpenSaved, globalAccountId }){
  const [saved, setSaved] = useState(null);
  useEffect(()=>{
    RelayAPI.get('/report/list?account='+encodeURIComponent(globalAccountId||''))
      .then(r=>setSaved(r.reports||[])).catch(()=>setSaved([]));
  }, [globalAccountId]);
  const iconTone = { tpl_monthly:'#2f7bff', tpl_wrapup:'#f59e0b', tpl_weekly:'#10b981',
    tpl_almunawarah:'#A67E32', tpl_blank:'#8b8b80' };
  return (
    <div>
      <div className="flex items-center gap-3 mb-5">
        <span className="inline-flex items-center justify-center w-10 h-10 rounded-xl bg-accent-100 dark:bg-accent-500/15 text-accent-600 dark:text-accent-300">
          <Icon name="file" size={20}/>
        </span>
        <div>
          <h1 className="text-[20px] font-semibold tracking-tight leading-none">Report Builder</h1>
          <div className="text-[12px] text-ink-500 dark:text-ink-300 mt-1">
            Branded client reports — PPTX, PDF and Excel from live or imported data.
          </div>
        </div>
      </div>

      <div className="text-[12.5px] font-semibold mb-2">Start from a template</div>
      <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-3 mb-7">
        {REPORT_TEMPLATES.map(t=>(
          <button key={t.id} onClick={()=>onOpenTemplate(t)}
            className="text-left p-4 rounded-2xl border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/50 hover:border-accent-400 hover:shadow-card transition group">
            <span className="inline-flex items-center justify-center w-9 h-9 rounded-lg mb-2.5"
              style={{background:(iconTone[t.id]||'#8b8b80')+'22', color:iconTone[t.id]||'#8b8b80'}}>
              <Icon name={t.icon} size={17}/>
            </span>
            <div className="text-[13.5px] font-bold flex items-center gap-2">
              {t.name}
              {t.lang==='ar' && <Pill tone="neutral">عربي · RTL</Pill>}
            </div>
            <div className="text-[11.5px] text-ink-500 dark:text-ink-300 mt-1 leading-relaxed">{t.desc}</div>
          </button>
        ))}
      </div>

      <div className="text-[12.5px] font-semibold mb-2">Saved reports</div>
      {saved===null && <div className="text-[12px] text-ink-400 font-mono animate-pulse">loading…</div>}
      {saved && saved.length===0 && (
        <div className="text-[12px] text-ink-400 border border-dashed border-ink-200 dark:border-white/10 rounded-xl px-4 py-6 text-center">
          Nothing saved yet — reports autosave while you build.
        </div>
      )}
      {saved && saved.length>0 && (
        <div className="space-y-1.5">
          {saved.map(r=>(
            <button key={r.id} onClick={()=>onOpenSaved(r.id)}
              className="w-full flex items-center gap-3 px-3.5 py-2.5 rounded-xl border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/50 hover:border-accent-400 transition text-left">
              <Icon name={r.is_template?'star':'file'} size={15} className="text-ink-400"/>
              <span className="text-[13px] font-semibold flex-1 truncate" dir="auto">{r.name}</span>
              {r.is_template && <Pill tone="accent">template</Pill>}
              {r.lang==='ar' && <Pill tone="neutral">عربي</Pill>}
              <span className="text-[10.5px] text-ink-400 font-mono">{String(r.updated_at||'').slice(0,16)}</span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

/* ---------- Root routed view ---------------------------------------------- */
function ReportsView({ accountId, currentUser }){
  const g = useGlobal();
  const accounts = g.myAccounts || [];
  const [mode, setMode] = useState('home');            // home | setup | builder
  const [template, setTemplate] = useState(null);
  const [builderDef, setBuilderDef] = useState(null);
  const [builderId, setBuilderId] = useState(null);
  const canBuild = typeof can==='function' ? can(currentUser,'reports.build') : true;

  const openSaved = async (id)=>{
    try {
      const r = await RelayAPI.get('/report/get?id='+encodeURIComponent(id));
      if (r.report && r.report.def){
        const def = r.report.def;
        def.accountId = def.accountId || r.report.account_id;
        def.brandKitId = def.brandKitId || r.report.brand_kit_id;
        setBuilderDef(def); setBuilderId(id); setMode('builder');
      }
    } catch(e){ console.warn(e); }
  };
  if (!canBuild){
    return <div className="text-[13px] text-ink-500 dark:text-ink-300 border border-dashed border-ink-200 dark:border-white/10 rounded-2xl px-5 py-10 text-center">
      Your role cannot build reports — ask an admin for Reporter or Analyst access (or higher).
    </div>;
  }
  if (mode==='builder' && builderDef){
    return <ReportBuilderView initialDef={builderDef} savedId={builderId}
      onExit={()=>{ setMode('home'); setBuilderDef(null); setBuilderId(null); }}
      currentUser={currentUser} accounts={accounts}/>;
  }
  if (mode==='setup' && template){
    return <ReportSetup template={template} accounts={accounts} currentUser={currentUser}
      globalAccountId={accountId} globalRange={g.dateRange}
      onBack={()=>setMode('home')}
      onLaunch={(def)=>{ setBuilderDef(def); setBuilderId(null); setMode('builder'); }}/>;
  }
  return <ReportsHome accounts={accounts} currentUser={currentUser} globalAccountId={accountId}
    onOpenTemplate={(t)=>{ setTemplate(t); setMode('setup'); }} onOpenSaved={openSaved}/>;
}

Object.assign(window, { ReportsView, ReportBuilderView, ReportSetup, ReportsHome,
  BrandKitEditor, FollowersEntry, useReportDataset });
