// ============================================================
// SocialPulse — Reports Page (async + tab-resilient + safe markdown)
// ============================================================

// Module-level in-flight state (survives page navigation, survives browser
// refresh via /api/reports/generating).
const _reportJobs = new Map(); // key -> { key, id?, title, pct, startedAt, status, error? }
const _reportListeners = new Set();
function _notifyReports() { _reportListeners.forEach(fn => { try { fn(); } catch {} }); }

function _syntheticPct(startedAt) {
  const s = (Date.now() - startedAt) / 1000;
  if (s < 5) return 5;
  if (s < 30) return 5 + (s - 5) * 1.5;   // 5 → 42 over 30s
  if (s < 90) return 42 + (s - 30) * 0.7; // 42 → 84 over 60s
  if (s < 180) return Math.min(84 + (s - 90) * 0.1, 93);
  return 95;
}

async function _syncGenerating() {
  try {
    const rows = await spReports.generating();
    const seenIds = new Set();
    for (const r of (rows || [])) {
      seenIds.add(r.id);
      const existing = _reportJobs.get(String(r.id));
      if (existing) {
        existing.pct = _syntheticPct(existing.startedAt);
      } else {
        _reportJobs.set(String(r.id), {
          key: String(r.id), id: r.id, title: r.title,
          pct: _syntheticPct(Date.parse(r.created_at) || Date.now()),
          startedAt: Date.parse(r.created_at) || Date.now(),
          status: 'generating',
        });
      }
    }
    // Drop any server-id-keyed job the server no longer reports.
    for (const [k, job] of _reportJobs) {
      if (job.id != null && !seenIds.has(job.id)) _reportJobs.delete(k);
    }
    _notifyReports();
  } catch {}
}

let _pollTimer = null;
function _startPollingIfNeeded() {
  if (_pollTimer || !_reportJobs.size) return;
  _pollTimer = setInterval(() => {
    _syncGenerating().finally(() => {
      if (!_reportJobs.size) { clearInterval(_pollTimer); _pollTimer = null; }
    });
  }, 5000);
}

async function spStartReport(body, titleHint = 'AI 報告生成中…') {
  const tempKey = 'tmp_' + Date.now();
  _reportJobs.set(tempKey, {
    key: tempKey, title: titleHint,
    pct: 5, startedAt: Date.now(), status: 'generating',
  });
  _notifyReports();
  _startPollingIfNeeded();
  try {
    const r = await spReports.generate(body);
    _reportJobs.delete(tempKey);
    await _syncGenerating();
    _notifyReports();
    spFetchMe();
    return r;
  } catch (e) {
    _reportJobs.delete(tempKey);
    _notifyReports();
    throw e;
  }
}

async function spRestoreReportJobs() { await _syncGenerating(); _startPollingIfNeeded(); }

function useReportJobs() {
  const [, setTick] = useState(0);
  useEffect(() => {
    const fn = () => setTick(v => v + 1);
    _reportListeners.add(fn);
    return () => _reportListeners.delete(fn);
  }, []);
  // Keep synthetic pct ticking locally so the bar moves even without server sync.
  useEffect(() => {
    const tid = setInterval(() => {
      let changed = false;
      for (const job of _reportJobs.values()) {
        const next = _syntheticPct(job.startedAt);
        if (Math.abs(next - job.pct) > 0.5) { job.pct = next; changed = true; }
      }
      if (changed) _notifyReports();
    }, 1500);
    return () => clearInterval(tid);
  }, []);
  return Array.from(_reportJobs.values());
}

// Read-receipt tracker — used to gate deletion and render a NEW badge on
// reports the user hasn't opened yet. localStorage keeps it client-side; users
// can clear storage to "re-notify" themselves.
const _READ_KEY = 'sp_reports_read';
function spReadReports() {
  try { return JSON.parse(localStorage.getItem(_READ_KEY) || '[]'); }
  catch { return []; }
}
function spMarkReportRead(id) {
  if (!id || id === 'demo') return;
  const ids = spReadReports();
  if (!ids.includes(id)) {
    ids.push(id);
    localStorage.setItem(_READ_KEY, JSON.stringify(ids.slice(-200)));
  }
}
function spIsReportRead(id) {
  if (!id || id === 'demo') return true;
  return spReadReports().includes(id);
}

