// News Intake — the busiest screen.
// Table of all news items with status, signals, priority, action. Filterable, sortable.
// Manual review actions for "Maybe relevant" rows.

const { SIGNALS: _SIG, PRIORITY_META: _PM, STATUS_META: _SM } = window.IPSEM_DATA;

// "Needs review" and "Reviewed" lead, because they are the two questions asked
// in the weekly meeting. They read brands.reviewed_at (see needsReview /
// isReviewed in ui.jsx), so the count here always equals the Dashboard tile —
// the old "awaiting triage" number matched no filter on this page at all.
const STATUS_TABS = [
  // This page answers ONE question: is this brand relevant to us? So its review
  // tab is gate 1 only — brands nothing has been researched for yet. Brands that
  // are researched and awaiting a property belong to Brand Research, not here.
  { id: 'needs-review', label: 'To review', filter: (b) => needsRelevanceCall(b) },
  { id: 'all', label: 'All' },
  { id: 'relevant', label: 'Relevant', filter: (b) => b.status === 'relevant' },
  { id: 'maybe', label: 'Maybe relevant', filter: (b) => b.status === 'maybe' },
  // researchStatus carries these states too (see pbIsHeldOrClosed), so a brand
  // KIV'd that way showed the "KIV — watching" chip on its row while being absent
  // from this tab and its count.
  { id: 'kiv', label: 'Keep in view', filter: (b) => b.priority === 'kiv' || b.researchStatus === 'kiv' },
  { id: 'rejected', label: 'Not relevant / Rejected', filter: (b) => b.status === 'not-relevant' || b.researchStatus === 'rejected' },
  { id: 'duplicate', label: 'Duplicate', filter: (b) => b.status === 'duplicate' },
  // The exact set the Dashboard funnel's "Held / closed" row counts. It used to
  // link to the Pipeline board, so clicking a number showed you nothing; the
  // tab shares pbIsHeldOrClosed with the funnel, so the count and the list can
  // never drift apart.
  { id: 'held', label: 'Held / closed', filter: (b) => pbIsHeldOrClosed(b) },
  { id: 'industry', label: 'Industry news' },
];

const PRIORITY_FILTERS = [
  { id: 'all', label: 'All priorities' },
  { id: 'tier-1', label: 'Tier 1' },
  { id: 'tier-2', label: 'Tier 2' },
  { id: 'tier-3', label: 'Tier 3' },
  { id: 'kiv', label: 'KIV' },
  { id: 'no-action', label: 'No action' },
];

const SIGNAL_FILTERS = Object.keys(_SIG).map((k) => ({ id: k, label: _SIG[k].label }));