// Strip **bold** / *italic* / leading #/> markers so summary previews don't
// render literal asterisks when the backend stored markdown-flavored text.
function _stripMd(s) {
  if (!s) return '';
  return String(s)
    .replace(/\*\*(.+?)\*\*/g, '$1')
    .replace(/\*(.+?)\*/g, '$1')
    .replace(/^\s*[#>]+\s*/gm, '')
    .trim();
}

Object.assign(window, { spStartReport, spRestoreReportJobs, useReportJobs, _stripMd, spMarkReportRead, spIsReportRead });

// ------------------------------------------------------------
// Safe markdown renderer — escape first, handle a small subset:
// headings (# ## ###), bold **x**, italic *x*, bullet lists,
// numbered lists, blank-line paragraphs, --- horizontal rule.
// Returns React nodes so we never touch innerHTML.
// ------------------------------------------------------------
function _renderInline(text, keyPrefix = '') {
  // Tokenize [text](url), `code`, **bold**, *italic*. Link first so its label
  // can itself contain emphasis; then code; then bold; then italic.
  const parts = [];
  let rest = text;
  let i = 0;
  const LINK = /\[([^\]]+)\]\(([^)\s]+)\)/;
  const CODE = /`([^`]+)`/;
  const BOLD = /\*\*([^*]+)\*\*/;
  const ITAL = /\*([^*]+)\*/;
  const safeUrl = (u) => /^(https?:|mailto:|\/|#)/i.test(u) ? u : '#';
  while (rest.length) {
    const lm = rest.match(LINK);
    const cm = rest.match(CODE);
    const bm = rest.match(BOLD);
    const im = rest.match(ITAL);
    const candidates = [
      lm && { idx: lm.index, len: lm[0].length, kind: 'link', m: lm },
      cm && { idx: cm.index, len: cm[0].length, kind: 'code', m: cm },
      bm && { idx: bm.index, len: bm[0].length, kind: 'bold', m: bm },
      im && { idx: im.index, len: im[0].length, kind: 'ital', m: im },
    ].filter(Boolean);
    if (!candidates.length) { parts.push(rest); break; }
    candidates.sort((a,b) => a.idx - b.idx || (a.kind === 'link' ? -1 : 1));
    const pick = candidates[0];
    if (pick.idx > 0) parts.push(rest.slice(0, pick.idx));
    if (pick.kind === 'link') {
      parts.push(
        <a key={`${keyPrefix}-l${i++}`} href={safeUrl(pick.m[2])} target="_blank" rel="noopener noreferrer"
           style={{color:'#6366f1',textDecoration:'underline'}}>
          {_renderInline(pick.m[1], `${keyPrefix}-l${i}`)}
        </a>
      );
    } else if (pick.kind === 'code') {
      parts.push(
        <code key={`${keyPrefix}-c${i++}`} style={{fontFamily:'var(--font-mono)',fontSize:'0.9em',background:'rgba(10,10,19,.06)',padding:'1px 5px',borderRadius:3}}>
          {pick.m[1]}
        </code>
      );
    } else if (pick.kind === 'bold') {
      parts.push(<strong key={`${keyPrefix}-b${i++}`}>{_renderInline(pick.m[1], `${keyPrefix}-b${i}`)}</strong>);
    } else {
      parts.push(<em key={`${keyPrefix}-i${i++}`}>{_renderInline(pick.m[1], `${keyPrefix}-i${i}`)}</em>);
    }
    rest = rest.slice(pick.idx + pick.len);
  }
  return parts;
}

function _parseTableRow(line) {
  // Strip leading/trailing pipes then split by unescaped pipe.
  let s = line.trim();
  if (s.startsWith('|')) s = s.slice(1);
  if (s.endsWith('|')) s = s.slice(0, -1);
  return s.split('|').map(c => c.trim());
}

function _isTableSep(line) {
  return /^\s*\|?\s*:?-{3,}:?(\s*\|\s*:?-{3,}:?)+\s*\|?\s*$/.test(line);
}

function Markdown({ text }) {
  if (!text) return null;
  const lines = String(text).replace(/\r\n/g, '\n').split('\n');
  const blocks = [];
  let buf = [];
  let listType = null; // 'ul' | 'ol' | null
  let listItems = [];

  const flushParagraph = () => {
    if (!buf.length) return;
    const key = `p-${blocks.length}`;
    blocks.push(
      <p key={key} style={{margin:'0 0 14px',lineHeight:1.75}}>
        {buf.map((l, i) => (
          <React.Fragment key={i}>
            {_renderInline(l, `${key}-l${i}`)}
            {i < buf.length - 1 && <br/>}
          </React.Fragment>
        ))}
      </p>
    );
    buf = [];
  };
  const flushList = () => {
    if (!listItems.length) return;
    const key = `list-${blocks.length}`;
    const Tag = listType === 'ol' ? 'ol' : 'ul';
    blocks.push(
      <Tag key={key} style={{margin:'0 0 14px',paddingLeft:24,lineHeight:1.75}}>
        {listItems.map((li, i) => <li key={i}>{_renderInline(li, `${key}-li${i}`)}</li>)}
      </Tag>
    );
    listItems = []; listType = null;
  };

  for (let idx = 0; idx < lines.length; idx++) {
    const raw = lines[idx];
    const line = raw.replace(/\s+$/, '');

    // Table: header | sep | body rows (pipe-delimited, sep line uses ---).
    if (line.includes('|') && idx + 1 < lines.length && _isTableSep(lines[idx + 1])) {
      flushParagraph(); flushList();
      const headers = _parseTableRow(line);
      const rows = [];
      idx += 2; // skip header + sep
      while (idx < lines.length && lines[idx].includes('|') && lines[idx].trim()) {
        rows.push(_parseTableRow(lines[idx]));
        idx++;
      }
      idx--; // loop will ++
      const key = `tbl-${blocks.length}`;
      blocks.push(
        <div key={key} style={{overflowX:'auto',margin:'0 0 16px'}}>
          <table style={{borderCollapse:'collapse',width:'100%',fontSize:13}}>
            <thead>
              <tr>
                {headers.map((h, i) => (
                  <th key={i} style={{textAlign:'left',padding:'8px 10px',borderBottom:'2px solid #0a0a13',fontWeight:700,background:'rgba(10,10,19,.04)'}}>
                    {_renderInline(h, `${key}-h${i}`)}
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {rows.map((r, ri) => (
                <tr key={ri}>
                  {r.map((c, ci) => (
                    <td key={ci} style={{padding:'8px 10px',borderBottom:'1px solid rgba(10,10,19,.1)',verticalAlign:'top'}}>
                      {_renderInline(c, `${key}-r${ri}c${ci}`)}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      );
      continue;
    }

    if (!line.trim()) { flushParagraph(); flushList(); continue; }
    const hMatch = line.match(/^(#{1,3})\s+(.*)$/);
    if (hMatch) {
      flushParagraph(); flushList();
      const lvl = hMatch[1].length;
      const sz = lvl === 1 ? 22 : lvl === 2 ? 18 : 15;
      const margin = lvl === 1 ? '24px 0 12px' : '18px 0 8px';
      const Tag = `h${lvl}`;
      blocks.push(
        <Tag key={`h-${blocks.length}`} style={{fontSize:sz,fontWeight:700,margin:margin,letterSpacing:'-.01em'}}>
          {_renderInline(hMatch[2], `h-${blocks.length}`)}
        </Tag>
      );
      continue;
    }
    if (line.trim() === '---') {
      flushParagraph(); flushList();
      blocks.push(<hr key={`hr-${blocks.length}`} style={{border:0,borderTop:'1px solid #0a0a13',margin:'20px 0'}}/>);
      continue;
    }
    const bullet = line.match(/^\s*[-*]\s+(.+)$/);
    const numbered = line.match(/^\s*\d+\.\s+(.+)$/);
    if (bullet) {
      flushParagraph();
      if (listType !== 'ul') { flushList(); listType = 'ul'; }
      listItems.push(bullet[1]);
      continue;
    }
    if (numbered) {
      flushParagraph();
      if (listType !== 'ol') { flushList(); listType = 'ol'; }
      listItems.push(numbered[1]);
      continue;
    }
    flushList();
    buf.push(line);
  }
  flushParagraph(); flushList();
  return <>{blocks}</>;
}

// ------------------------------------------------------------
// Pages
// ------------------------------------------------------------
function ReportsPage() {
  const reports = useApi(() => spReports.list({ limit: 50 }), []);
  const [selectedReportId, setSelectedReportId] = useState(null);
  const [showGenerate, setShowGenerate] = useState(false);
  const [demoDismissed, setDemoDismissed] = useState(spIsDemoReportDismissed());
  const jobs = useReportJobs();

  // Restore any in-flight jobs on mount.
  useEffect(() => { spRestoreReportJobs(); }, []);

  // When a job disappears, reload the list.
  const lastCount = useRef(jobs.length);
  useEffect(() => {
    if (lastCount.current > 0 && jobs.length === 0) reports.reload();
    lastCount.current = jobs.length;
  }, [jobs.length]);

  const realReports = reports.data || [];
  const showDemo = !reports.loading && !reports.error && realReports.length === 0 && !demoDismissed;
  const demo = showDemo ? spGetDemoReport() : null;
  const listItems = demo ? [demo, ...realReports] : realReports;

  const handleDemoDismissed = () => {
    spDismissDemoReport();
    setDemoDismissed(true);
    setSelectedReportId(null);
  };

  if (selectedReportId) {
    if (selectedReportId === 'demo') {
      return <ReportDetail demoReport={demo || spGetDemoReport()} onBack={()=>setSelectedReportId(null)} onDeleted={handleDemoDismissed}/>;
    }
    return <ReportDetail reportId={selectedReportId} onBack={()=>{setSelectedReportId(null);reports.reload();}} onDeleted={()=>{setSelectedReportId(null);reports.reload();}}/>;
  }

  return (
    <div>
      <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:20}}>
        <h2 style={{fontSize:22,fontWeight:800,letterSpacing:'-.02em'}}>Reports</h2>
        <span style={{fontFamily:'var(--font-mono)',fontSize:11,color:'var(--text3)',padding:'3px 8px',border:'1px solid var(--line)',borderRadius:'var(--r-xs)'}}>{(reports.data||[]).length}</span>
        <div style={{marginLeft:'auto'}}>
          <button className="btn btn-primary"
            onClick={()=>setShowGenerate(true)}
            disabled={jobs.length > 0}
            title={jobs.length > 0 ? 'AI 報告生成中，請稍候…' : ''}
            style={jobs.length > 0 ? {opacity:.5,cursor:'not-allowed'} : undefined}>
            {jobs.length > 0 ? '生成中…' : '+ 生成報告'}
          </button>
        </div>
      </div>

      {jobs.length > 0 && <ReportProgressBanner jobs={jobs}/>}

      {reports.loading ? <LoadingState/> :
       reports.error ? <ErrorState error={reports.error} onRetry={reports.reload}/> :
       listItems.length === 0 && jobs.length === 0 ? <EmptyState title="尚無報告" desc="點「+ 生成報告」建立第一份"/> :
      <div style={{display:'flex',flexDirection:'column',gap:12}}>
        {listItems.map(r => {
          const unread = !r.is_demo && !spIsReportRead(r.id);
          return (
            <div key={r.id} onClick={()=>{ spMarkReportRead(r.id); setSelectedReportId(r.id); }}
              onMouseOver={e=>e.currentTarget.style.borderColor='var(--indigo)'}
              onMouseOut={e=>e.currentTarget.style.borderColor=unread?'rgba(99,102,241,.4)':'var(--line)'}
              style={{background:'var(--panel)',border:`1px solid ${unread?'rgba(99,102,241,.4)':'var(--line)'}`,borderRadius:'var(--r-xl)',padding:'20px 24px',cursor:'pointer',transition:'border-color .15s',position:'relative'}}>
              <div style={{display:'flex',justifyContent:'space-between',alignItems:'flex-start',marginBottom:8}}>
                <div style={{minWidth:0,flex:1}}>
                  <div style={{fontSize:16,fontWeight:700,marginBottom:2,display:'flex',alignItems:'center',gap:8,flexWrap:'wrap'}}>
                    {r.title}
                    {r.is_demo && <span style={{fontFamily:'var(--font-mono)',fontSize:10,fontWeight:700,color:'var(--purple)',padding:'2px 8px',borderRadius:100,background:'rgba(168,85,247,.2)',letterSpacing:'.08em',textTransform:'uppercase'}}>DEMO</span>}
                    {unread && <span style={{fontFamily:'var(--font-mono)',fontSize:10,fontWeight:700,color:'var(--bg)',padding:'2px 7px',borderRadius:'var(--r-xs)',background:'var(--lime)',letterSpacing:'.08em'}}>NEW</span>}
                  </div>
                  <div style={{display:'flex',gap:8,alignItems:'center'}}>
                    <span style={{fontFamily:'var(--font-mono)',fontSize:10,color:'var(--text3)'}}>{r.created_at?.slice(0,10)}</span>
                    {r.week_start && <span style={{fontFamily:'var(--font-mono)',fontSize:10,color:'var(--text3)'}}>{r.week_start} → {r.week_end}</span>}
                  </div>
                </div>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--text3)" strokeWidth="2"><path d="M9 18l6-6-6-6"/></svg>
              </div>
              {r.summary && <div style={{fontSize:13,color:'var(--text2)',lineHeight:1.6,display:'-webkit-box',WebkitLineClamp:2,WebkitBoxOrient:'vertical',overflow:'hidden'}}>{_stripMd(r.summary)}</div>}
            </div>
          );
        })}
      </div>}

      {showGenerate && <GenerateReportModal onClose={()=>setShowGenerate(false)} onQueued={()=>{setShowGenerate(false);}}/>}
    </div>
  );
}

function ReportProgressBanner({ jobs }) {
  const avgPct = Math.round(jobs.reduce((s,j)=>s+(j.pct||0),0) / jobs.length);
  return (
    <div style={{
      background:'var(--panel)',border:'1px solid var(--line)',borderRadius:'var(--r-lg)',
      padding:'14px 20px',marginBottom:16,
    }}>
      <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:8}}>
        <span style={{width:8,height:8,borderRadius:'50%',background:'var(--indigo2)',boxShadow:'0 0 8px var(--indigo2)',animation:'spin .8s linear infinite'}}/>
        <span style={{fontSize:14,fontWeight:600}}>AI 正在生成 {jobs.length} 份報告</span>
        <span style={{marginLeft:'auto',fontFamily:'var(--font-mono)',fontSize:11,color:'var(--text3)'}}>{avgPct}%</span>
      </div>
      <div style={{height:4,background:'var(--line)',borderRadius:2,overflow:'hidden'}}>
        <div style={{height:'100%',width:`${avgPct}%`,background:'var(--indigo)',borderRadius:2,transition:'width .3s'}}/>
      </div>
      <div style={{fontSize:11,color:'var(--text3)',fontFamily:'var(--font-mono)',marginTop:8}}>
        可切換分頁或重新整理頁面，進度不會遺失
      </div>
    </div>
  );
}

function _ymd(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  return `${y}-${m}-${day}`;
}

function DateField({ label, value, onChange, min, max }) {
  return (
    <div>
      <label style={{display:'block',fontSize:12,color:'var(--text2)',marginBottom:6,fontWeight:500}}>{label}</label>
      <input type="date" value={value || ''} onChange={e=>onChange(e.target.value)} min={min} max={max}
        style={{
          width:'100%',padding:'10px 12px',background:'var(--bg)',
          border:'1px solid var(--line)',borderRadius:'var(--r-md)',
          color:'var(--text)',fontSize:14,fontFamily:'var(--font-ui)',outline:'none',
          colorScheme:'dark',
        }}
        onFocus={e=>e.target.style.borderColor='var(--indigo)'}
        onBlur={e=>e.target.style.borderColor='var(--line)'}/>
    </div>
  );
}

function GenerateReportModal({ onClose, onQueued }) {
  const brands = useApi(() => spBrands.list(), []);
  const cats = useApi(() => spStats.categories(), []);
  const [brandIds, setBrandIds] = useState([]);
  const [catIds, setCatIds] = useState([]);
  const [platform, setPlatform] = useState('');
  const [dateFrom, setDateFrom] = useState(() => _ymd(new Date(Date.now() - 7 * 86400000)));
  const [dateTo, setDateTo] = useState(() => _ymd(new Date()));
  const [estimate, setEstimate] = useState(null);
  const [starting, setStarting] = useState(false);
  const [err, setErr] = useState('');

  // When categories are selected, auto-include every brand in those categories.
  const effectiveBrandIds = useMemo(() => {
    if (!catIds.length) return brandIds;
    const set = new Set(brandIds);
    (brands.data || []).forEach(b => {
      if (catIds.includes(b.category_id)) set.add(b.id);
    });
    return Array.from(set);
  }, [brandIds, catIds, brands.data]);

  const buildBody = () => ({
    brand_ids: effectiveBrandIds.length ? effectiveBrandIds.join(',') : null,
    category_id: catIds.length === 1 ? catIds[0] : null,
    platform: platform || null,
    date_from: dateFrom || null, date_to: dateTo || null,
  });

  const doEstimate = async () => {
    setErr('');
    try { setEstimate(await spReports.estimate(buildBody())); }
    catch (e) { setErr(e.message); }
  };

  // Invalidate cached estimate when the selection criteria change.
  useEffect(() => { setEstimate(null); }, [effectiveBrandIds.join(','), platform, dateFrom, dateTo]);

  const doGenerate = async () => {
    setStarting(true); setErr('');
    const title = (effectiveBrandIds.length
      ? (brands.data||[]).filter(b=>effectiveBrandIds.includes(b.id)).map(b=>b.name).join('、')
      : catIds.length
        ? (cats.data||[]).filter(c=>catIds.includes(c.id)).map(c=>c.name).join('、')
        : '全品牌') + ' 報告';
    // Backend 429/403/400 (claim slot, no posts, no credits) reject within ~1s;
    // success keeps the promise pending for the full 60–180s generation. Race
    // against a short window so early failures surface inline, then defer to
    // the global banner for the long-running success case.
    const promise = spStartReport(buildBody(), title + '（生成中）');
    let resolved = false;
    promise.then(() => { resolved = true; }, () => { resolved = true; });
    try {
      await Promise.race([promise, new Promise(r => setTimeout(r, 2500))]);
      if (resolved) {
        // Promise settled fast — was an error (success would still be pending after 2.5s).
        try { await promise; } catch (e) { setErr(e.message || '生成失敗'); setStarting(false); return; }
      }
      onQueued();
    } catch (e) {
      setErr(e.message || '生成失敗');
      setStarting(false);
    }
  };

  return (
    <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,.6)',zIndex:200,display:'flex',alignItems:'center',justifyContent:'center'}} onClick={onClose}>
      <div style={{background:'var(--panel)',border:'1px solid var(--line)',borderRadius:'var(--r-2xl)',padding:28,width:'90%',maxWidth:480}} onClick={e=>e.stopPropagation()}>
        <h3 style={{fontSize:18,fontWeight:700,marginBottom:20}}>生成 AI 報告</h3>

        {(cats.data || []).length > 0 && (
          <div style={{marginBottom:14}}>
            <label style={{display:'block',fontSize:12,color:'var(--text2)',marginBottom:6,fontWeight:500}}>分類快選（可多選，會自動帶入該分類下所有品牌）</label>
            <div style={{display:'flex',flexWrap:'wrap',gap:6}}>
              {(cats.data||[]).map(c => (
                <Chip key={c.id} active={catIds.includes(c.id)} onClick={()=>setCatIds(ids=>ids.includes(c.id)?ids.filter(x=>x!==c.id):[...ids,c.id])}>
                  {c.icon} {c.name}
                </Chip>
              ))}
            </div>
          </div>
        )}

        <div style={{marginBottom:14}}>
          <label style={{display:'block',fontSize:12,color:'var(--text2)',marginBottom:6,fontWeight:500}}>選擇品牌（可多選）</label>
          <div style={{display:'flex',flexWrap:'wrap',gap:6,maxHeight:120,overflowY:'auto',padding:8,background:'var(--bg)',border:'1px solid var(--line)',borderRadius:'var(--r-md)'}}>
            {(brands.data||[]).map(b => {
              const included = effectiveBrandIds.includes(b.id);
              const fromCat = catIds.includes(b.category_id) && !brandIds.includes(b.id);
              return (
                <Chip key={b.id} active={included}
                  onClick={()=>setBrandIds(ids=>ids.includes(b.id)?ids.filter(x=>x!==b.id):[...ids,b.id])}>
                  {b.name}{fromCat && <span style={{marginLeft:4,fontSize:9,opacity:.6}}>(分類)</span>}
                </Chip>
              );
            })}
          </div>
        </div>

        <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:10,marginBottom:14}}>
          <DateField label="起始日期" value={dateFrom} onChange={setDateFrom} max={dateTo}/>
          <DateField label="結束日期" value={dateTo} onChange={setDateTo} min={dateFrom} max={_ymd(new Date())}/>
        </div>

        <div style={{marginBottom:14}}>
          <label style={{display:'block',fontSize:12,color:'var(--text2)',marginBottom:6,fontWeight:500}}>平台</label>
          <div style={{display:'flex',gap:6}}>
            {[['','全部'],['fb','僅 FB'],['ig','僅 IG']].map(([v,l]) =>
              <Chip key={v} active={platform===v} onClick={()=>setPlatform(v)}>{l}</Chip>)}
          </div>
        </div>

        {estimate && estimate.postCount > 0 && (
          <div style={{padding:'12px 14px',background:'var(--indigo-soft)',border:'1px solid rgba(99,102,241,.3)',borderRadius:'var(--r-md)',marginBottom:14,fontSize:13,lineHeight:1.6}}>
            <div>📊 {estimate.postCount} 篇貼文（分析 {estimate.analyzedCount} 篇）· 預估消耗 <strong style={{color:'var(--indigo2)'}}>{estimate.min}~{estimate.max} 點</strong></div>
            {estimate.dailyLimit != null && (
              <div style={{fontSize:11,color:'var(--text3)',marginTop:4}}>
                測試期間額度每天 {estimate.dailyLimit} 篇（今日已用 {estimate.dailyUsed || 0}）
              </div>
            )}
          </div>
        )}

        {estimate && estimate.postCount === 0 && (
          <div style={{padding:'12px 14px',background:'rgba(239,68,68,.1)',border:'1px solid rgba(239,68,68,.3)',borderRadius:'var(--r-md)',marginBottom:14,fontSize:13,lineHeight:1.6,color:'var(--red)'}}>
            ⚠️ 所選範圍內沒有任何貼文,請調整品牌、日期或平台後再試。
          </div>
        )}

        {err && <div style={{fontSize:12,color:'var(--red)',marginBottom:12}}>{err}</div>}

        <div style={{display:'flex',justifyContent:'flex-end',gap:8,marginTop:20}}>
          <button className="btn btn-ghost" onClick={onClose}>取消</button>
          {(!estimate || estimate.postCount === 0) ? (
            <button className="btn btn-ghost" onClick={doEstimate}>試算點數</button>
          ) : (
            <button className="btn btn-primary" onClick={doGenerate} disabled={starting}>
              {starting ? '啟動中…' : '生成 →'}
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

function ReportDetail({ reportId, demoReport, onBack, onDeleted }) {
  const isDemo = !!demoReport;
  const report = useApi(() => isDemo ? Promise.resolve(demoReport) : spReports.get(reportId), [reportId, isDemo]);
  const [deleting, setDeleting] = useState(false);
  const [confirmDel, setConfirmDel] = useState(false);

  // Mark read once the report content loads (ensures user has actually seen it
  // before we unlock deletion).
  useEffect(() => {
    if (!isDemo && report.data && reportId) spMarkReportRead(reportId);
  }, [isDemo, report.data, reportId]);

  if (report.loading) return <LoadingState/>;
  if (report.error) return <ErrorState error={report.error}/>;
  const r = report.data || {};

  const doDelete = async () => {
    setDeleting(true);
    try {
      if (isDemo) { onDeleted && onDeleted(); return; }
      await spReports.delete(reportId);
      onDeleted && onDeleted();
    } catch (e) { alert(e.message); setDeleting(false); }
  };

  return (
    <div>
      <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:16}}>
        <button onClick={onBack} className="btn btn-ghost btn-sm">← 回報告列表</button>
        <div style={{marginLeft:'auto'}}>
          {confirmDel ? (
            <>
              <span style={{fontSize:12,color:'var(--text2)',marginRight:10}}>確定刪除？</span>
              <button className="btn btn-ghost btn-sm" onClick={()=>setConfirmDel(false)} disabled={deleting}>取消</button>
              <button className="btn btn-sm" onClick={doDelete} disabled={deleting} style={{background:'var(--red-soft)',color:'var(--red)',border:'1px solid rgba(255,85,119,.3)',marginLeft:8}}>
                {deleting ? '刪除中…' : '確定刪除'}
              </button>
            </>
          ) : (
            <button className="btn btn-ghost btn-sm" onClick={()=>setConfirmDel(true)} style={{color:'var(--text3)'}}>
              刪除
            </button>
          )}
        </div>
      </div>

      <div style={{background:'var(--paper)',color:'#0a0a13',borderRadius:'var(--r-2xl)',overflow:'hidden',boxShadow:'var(--shadow-lg)',maxWidth:860,margin:'0 auto'}}>
        <div style={{padding:'18px 32px',borderBottom:'1px solid #0a0a13',display:'flex',alignItems:'center',justifyContent:'space-between'}}>
          <div style={{display:'flex',alignItems:'center',gap:10}}>
            <div style={{width:22,height:22,borderRadius:6,background:'linear-gradient(135deg,#6366f1,#a855f7)',display:'grid',placeItems:'center'}}>
              <svg width="12" height="12" viewBox="0 0 24 24" fill="#fff"><path d="M3.5 18.49l6-6.01 4 4L22 6.92l-1.41-1.41-7.09 7.97-4-4L2 16.99z"/></svg>
            </div>
            <span style={{fontWeight:700,fontSize:14}}>SocialPulse Report</span>
          </div>
          <span style={{fontFamily:'var(--font-mono)',fontSize:10,color:'#5b5b60',textTransform:'uppercase',letterSpacing:'.1em'}}>{r.created_at?.slice(0,10)}</span>
        </div>

        <div style={{padding:'36px 48px 24px'}}>
          <h1 style={{fontSize:32,fontWeight:800,letterSpacing:'-.025em',lineHeight:1.1,marginBottom:16}}>{r.title}</h1>
          {r.week_start && <div style={{fontFamily:'var(--font-mono)',fontSize:11,color:'#5b5b60'}}>{r.week_start} → {r.week_end}</div>}
        </div>

        <div style={{height:1,background:'#0a0a13',margin:'0 48px'}}/>

        <div style={{padding:'28px 48px 48px'}}>
          {r.summary && (
            <div style={{marginBottom:24}}>
              <div style={{fontFamily:'var(--font-mono)',fontSize:10,color:'#5b5b60',textTransform:'uppercase',letterSpacing:'.12em',marginBottom:8}}>摘要</div>
              <p style={{fontSize:15,lineHeight:1.7,margin:0}}>{_stripMd(r.summary)}</p>
            </div>
          )}
          {r.content && (
            <div style={{fontSize:14,color:'#0a0a13'}}>
              <Markdown text={r.content}/>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { ReportsPage, Markdown });