function NewsIntake({ brands, onNavigate, onOpenBrand, onUpdateBrand, onAddBrand, onResearchUrl, onMoveToResearch, onToast, onReload, onDeleteBrand, onResearchBrand, onScanStart, currentUser }) {
  // Scan logic now lives in ui.jsx (useScan / ScanButton) so the same control,
  // the same quota guard and the same warning modal are available from the
  // topbar on every page rather than only here.
  const [confirm, confirmEl] = useConfirm();
  // Dashboard's "See all" deep-links straight to the Industry news tab.
  const [tab, setTab] = React.useState(() => {
    try {
      const t = sessionStorage.getItem('ipsem-intake-tab');
      if (t) { sessionStorage.removeItem('ipsem-intake-tab'); return t; }
    } catch (_) {}
    // Open on the review list, not the whole 593-row book — landing on "All"
    // was the reason it was never obvious what still needed looking at.
    return 'needs-review';
  });
  const [priorityFilter, setPriorityFilter] = React.useState('all');
  const [signalFilter, setSignalFilter] = React.useState('all');
  const [outreachFilter, setOutreachFilter] = React.useState('all');
  const [query, setQuery] = React.useState('');
  const [sortBy, setSortBy] = React.useState('date'); // default: most recent first
  const [showFilters, setShowFilters] = React.useState(false);
  const [sel, setSel] = React.useState(() => new Set()); // bulk-selected brand ids
  // Brands decided during THIS pass down the list. They no longer match the tab
  // filter, but pulling them out from under the cursor is what made the list
  // feel like it was rearranging itself — every remaining row jumped up a place
  // on each decision. They stay in position, dimmed, until the tab, the filters
  // or the sort change (or you clear them).
  const [held, setHeld] = React.useState(() => new Set());
  const [showScoringHelp, setShowScoringHelp] = React.useState(false);
  const [showAddBrand, setShowAddBrand] = React.useState(false);

  // Counts per tab
  const tabCounts = React.useMemo(() => {
    const c = { all: brands.length };
    STATUS_TABS.forEach((t) => {
      if (t.filter) c[t.id] = brands.filter(t.filter).length;
    });
    return c;
  }, [brands]);

  // Decisions made here keep the row on screen until the pass ends.
  const holdInPlace = React.useCallback((id) => {
    setHeld((s) => (s.has(id) ? s : new Set(s).add(id)));
  }, []);
  const updateBrandHeld = React.useCallback((id, patch) => {
    holdInPlace(id);
    return onUpdateBrand(id, patch);
  }, [onUpdateBrand, holdInPlace]);

  // Apply filters
  const filtered = React.useMemo(() => {
    const t = STATUS_TABS.find((x) => x.id === tab);
    let rows = brands;
    if (t && t.filter) rows = rows.filter((b) => t.filter(b) || held.has(b.id));
    if (priorityFilter !== 'all') rows = rows.filter((b) => b.priority === priorityFilter);
    if (signalFilter !== 'all') rows = rows.filter((b) => (b.signals || []).includes(signalFilter));
    if (outreachFilter !== 'all') rows = rows.filter((b) => (b.outreachStatus || 'not_contacted') === outreachFilter);
    if (query.trim()) {
      const q = query.toLowerCase();
      rows = rows.filter(
        (b) =>
          (b.brand || '').toLowerCase().includes(q) ||
          (b.category || '').toLowerCase().includes(q) ||
          (b.news?.title || '').toLowerCase().includes(q)
      );
    }
    // Every sort ends on the brand id. Hundreds of rows share a review date (and
    // plenty share a score), and without a final tie-break their order came from
    // whatever order the server happened to return — so the list reshuffled
    // under you mid-review, every time data refreshed. The id is arbitrary but
    // it is the SAME arbitrary order on every render.
    const byId = (a, b) => String(a.id || '').localeCompare(String(b.id || ''));
    if (sortBy === 'score') {
      rows = [...rows].sort((a, b) => ((b.relevanceScore || 0) - (a.relevanceScore || 0)) || byId(a, b));
    } else if (sortBy === 'date') {
      rows = [...rows].sort((a, b) =>
        (b.reviewDate || '').localeCompare(a.reviewDate || '')
        || (b.createdAt || '').localeCompare(a.createdAt || '')   // real timestamp, as on Brand Research
        || byId(a, b));
    } else if (sortBy === 'priority') {
      const order = { 'tier-1': 0, 'tier-2': 1, 'tier-3': 2, 'kiv': 3, 'no-action': 4 };
      rows = [...rows].sort((a, b) => ((order[a.priority] ?? 9) - (order[b.priority] ?? 9)) || byId(a, b));
    }
    return rows;
  }, [brands, tab, priorityFilter, signalFilter, outreachFilter, query, sortBy, held]);

  // Pagination: render at most PAGE_SIZE rows at a time so a large book
  // (hundreds of brands) doesn't bog down scrolling. Resets when filters change.
  const PAGE_SIZE = 100;
  const [visibleCount, setVisibleCount] = React.useState(PAGE_SIZE);
  React.useEffect(() => {
    setVisibleCount(PAGE_SIZE);
    setHeld(new Set());   // a new view = a new pass
  }, [tab, priorityFilter, signalFilter, outreachFilter, query, sortBy]);
  const visible = filtered.slice(0, visibleCount);

  // IPSEM's operating day (GMT+8) — matches the scanner's review_date.
  const todayStr = ipsemToday();
  const newToday = brands.filter((b) => b.reviewDate === todayStr).length;

  // Gate 1 only: brands whose relevance nobody has called yet. Researched brands
  // awaiting a property are Brand Research's job, so they are not counted here.
  const reviewList = React.useMemo(() => brands.filter(needsRelevanceCall), [brands]);
  const propertyQueue = React.useMemo(() => brands.filter(needsPropertyDecision).length, [brands]);

  let intakeSub = newToday > 0
    ? `${newToday} new brand${newToday === 1 ? '' : 's'} in the latest scan.`
    : `No new brands in the latest scan.`;
  intakeSub += reviewList.length > 0
    ? ` ${reviewList.length} ${reviewList.length === 1 ? 'brand needs' : 'brands need'} a decision: Research it, Keep in view, or Reject.`
    : ` Nothing here is waiting on a decision.`;
  if (reviewList.length === 0 && propertyQueue > 0) {
    intakeSub += ` The ${propertyQueue} brands waiting on a property decision are on Brand Research.`;
  }

  // ─── Selection + bulk actions + export ────────────────────────────────
  const toggleSel = (id) => setSel((s) => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; });
  const clearSel = () => setSel(new Set());
  // Built from `visible`, NOT `filtered`. Only the first PAGE_SIZE rows are on
  // screen, so selecting `filtered` meant one click on the All tab selected all
  // 593 brands while the user could see 100 — and the bulk bar then offered to
  // permanently Delete them under a checkbox labelled "Select all shown". It also
  // left the header checkbox unticked after selecting everything visible, because
  // the 493 off-screen rows were never in the set.
  const allVisibleSelected = visible.length > 0 && visible.every((b) => sel.has(b.id));
  const toggleSelAll = () => setSel(allVisibleSelected ? new Set() : new Set(visible.map((b) => b.id)));

  const applyBulk = (fn, label) => {
    const ids = [...sel];
    ids.forEach(fn);
    setHeld((s) => { const n = new Set(s); ids.forEach((id) => n.add(id)); return n; });
    clearSel();
    onToast(`${ids.length} brand${ids.length === 1 ? '' : 's'} ${label}`);
  };
  const bulkResearch = () => applyBulk((id) => {
    onUpdateBrand(id, { status: 'relevant', researchStatus: 'researching', needsManualReview: false, userOverride: true });
    onResearchBrand && onResearchBrand(id);
  }, 'sent to research');
  const bulkKiv = () => applyBulk((id) => onUpdateBrand(id, { status: 'maybe', priority: 'kiv', researchStatus: 'kiv', needsManualReview: false, userOverride: true }), 'kept in view');
  const bulkReject = () => applyBulk((id) => onUpdateBrand(id, { status: 'not-relevant', priority: 'no-action', researchStatus: 'rejected', needsManualReview: false, userOverride: true }), 'rejected');
  const bulkDuplicate = () => applyBulk((id) => onUpdateBrand(id, { status: 'duplicate', needsManualReview: false, userOverride: true }), 'marked as duplicate');
  const bulkAssign = (name) => { if (name) applyBulk((id) => onUpdateBrand(id, { assignee: name }), `assigned to ${name}`); };
  // Sign a batch off in one go — the meeting works down a list, it does not open
  // 40 brands one at a time.
  const bulkMarkReviewed = () => {
    const by = displayNameFor(currentUser);
    applyBulk((id) => onUpdateBrand(id, reviewPatch(true, by)), 'marked reviewed');
  };
  const bulkDelete = async () => {
    const ok = await confirm({
      title: `Delete ${sel.size} brand${sel.size === 1 ? '' : 's'}?`,
      message: 'This permanently removes them from the database and cannot be undone.',
      confirmLabel: 'Delete',
      danger: true,
    });
    if (ok) applyBulk((id) => onDeleteBrand && onDeleteBrand(id), 'deleted');
  };

  const exportCsv = () => {
    const rows = filtered.map((b) => ({
      brand: b.brand, category: b.category || '', priority: b.priority || '', status: b.status || '',
      score: b.relevanceScore ?? '', assignee: displayAssignee(b.assignee) || '',
      signals: (b.signals || []).join('; '), outreach: b.outreachStatus || '', relationship: b.contactStatus || '',
      hq: b.hq || '', confirmed_properties: (b.confirmedProperties || []).join('; '),
      news_title: b.news?.title || '', news_url: b.news?.url || '', review_date: b.reviewDate || '',
    }));
    if (!rows.length) { onToast('Nothing to export'); return; }
    downloadCsv(`ipsem-brands-${todayStr}.csv`, rows);
    onToast(`Exported ${rows.length} brand${rows.length === 1 ? '' : 's'}`);
  };

  return (
    <div className="page page-cap" style={{ paddingLeft: 18, paddingRight: 18, maxWidth: 1780 }}>
      <SectionHeader
        eyebrow="Step 01 · Today's intake"
        title="News intake"
        sub={intakeSub}
        right={
          <>
            <div className="field" style={{ width: 280 }}>
              <window.I.search size={14} stroke="var(--ink-3)" />
              <input
                placeholder="Search brand, category, headline…"
                value={query}
                onChange={(e) => setQuery(e.target.value)}
              />
            </div>
            <button
              className={`btn ${showFilters ? '' : 'btn-ghost'}`}
              onClick={() => setShowFilters((x) => !x)}
            >
              <window.I.filter size={15} />
              Filters
            </button>
            <button className="btn" onClick={() => setShowAddBrand(true)}>
              <window.I.plus size={14} />
              Add brand
            </button>
            <button className="btn btn-ghost" onClick={exportCsv} title="Export the filtered list to CSV">
              <window.I.download size={14} />
              Export
            </button>
            <ScanButton onToast={onToast} onScanStart={onScanStart} />
          </>
        }
      />

      {/* Sort + scoring help row */}
      {tab !== 'industry' ? (
      <div className="row gap-3" style={{ marginBottom: 14, justifyContent: 'space-between' }}>
        <div className="row gap-2">
          <span className="eyebrow">Sort</span>
          <div className="toggle-group">
            <button className={sortBy === 'date' ? 'is-active' : ''} onClick={() => setSortBy('date')}>Date</button>
            <button className={sortBy === 'score' ? 'is-active' : ''} onClick={() => setSortBy('score')}>Score</button>
            <button className={sortBy === 'priority' ? 'is-active' : ''} onClick={() => setSortBy('priority')}>Priority</button>
          </div>
        </div>
        <button className="btn btn-sm btn-ghost" onClick={() => setShowScoringHelp(true)}>
          <window.I.info size={13} />
          How scoring works
        </button>
      </div>
      ) : null}

      {/* Tabs */}
      <div className="tabs">
        {STATUS_TABS.map((t) => (
          <span
            key={t.id}
            className={`tab ${tab === t.id ? 'is-active' : ''}`}
            onClick={() => setTab(t.id)}
          >
            {t.label}
            {tabCounts[t.id] != null ? <span className="count">{tabCounts[t.id]}</span> : null}
          </span>
        ))}
      </div>

      {/* Industry news tab replaces the brand table entirely */}
      {tab === 'industry' ? <IndustryFeed onToast={onToast} /> : null}

      {/* Filter row */}
      {showFilters && tab !== 'industry' ? (
        <div className="row gap-3" style={{ marginBottom: 18, padding: '12px 16px', background: 'var(--card)', border: '1px solid var(--hairline)', borderRadius: 10 }}>
          <span className="eyebrow">Priority</span>
          <div className="toggle-group">
            {PRIORITY_FILTERS.map((p) => (
              <button
                key={p.id}
                className={priorityFilter === p.id ? 'is-active' : ''}
                onClick={() => setPriorityFilter(p.id)}
              >
                {p.label}
              </button>
            ))}
          </div>

          <span className="eyebrow" style={{ marginLeft: 12 }}>Signal</span>
          <select
            value={signalFilter}
            onChange={(e) => setSignalFilter(e.target.value)}
            className="field"
            style={{ height: 30, paddingTop: 0, paddingBottom: 0 }}
          >
            <option value="all">Any signal</option>
            {SIGNAL_FILTERS.map((s) => (
              <option key={s.id} value={s.id}>{s.label}</option>
            ))}
          </select>

          <span className="eyebrow" style={{ marginLeft: 12 }}>Outreach</span>
          <select
            value={outreachFilter}
            onChange={(e) => setOutreachFilter(e.target.value)}
            className="field"
            style={{ height: 30, paddingTop: 0, paddingBottom: 0 }}
          >
            <option value="all">Any stage</option>
            {OUTREACH_STAGES.map((s) => (
              <option key={s.value} value={s.value}>{s.label}</option>
            ))}
          </select>
        </div>
      ) : null}

      {/* Bulk action bar */}
      {sel.size > 0 && tab !== 'industry' ? (
        <div className="row gap-2" style={{ marginBottom: 12, padding: '10px 14px', background: 'var(--accent-soft-bg)', border: '1px solid var(--accent)', borderRadius: 10, alignItems: 'center', flexWrap: 'wrap' }}>
          <span className="text-sm" style={{ fontWeight: 600 }}>{sel.size} selected</span>
          <span className="grow" />
          <select
            defaultValue=""
            onChange={(e) => { bulkAssign(e.target.value); e.target.value = ''; }}
            className="field"
            style={{ height: 30, padding: '0 8px', fontSize: 12.5 }}
            title="Assign selected to"
          >
            <option value="">Assign to…</option>
            {ASSIGNEE_NAMES.map((a) => <option key={a} value={a}>{a}</option>)}
          </select>
          <button className="btn btn-sm btn-primary" onClick={bulkMarkReviewed} title="Sign these off as reviewed — they leave the Needs review list"><window.I.check size={13} /> Mark reviewed</button>
          <button className="btn btn-sm" onClick={bulkResearch}><window.I.sparkle size={13} /> Research</button>
          <button className="btn btn-sm" onClick={bulkKiv}><window.I.eye size={13} /> KIV</button>
          <button className="btn btn-sm btn-ghost" onClick={bulkReject} style={{ color: 'var(--negative)' }}><window.I.x size={13} /> Reject</button>
          <button className="btn btn-sm btn-ghost" onClick={bulkDuplicate} title="Mark selected as duplicates"><window.I.copy size={13} /> Duplicate</button>
          <button className="btn btn-sm btn-ghost" onClick={bulkDelete} style={{ color: 'var(--negative)' }}><window.I.trash size={13} /> Delete</button>
          <button className="btn btn-sm btn-ghost" onClick={clearSel}>Clear</button>
        </div>
      ) : null}

      {/* Decided-in-this-pass notice */}
      {held.size > 0 && tab !== 'industry' ? (
        <div className="row gap-2 text-sm" style={{ marginBottom: 12, padding: '8px 14px', background: 'var(--card-alt)', border: '1px solid var(--hairline)', borderRadius: 10, alignItems: 'center' }}>
          <window.I.check size={13} stroke="var(--positive)" />
          <span>
            {held.size} decided in this pass — kept in place so the list doesn't move under you.
          </span>
          <span className="grow" />
          <button className="btn btn-sm btn-ghost" onClick={() => setHeld(new Set())}>Clear decided</button>
        </div>
      ) : null}

      {/* Table */}
      {tab !== 'industry' ? (
      <>
      <Card pad={false} style={{ overflow: 'hidden' }}>
        <div style={{ overflowX: 'auto' }}>
          <table className="tbl" style={{ width: '100%' }}>
            <thead>
              <tr>
                <th className="mob-hide" style={{ width: 34, paddingLeft: 14 }}>
                  <input type="checkbox" checked={allVisibleSelected} onChange={toggleSelAll} title="Select all shown" />
                </th>
                <th style={{ width: 210 }}>Brand</th>
                <th>News headline</th>
                <th style={{ width: 132 }}>Signals</th>
                <th style={{ width: 74 }}>Score</th>
                <th style={{ width: 104 }}>Status</th>
                <th className="mob-hide" style={{ width: 156 }}>Relationship / Outreach</th>
                <th className="mob-hide" style={{ width: 112 }}>Assignee</th>
                <th style={{ width: 158 }}>Action</th>
                <th style={{ width: 78 }}></th>
              </tr>
            </thead>
            <tbody>
              {visible.map((b) => (
                <NewsRow key={b.id} brand={b} todayStr={todayStr} onOpen={() => onOpenBrand(b.id)} onNavigate={onNavigate} onUpdate={updateBrandHeld} onMoveToResearch={onMoveToResearch} onToast={onToast} onDelete={onDeleteBrand} onResearch={onResearchBrand} selected={sel.has(b.id)} onToggleSelect={() => toggleSel(b.id)} confirm={confirm} currentUser={currentUser} decided={held.has(b.id)} />
              ))}
              {filtered.length === 0 ? (
                <tr>
                  <td colSpan={10} className="empty">
                    {/* Name the one action that fixes it, rather than just
                        reporting that the list is empty. */}
                    {(() => {
                      const narrowed = query.trim() || priorityFilter !== 'all' ||
                        signalFilter !== 'all' || outreachFilter !== 'all';
                      // The state the weekly meeting wants to land on: this
                      // section is done, here is where the work actually is.
                      if (tab === 'needs-review' && !narrowed) {
                        return (
                          <>
                            <window.I.check size={26} stroke="var(--positive)" />
                            <div style={{ marginTop: 10, fontWeight: 600, color: 'var(--ink)', fontSize: 15 }}>
                              You've reviewed this section.
                            </div>
                            <div className="text-sm" style={{ marginTop: 6, maxWidth: 460, marginInline: 'auto', lineHeight: 1.6 }}>
                              Every brand here has been researched, kept in view or rejected. Nothing on this page is waiting on a decision.
                            </div>
                            <div className="row gap-2" style={{ marginTop: 14, justifyContent: 'center', flexWrap: 'wrap' }}>
                              {propertyQueue > 0 ? (
                                <button className="btn btn-sm btn-primary" onClick={() => onNavigate && onNavigate('research')}>
                                  Next: {propertyQueue} brand{propertyQueue === 1 ? '' : 's'} need a property
                                  <window.I.arrowRight size={12} />
                                </button>
                              ) : null}
                              <ScanButton onToast={onToast} onScanStart={onScanStart} className="btn btn-sm" label="Scan for new brands" />
                            </div>
                          </>
                        );
                      }
                      if (narrowed) {
                        return (
                          <>
                            <div style={{ fontWeight: 600, color: 'var(--ink)' }}>Nothing matches your search or filters.</div>
                            <button
                              className="btn btn-sm"
                              style={{ marginTop: 10 }}
                              onClick={() => { setQuery(''); setPriorityFilter('all'); setSignalFilter('all'); setOutreachFilter('all'); }}
                            >
                              Clear search and filters
                            </button>
                          </>
                        );
                      }
                      if (brands.length === 0) {
                        return (
                          <>
                            <div style={{ fontWeight: 600, color: 'var(--ink)' }}>No brands yet.</div>
                            <div className="text-sm" style={{ marginTop: 6 }}>
                              Run a scan to pull brands in from the news, or add one by hand.
                            </div>
                            <div className="row gap-2" style={{ marginTop: 12, justifyContent: 'center' }}>
                              <ScanButton onToast={onToast} onScanStart={onScanStart} className="btn btn-sm btn-primary" />
                              <button className="btn btn-sm" onClick={() => setShowAddBrand(true)}>Add brand</button>
                            </div>
                          </>
                        );
                      }
                      return (
                        <>
                          <div style={{ fontWeight: 600, color: 'var(--ink)' }}>Nothing on this tab.</div>
                          <div className="text-sm" style={{ marginTop: 6 }}>
                            Every brand here has already been dealt with. Switch to <strong>All</strong> to see the full book, or run a scan for new ones.
                          </div>
                          <div className="row gap-2" style={{ marginTop: 12, justifyContent: 'center' }}>
                            <button className="btn btn-sm" onClick={() => setTab('all')}>Show all brands</button>
                            <ScanButton onToast={onToast} onScanStart={onScanStart} className="btn btn-sm btn-primary" />
                          </div>
                        </>
                      );
                    })()}
                  </td>
                </tr>
              ) : null}
              {filtered.length > visibleCount ? (
                <tr>
                  <td colSpan={10} style={{ textAlign: 'center', padding: '14px 0' }}>
                    <button className="btn btn-sm" onClick={() => setVisibleCount((c) => c + PAGE_SIZE)}>
                      Show {Math.min(PAGE_SIZE, filtered.length - visibleCount)} more ({filtered.length - visibleCount} remaining)
                    </button>
                  </td>
                </tr>
              ) : null}
            </tbody>
          </table>
        </div>
      </Card>

      <div className="row gap-3 text-xs text-3" style={{ marginTop: 14 }}>
        <span>{filtered.length} of {brands.length} items</span>
        <span>·</span>
        <span className="row gap-1">
          <window.I.info size={12} />
          Rows with dimmed text were stopped by the system — flow does not continue past intake.
        </span>
      </div>
      </>
      ) : null}

      {showScoringHelp ? <ScoringHelpDrawer onClose={() => setShowScoringHelp(false)} /> : null}
      {showAddBrand ? <AddBrandDrawer brands={brands} onClose={() => setShowAddBrand(false)} onAdd={(payload) => { onAddBrand(payload); setShowAddBrand(false); }} onResearchUrl={onResearchUrl} /> : null}
      {confirmEl}
    </div>
  );
}

const TIER_LABEL = { 'tier-1': 'Tier 1', 'tier-2': 'Tier 2', 'tier-3': 'Tier 3' };

// displayAssignee + ASSIGNEE_NAMES come from ui.jsx (shared).
// Tier-1 → Rene, otherwise Aaron. Stuart is manual-only (when he has a contact).
function autoAssigneeFor(tier) {
  return tier === 'tier-1' ? 'Rene' : 'Aaron';
}

// CONTACT_STATUSES now lives in ui.jsx so the shared BrandControlBar can offer
// the same relationship options on every stage. See window.CONTACT_STATUSES.

function NewsRow({ brand, todayStr, onOpen, onNavigate, onUpdate, onMoveToResearch, onToast, onDelete, onResearch, selected, onToggleSelect, confirm, currentUser, decided }) {
  const stopped = brand.priority === 'no-action' || brand.priority === 'kiv';
  const isMaybe = brand.status === 'maybe';
  const isRejected = brand.status === 'not-relevant' || brand.status === 'duplicate';
  // NOTE: `overridden` is the old "a human touched the AI's call" flag. It is
  // NOT the review sign-off — that is brand.reviewedAt (see isReviewed).
  const overridden = brand.userOverride === true;
  const isNewToday = todayStr && brand.reviewDate === todayStr;
  // "Researching now" = marked as researching but hasn't been written by the
  // research engine yet (no description). Lets us show a spinner instead of
  // the misleading "Auto-researched" label while the background job runs.
  const isResearchingNow = brand.researchStatus === 'researching' && !(brand.description || '').trim();

  const [tier, setTier] = React.useState(
    TIER_LABEL[brand.priority] ? brand.priority : 'tier-2'
  );

  const handleAction = (action) => {
    if (action === 'research') {
      const newAssignee = autoAssigneeFor(tier);
      onUpdate(brand.id, { status: 'relevant', priority: tier, researchStatus: 'researching', needsManualReview: false, userOverride: true, assignee: newAssignee });
      onToast(`${brand.brand} → researching (${TIER_LABEL[tier]}, ${newAssignee})`);
      // Kick off deep research in the background; the row will update when it returns.
      if (onResearch) onResearch(brand.id);
    } else if (action === 'kiv') {
      onUpdate(brand.id, { status: 'maybe', priority: 'kiv', researchStatus: 'kiv', needsManualReview: false, userOverride: true });
      onToast(`${brand.brand} → kept in view`);
    } else if (action === 'reject') {
      onUpdate(brand.id, { status: 'not-relevant', priority: 'no-action', researchStatus: 'rejected', needsManualReview: false, userOverride: true });
      onToast(`${brand.brand} → rejected`);
    } else if (action === 'duplicate') {
      onUpdate(brand.id, { status: 'duplicate', needsManualReview: false, userOverride: true });
      onToast(`${brand.brand} → marked as duplicate`);
    }
  };

  const handleDelete = async () => {
    const ok = await confirm({
      title: `Delete ${brand.brand}?`,
      message: 'This permanently removes the brand from the database and cannot be undone.',
      confirmLabel: 'Delete',
      danger: true,
    });
    if (ok && onDelete) onDelete(brand.id);
  };

  const handleOutreachChange = async (next) => {
    if (await guardOutreachChange(brand, next, confirm)) {
      onUpdate(brand.id, { outreachStatus: next });
    }
  };

  return (
    <tr
      className={`row-clickable ${stopped ? 'row-stopped' : ''}`}
      onClick={onOpen}
      style={{
        ...(selected ? { background: 'var(--accent-soft-bg)' } : null),
        // Decided in this pass: kept in place so the rows below don't jump, but
        // dimmed so it reads as done rather than still waiting on you.
        ...(decided ? { opacity: 0.55 } : null),
      }}
    >
      <td className="mob-hide" style={{ paddingLeft: 14 }} onClick={(e) => e.stopPropagation()}>
        <input type="checkbox" checked={!!selected} onChange={onToggleSelect} />
      </td>
      <td data-label="Brand">
        <div className="row gap-2" style={{ alignItems: 'center', minWidth: 0 }}>
          <BrandLogo brand={brand} size={28} />
          <div className="col" style={{ gap: 2, minWidth: 0 }}>
            <div className="row gap-2" style={{ alignItems: 'center' }}>
              <span style={{ fontWeight: 600 }} className="truncate">{brand.brand}</span>
              {isNewToday ? (
                <span
                  title="Found in today's scan"
                  style={{
                    background: 'var(--accent)',
                    color: 'white',
                    fontSize: 9.5,
                    fontWeight: 700,
                    letterSpacing: '0.06em',
                    padding: '2px 6px',
                    borderRadius: 4,
                    fontFamily: 'var(--f-mono)',
                    flexShrink: 0,
                  }}
                >
                  NEW
                </span>
              ) : null}
            </div>
            <span className="text-xs text-3 truncate">{brand.category}</span>
          </div>
        </div>
      </td>
      <td data-label="Headline">
        <div className="col" style={{ gap: 4 }}>
          <span className="text-sm" style={{ lineHeight: 1.4, maxWidth: 500 }}>{brand.news?.title}</span>
          <div className="row gap-2 text-xs text-3">
            <span>{brand.news?.source || '—'}</span>
            <span>·</span>
            <span>{formatDate(brand.news?.date)}</span>
            {brand.news?.url ? (
              <a
                href={brand.news.url}
                target="_blank"
                rel="noopener noreferrer"
                className="row gap-1"
                onClick={(e) => e.stopPropagation()}
                style={{ color: 'var(--accent)' }}
              >
                <window.I.external size={11} />
                Source
              </a>
            ) : null}
          </div>
        </div>
      </td>
      <td data-label="Signals">
        <div className="row gap-1" style={{ flexWrap: 'wrap', maxWidth: 132 }}>
          {(brand.signals || []).slice(0, 3).map((s) => (
            <SignalChip key={s} signal={s} />
          ))}
          {(brand.signals || []).length > 3 ? (
            <span className="text-xs text-3 mono">+{brand.signals.length - 3}</span>
          ) : null}
        </div>
      </td>
      <td data-label="Score"><ScoreWithBreakdown brand={brand} mute={stopped} /></td>
      <td data-label="Status">
        <div className="col gap-1" style={{ alignItems: 'flex-start' }}>
          <StatusPill status={brand.status} />
          <PriorityPill priority={brand.priority} />
          {/* Two separate questions: has a person signed this off, and what is
              the stage waiting on. Both belong on the row. */}
          <BrandStatusChip brand={brand} onNavigate={onNavigate} />
        </div>
      </td>
      <td className="mob-hide" onClick={(e) => e.stopPropagation()}>
        <div className="col gap-1">
          <select
            value={brand.contactStatus || 'cold'}
            onChange={(e) => onUpdate(brand.id, { contactStatus: e.target.value })}
            className="field"
            style={{ height: 24, padding: '0 6px', fontSize: 11.5, width: '100%' }}
            title="Relationship with this brand"
          >
            {CONTACT_STATUSES.map((c) => (
              <option key={c.value} value={c.value}>{c.label}</option>
            ))}
          </select>
          <select
            value={brand.outreachStatus || 'not_contacted'}
            onChange={(e) => handleOutreachChange(e.target.value)}
            className="field"
            style={{ height: 24, padding: '0 6px', fontSize: 11.5, width: '100%', color: outreachMeta(brand.outreachStatus).color }}
            title="Outreach pipeline stage"
          >
            {OUTREACH_STAGES.map((s) => (
              <option key={s.value} value={s.value}>{s.label}</option>
            ))}
          </select>
        </div>
      </td>
      <td className="mob-hide" onClick={(e) => e.stopPropagation()}>
        <select
          value={displayAssignee(brand.assignee) || ''}
          onChange={(e) => onUpdate(brand.id, { assignee: e.target.value || null })}
          className="field"
          style={{ height: 26, padding: '0 6px', fontSize: 12, width: '100%' }}
          title="Who owns this brand"
        >
          <option value="">Unassigned</option>
          {ASSIGNEE_NAMES.map((a) => (
            <option key={a} value={a}>{a}</option>
          ))}
        </select>
      </td>
      <td data-label="Action" onClick={(e) => e.stopPropagation()}>
        {isResearchingNow ? (
          <span className="text-xs text-2 row gap-1">
            <window.I.refresh size={12} className="spin" stroke="var(--accent)" />
            Researching…
          </span>
        ) : overridden ? (
          <div className="row gap-2" style={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 4 }}>
            <span className="text-xs text-2 row gap-1" title="A person overrode the AI's call on this brand">
              <window.I.check size={12} stroke="var(--positive)" />
              Decided manually
            </span>
            <button
              className="btn btn-sm btn-ghost"
              onClick={() => handleAction('reject')}
              title="Not actually relevant — move to Rejected"
              style={{ color: 'var(--negative)' }}
            >
              <window.I.x size={12} />
              Reject
            </button>
            <button
              className="btn btn-sm btn-ghost"
              onClick={() => handleAction('duplicate')}
              title="Mark as duplicate"
            >
              <window.I.copy size={12} />
              Duplicate
            </button>
          </div>
        ) : isMaybe ? (
          <div className="col gap-1" style={{ alignItems: 'flex-start' }}>
            <select
              value={tier}
              onChange={(e) => setTier(e.target.value)}
              className="field"
              style={{ height: 26, padding: '0 6px', fontSize: 12 }}
              title="Tier to assign when you research this brand"
            >
              <option value="tier-1">Tier 1</option>
              <option value="tier-2">Tier 2</option>
              <option value="tier-3">Tier 3</option>
            </select>
            <div className="row gap-1" style={{ flexWrap: 'wrap', rowGap: 4 }}>
              <button className="btn btn-sm btn-primary" onClick={() => handleAction('research')}>
                Research
              </button>
              <button className="btn btn-sm" onClick={() => handleAction('kiv')} title="Keep in view">
                <window.I.eye size={13} />
              </button>
              <button className="btn btn-sm btn-ghost" onClick={() => handleAction('reject')} title="Reject">
                <window.I.x size={13} />
              </button>
              <button className="btn btn-sm btn-ghost" onClick={() => handleAction('duplicate')} title="Mark as duplicate">
                <window.I.copy size={13} />
              </button>
            </div>
          </div>
        ) : isRejected ? (
          <div className="row gap-2" style={{ flexWrap: 'wrap', rowGap: 4 }}>
            <span className="text-xs text-3 row gap-1">
              <window.I.ban size={12} />
              Stopped
            </span>
            <button
              className="btn btn-sm btn-ghost"
              onClick={async () => {
                const ok = await confirm({
                  title: `Research ${brand.brand} anyway?`,
                  message: 'This brand was rejected. Overriding moves it back into the research flow.',
                  confirmLabel: 'Override',
                });
                if (ok) { onMoveToResearch(brand.id); onToast(`${brand.brand} → moved back to research`); }
              }}
              title="I disagree — research this anyway"
              style={{ color: 'var(--accent)' }}
            >
              <window.I.refresh size={12} />
              Override
            </button>
          </div>
        ) : brand.priority === 'kiv' ? (
          <div className="row gap-2" style={{ flexWrap: 'wrap', rowGap: 4 }}>
            <span className="text-xs text-3 row gap-1">
              <window.I.eye size={12} />
              Monitoring
            </span>
            <button
              className="btn btn-sm btn-ghost"
              onClick={() => { onMoveToResearch(brand.id); onToast(`${brand.brand} → activated`); }}
              title="Activate now"
            >
              <window.I.bolt size={12} />
            </button>
          </div>
        ) : (
          <div className="row gap-2" style={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 4 }}>
            <span className="text-xs text-2 row gap-1">
              <window.I.check size={12} stroke="var(--positive)" />
              Auto-researched
            </span>
            <button
              className="btn btn-sm btn-ghost"
              onClick={() => handleAction('reject')}
              title="Not actually relevant — move to Rejected"
              style={{ color: 'var(--negative)' }}
            >
              <window.I.x size={12} />
              Reject
            </button>
            <button
              className="btn btn-sm btn-ghost"
              onClick={() => handleAction('duplicate')}
              title="Mark as duplicate"
            >
              <window.I.copy size={12} />
              Duplicate
            </button>
          </div>
        )}
      </td>
      <td onClick={(e) => e.stopPropagation()} style={{ textAlign: 'right', paddingRight: 16 }}>
        <div className="row gap-1" style={{ justifyContent: 'flex-end' }}>
          {/* Tick a brand off without leaving the list — the weekly meeting
              works straight down the To review tab. Labelled, not an icon: as a
              bare checkmark nobody recognised it as the review sign-off. */}
          {onUpdate && !isRejected ? (
            <MarkReviewedControl
              brand={brand}
              currentUser={currentUser}
              onUpdateBrand={onUpdate}
              onToast={onToast}
            />
          ) : null}
          <button className="btn btn-icon btn-ghost" onClick={handleDelete} title="Delete brand">
            <window.I.trash size={14} stroke="var(--negative)" />
          </button>
          <button className="btn btn-icon btn-ghost" onClick={onOpen} title="Open">
            <window.I.chevronRight size={14} />
          </button>
        </div>
      </td>
    </tr>
  );
}

// ─── Scoring help drawer ────────────────────────────────────────────────
function ScoringHelpDrawer({ onClose }) {
  const { SIGNALS, CATEGORY_MODIFIERS } = window.IPSEM_DATA;
  const bands = [80, 60, 30, 0].map((threshold) => window.scoreBand(threshold));
  return (
    <div className="drawer-bg" onClick={onClose}>
      <div className="drawer" onClick={(e) => e.stopPropagation()} style={{ padding: 28 }}>
        <div className="row" style={{ justifyContent: 'space-between', marginBottom: 18 }}>
          <h2 className="h2" style={{ margin: 0 }}>How scoring works</h2>
          <button className="btn btn-icon btn-ghost" onClick={onClose}>
            <window.I.x size={16} />
          </button>
        </div>
        <p className="text-2 text-sm" style={{ marginTop: 0, marginBottom: 18, lineHeight: 1.65 }}>
          Every news item is scored 0–100 for how likely it is to convert into a real outreach reply for IPSEM. The score blends an evidence-backed signal score with the AI's read of genuine buying intent, then scales it by how confident the AI is and how credible the source is.
        </p>
        <div className="col gap-2" style={{ marginBottom: 24 }}>
          <div className="row gap-2 text-sm text-2" style={{ alignItems: 'flex-start' }}>
            <window.I.check size={14} stroke="var(--positive)" style={{ marginTop: 2, flexShrink: 0 }} />
            <span><strong>Evidence required.</strong> A signal only counts if the AI can quote a sentence from the article proving it. Unsupported signals are dropped before scoring.</span>
          </div>
          <div className="row gap-2 text-sm text-2" style={{ alignItems: 'flex-start' }}>
            <window.I.check size={14} stroke="var(--positive)" style={{ marginTop: 2, flexShrink: 0 }} />
            <span><strong>Intent blended in.</strong> Final score = 55% evidence-signal score + 45% the AI's holistic buying-intent read, so keyword-stuffing alone can't inflate it.</span>
          </div>
          <div className="row gap-2 text-sm text-2" style={{ alignItems: 'flex-start' }}>
            <window.I.check size={14} stroke="var(--positive)" style={{ marginTop: 2, flexShrink: 0 }} />
            <span><strong>Confidence-scaled.</strong> Low-confidence reads are pulled down. Below 35 confidence, the item is rejected as noise.</span>
          </div>
          <div className="row gap-2 text-sm text-2" style={{ alignItems: 'flex-start' }}>
            <window.I.ban size={14} stroke="var(--negative)" style={{ marginTop: 2, flexShrink: 0 }} />
            <span><strong>Advertorials rejected.</strong> Sponsored posts, advertorials, opinion and link roundups are filtered out before they can score. Only brands the AI is confident about are auto-researched.</span>
          </div>
        </div>

        <div className="eyebrow" style={{ marginBottom: 10 }}>Score bands</div>
        <div className="col gap-2" style={{ marginBottom: 28 }}>
          {bands.map((b) => (
            <div key={b.band} className="row gap-3" style={{ padding: '10px 12px', background: 'var(--card-alt)', borderRadius: 8 }}>
              <span className="mono" style={{ minWidth: 56, fontWeight: 500, color: b.color, fontSize: 13 }}>
                {b.band === 'high' ? '80–100' : b.band === 'medium' ? '60–79' : b.band === 'low' ? '30–59' : '0–29'}
              </span>
              <span style={{ fontWeight: 600, minWidth: 70, color: b.color }}>{b.label}</span>
              <span className="text-2 text-sm grow">{b.desc}</span>
            </div>
          ))}
        </div>

        <div className="eyebrow" style={{ marginBottom: 10 }}>Signal weights</div>
        <table className="tbl" style={{ marginBottom: 28 }}>
          <thead>
            <tr><th>Signal</th><th style={{ width: 80, textAlign: 'right' }}>Points</th></tr>
          </thead>
          <tbody>
            {Object.entries(SIGNALS).sort((a, b) => b[1].weight - a[1].weight).map(([k, s]) => (
              <tr key={k}>
                <td>
                  <div className="col" style={{ gap: 2 }}>
                    <span style={{ fontWeight: 500 }}>{s.label}</span>
                    <span className="text-xs text-3">{s.desc}</span>
                  </div>
                </td>
                <td style={{ textAlign: 'right' }} className="mono text-sm">+{s.weight}</td>
              </tr>
            ))}
          </tbody>
        </table>

        <div className="eyebrow" style={{ marginBottom: 10 }}>Modifiers</div>
        <div className="col gap-2" style={{ marginBottom: 16 }}>
          <ModifierRow label="Recency · fresh news only" value={6} desc="the scan only pulls recent articles" />
          <ModifierRow label="Source · trusted editorial outlet" value={+4} desc="e.g. Reuters, FT, Ad Age, SportsPro" />
          <ModifierRow label="Category fit · premium / known converter" value={+5} desc="e.g. Airlines, Hotels, Tyres, Audio, AI" />
          <ModifierRow label="Category risk · crypto / news-only" value={-25} desc="brand-safety or no-buyer categories" />
        </div>

        <div className="text-3 text-xs" style={{ paddingTop: 16, borderTop: '1px solid var(--hairline)', lineHeight: 1.55 }}>
          Only brands scoring 60+ with high AI confidence are auto-researched. Everything else waits for your decision in the intake list. Click any score in the table to see its full breakdown.
        </div>
      </div>
    </div>
  );
}

function ModifierRow({ label, value, desc }) {
  return (
    <div className="row gap-3" style={{ padding: '8px 12px', background: 'var(--card-alt)', borderRadius: 6, fontSize: 13 }}>
      <span className="grow">
        {label}
        {desc ? <div className="text-xs text-3">{desc}</div> : null}
      </span>
      <span
        className="mono"
        style={{ color: value >= 0 ? 'var(--positive)' : 'var(--negative)', fontWeight: 500, minWidth: 44, textAlign: 'right' }}
      >
        {value > 0 ? '+' : ''}{value}
      </span>
    </div>
  );
}

// ─── Add brand drawer ────────────────────────────────────────────────────
function AddBrandDrawer({ brands, onClose, onAdd, onResearchUrl }) {
  const [mode, setMode] = React.useState('url'); // 'url' or 'manual'
  const [url, setUrl] = React.useState('');
  const [researching, setResearching] = React.useState(false);
  const [urlError, setUrlError] = React.useState('');

  // Manual form
  const [form, setForm] = React.useState({
    brand: '', category: '', hq: '', newsTitle: '', newsSource: '', newsUrl: '',
    description: '', signals: [],
  });
  const set = (k, v) => setForm((c) => ({ ...c, [k]: v }));
  const toggleSignal = (s) =>
    set('signals', form.signals.includes(s) ? form.signals.filter((x) => x !== s) : [...form.signals, s]);

  // Duplicate guard: warn (and block submit) if this brand is already tracked.
  // Matches on the same tolerant key the backend uses, so a curly apostrophe, a
  // doubled space or a "(Parent Co)" suffix cannot slip a second record through.
  const duplicateOf = React.useMemo(
    () => window.findBrandByName(brands, form.brand.trim()),
    [form.brand, brands],
  );

  const handleResearchUrl = async () => {
    const u = url.trim();
    if (!u || researching) return;
    setUrlError('');
    setResearching(true);
    try {
      // Backend reads the article, extracts the brand and starts real research.
      await onResearchUrl(u);
      onClose();  // the new brand opens in Brand Research automatically
    } catch (e) {
      setUrlError((e && e.message) || 'Could not research that URL. Try the manual entry tab.');
      setResearching(false);
    }
  };

  const handleManualSubmit = () => {
    if (!form.brand.trim()) return;
    const id = form.brand.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 24) + '-' + Date.now().toString(36).slice(-4);
    onAdd({
      id,
      brand: form.brand,
      category: form.category || 'Uncategorised',
      status: 'relevant',
      priority: 'tier-2',
      researchStatus: 'complete',
      assignee: 'AS',
      reviewDate: window.IPSEM_DATA.TODAY,
      signals: form.signals,
      relevanceScore: Math.min(95, 40 + form.signals.length * 10 + (form.description ? 5 : 0)),
      manuallyAdded: true,
      news: {
        title: form.newsTitle || `${form.brand} — manual entry`,
        summary: form.description || 'Manually added by user. No system-generated summary yet.',
        source: form.newsSource || 'Manual entry',
        date: window.IPSEM_DATA.TODAY,
        url: form.newsUrl || null,
      },
      description: form.description,
      hq: form.hq,
      coreValues: [],
      sponsorshipHistory: [],
      currentSponsorships: [],
      competitors: [],
      aiRecommendation: { properties: [], rationale: '' },
      potentialProperties: [],
      confirmedProperties: [],
      citations: [],
      contacts: [],
      draft: null,
    });
  };

  return (
    <div className="drawer-bg" onClick={onClose}>
      <div className="drawer" onClick={(e) => e.stopPropagation()} style={{ padding: 28 }}>
        <div className="row" style={{ justifyContent: 'space-between', marginBottom: 6 }}>
          <h2 className="h2" style={{ margin: 0 }}>Add brand</h2>
          <button className="btn btn-icon btn-ghost" onClick={onClose}>
            <window.I.x size={16} />
          </button>
        </div>
        <p className="text-2 text-sm" style={{ marginTop: 8, marginBottom: 18, lineHeight: 1.55 }}>
          Paste a news article URL and the system researches it for you, or enter what you already know manually.
        </p>

        <div className="toggle-group" style={{ marginBottom: 22 }}>
          <button className={mode === 'url' ? 'is-active' : ''} onClick={() => setMode('url')}>
            Research from URL
          </button>
          <button className={mode === 'manual' ? 'is-active' : ''} onClick={() => setMode('manual')}>
            Manual entry
          </button>
        </div>

        {mode === 'url' ? (
          <div className="col gap-3">
            <div>
              <div className="eyebrow" style={{ marginBottom: 8 }}>News article URL</div>
              <div className="field" style={{ height: 42 }}>
                <window.I.link size={14} stroke="var(--ink-3)" />
                <input
                  placeholder="https://www.marketingweek.com/…"
                  value={url}
                  onChange={(e) => { setUrl(e.target.value); if (urlError) setUrlError(''); }}
                  onKeyDown={(e) => { if (e.key === 'Enter') handleResearchUrl(); }}
                  disabled={researching}
                />
              </div>
              {urlError ? (
                <div className="row gap-1 text-xs" style={{ color: 'var(--negative)', marginTop: 8, alignItems: 'flex-start' }}>
                  <window.I.warn size={12} style={{ marginTop: 1, flexShrink: 0 }} />
                  <span>{urlError}</span>
                </div>
              ) : null}
            </div>

            <Card style={{ background: 'var(--card-alt)' }}>
              <div className="eyebrow row gap-1" style={{ marginBottom: 8 }}>
                <window.I.sparkle size={11} stroke="var(--accent)" />
                The system will
              </div>
              <ul className="text-2 text-sm" style={{ margin: 0, paddingLeft: 22, lineHeight: 1.65 }}>
                <li>Extract the brand and category from the article</li>
                <li>Detect signals (leadership change, expansion, revenue, sponsorship activity, etc.)</li>
                <li>Run full brand research with citations</li>
                <li>Score relevance and recommend matching IPSEM properties</li>
              </ul>
            </Card>

            <div className="row gap-2" style={{ marginTop: 14, justifyContent: 'flex-end' }}>
              <button className="btn" onClick={onClose} disabled={researching}>Cancel</button>
              <button className="btn btn-primary" disabled={!url.trim() || researching} onClick={handleResearchUrl}>
                {researching ? <><span className="skel" style={{ width: 12, height: 12, borderRadius: 999 }}/>Researching…</> : <><window.I.sparkle size={13} />Research this</>}
              </button>
            </div>
          </div>
        ) : (
          <div className="col gap-3">
            <EditField2 label="Brand name *" value={form.brand} onChange={(v) => set('brand', v)} />
            {duplicateOf ? (
              <div className="row gap-1 text-xs" style={{ color: 'var(--negative)', marginTop: -8 }}>
                <window.I.warn size={12} />
                {duplicateOf.brand} is already in the pipeline ({duplicateOf.status || 'tracked'}).
              </div>
            ) : null}
            <div className="row gap-2">
              <div style={{ flex: 1 }}><EditField2 label="Category" value={form.category} onChange={(v) => set('category', v)} /></div>
              <div style={{ flex: 1 }}><EditField2 label="HQ location" value={form.hq} onChange={(v) => set('hq', v)} /></div>
            </div>
            <EditField2 label="News headline" value={form.newsTitle} onChange={(v) => set('newsTitle', v)} />
            <div className="row gap-2">
              <div style={{ flex: 1 }}><EditField2 label="News source" value={form.newsSource} onChange={(v) => set('newsSource', v)} placeholder="e.g. Pearlfinders" /></div>
              <div style={{ flex: 2 }}><EditField2 label="News URL" value={form.newsUrl} onChange={(v) => set('newsUrl', v)} placeholder="https://…" /></div>
            </div>
            <EditField2 label="Brand description" value={form.description} onChange={(v) => set('description', v)} multiline />

            <div>
              <div className="eyebrow" style={{ marginBottom: 8 }}>Relevance signals</div>
              <div className="row gap-1" style={{ flexWrap: 'wrap' }}>
                {Object.entries(window.IPSEM_DATA.SIGNALS).map(([k, s]) => (
                  <button
                    key={k}
                    type="button"
                    onClick={() => toggleSignal(k)}
                    className="pill"
                    style={{
                      cursor: 'pointer',
                      background: form.signals.includes(k) ? `var(--sig-${s.tone}-bg)` : 'transparent',
                      color: form.signals.includes(k) ? `var(--sig-${s.tone}-fg)` : 'var(--ink-3)',
                      border: form.signals.includes(k) ? '1px solid transparent' : '1px solid var(--hairline)',
                      fontSize: 11.5,
                    }}
                  >
                    {s.label}
                  </button>
                ))}
              </div>
            </div>

            <div className="row gap-2" style={{ marginTop: 14, justifyContent: 'flex-end' }}>
              <button className="btn" onClick={onClose}>Cancel</button>
              <button className="btn btn-primary" disabled={!form.brand.trim() || !!duplicateOf} onClick={handleManualSubmit}>
                <window.I.plus size={13} />
                Add brand
              </button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

function EditField2({ label, value, onChange, multiline, placeholder }) {
  return (
    <div>
      <div className="eyebrow" style={{ marginBottom: 6 }}>{label}</div>
      {multiline ? (
        <textarea
          value={value}
          onChange={(e) => onChange(e.target.value)}
          placeholder={placeholder}
          rows={3}
          style={{
            width: '100%', padding: '10px 12px',
            border: '1px solid var(--hairline-strong)', borderRadius: 6,
            font: 'inherit', fontSize: 13.5, lineHeight: 1.5,
            background: 'var(--card)', color: 'var(--ink)', resize: 'vertical',
          }}
        />
      ) : (
        <input
          value={value}
          onChange={(e) => onChange(e.target.value)}
          placeholder={placeholder}
          style={{
            width: '100%', padding: '8px 12px',
            border: '1px solid var(--hairline-strong)', borderRadius: 6,
            font: 'inherit', fontSize: 13.5,
            background: 'var(--card)', color: 'var(--ink)',
          }}
        />
      )}
    </div>
  );
}

window.NewsIntake = NewsIntake;

