// ====================================================================
// LISTINGS, tabbed deep-dive: Overview, Directories, Edit Info,
//   Photos, AI Visibility, Analytics. Reviews live on their own page.
// ====================================================================
(function () {
const { useState, useEffect, useMemo } = React;

// ---------- shared bits ----------
const SectionLabel = ({ children, count }) => (
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', margin: '20px 0 10px' }}>
    <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--ink-3)' }}>{children}</div>
    {count && <span style={{ fontSize: 11, color: 'var(--ink-4)', fontFamily: 'var(--ff-mono)' }}>{count}</span>}
  </div>
);

function Pill({ tone = 'neutral', children, size = 'sm' }) {
  const map = {
    ok: { bg: 'var(--sage-soft)', col: 'var(--sage)' },
    warn: { bg: 'var(--marigold-soft)', col: '#917200' },
    bad: { bg: 'var(--berry-soft)', col: '#a12118' },
    info: { bg: 'var(--sky-soft)', col: 'var(--sky)' },
    neutral: { bg: 'var(--cream)', col: 'var(--ink-3)' },
    violet: { bg: 'rgba(77,105,137,0.12)', col: 'var(--violet)' },
  };
  const t = map[tone] || map.neutral;
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 4, padding: size === 'xs' ? '1px 7px' : '2px 9px',
      fontSize: size === 'xs' ? 10 : 10.5, fontWeight: 700, letterSpacing: '.02em',
      borderRadius: 999, background: t.bg, color: t.col, whiteSpace: 'nowrap',
    }}>{children}</span>
  );
}

// ---------- Hero ----------
function ListingsHero({ d, onTab, onScan, scanning }) {
  return (
    <div className="card card-pad" style={{
      background: 'linear-gradient(135deg, var(--ink) 0%, #2a2a2b 100%)',
      color: 'var(--cream)', border: 'none', marginBottom: 16,
      display: 'grid', gridTemplateColumns: '110px 1fr auto', gap: 24, alignItems: 'center',
    }}>
      {/* Score ring */}
      <div style={{ position: 'relative', width: 110, height: 110 }}>
        <svg width="110" height="110" viewBox="0 0 100 100">
          <circle cx="50" cy="50" r="42" fill="none" stroke="rgba(250,246,239,0.12)" strokeWidth="9"/>
          <circle cx="50" cy="50" r="42" fill="none" stroke="var(--sage)" strokeWidth="9"
            strokeDasharray="264" strokeDashoffset={264 - (264 * d.score / 100)} strokeLinecap="round"
            transform="rotate(-90 50 50)"/>
        </svg>
        <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
          alignItems: 'center', justifyContent: 'center' }}>
          <div style={{ fontFamily: 'var(--ff-display)', fontSize: 30, fontWeight: 500, lineHeight: 1 }}>{d.score}</div>
          <div style={{ fontSize: 8.5, color: 'rgba(250,246,239,0.5)', textTransform: 'uppercase', letterSpacing: '.12em', marginTop: 3 }}>Score</div>
        </div>
      </div>

      {/* Stats */}
      <div>
        <div style={{ fontFamily: 'var(--ff-display)', fontSize: 18, fontWeight: 500, marginBottom: 4 }}>
          Listing health · <span style={{ color: 'var(--sage)' }}>{d.score}/100</span>
          <span style={{ fontSize: 12, color: 'rgba(250,246,239,0.5)', fontFamily: 'var(--ff-ui)', fontWeight: 400, marginLeft: 8 }}>
            Top 24% of FreshNest system · ↑ {d.scoreDelta} this wk
          </span>
        </div>
        <div style={{ fontSize: 11.5, color: 'rgba(250,246,239,0.45)', marginBottom: 14 }}>
          2 issues need attention · Last scan: today 7:02 AM · 42 directories monitored
        </div>
        <div style={{ display: 'flex', gap: 22, flexWrap: 'wrap' }}>
          {[
            { v: '40', l: 'Live ✓', c: 'var(--sage)' },
            { v: d.napAccuracy + '%', l: 'NAP', c: 'var(--cream)' },
            { v: '2', l: 'Issues', c: 'var(--marigold)' },
            { v: d.aiScore, l: 'AI Score', c: 'var(--violet)' },
            { v: d.avgRating + '★', l: 'Avg Rating', c: 'var(--cream)' },
            { v: d.totalReviews, l: 'Reviews', c: 'var(--cream)' },
          ].map((s, i, arr) => (
            <React.Fragment key={i}>
              <div style={{ textAlign: 'left' }}>
                <div style={{ fontFamily: 'var(--ff-display)', fontSize: 22, fontWeight: 500, color: s.c, lineHeight: 1.1 }}>{s.v}</div>
                <div style={{ fontSize: 9.5, color: 'rgba(250,246,239,0.4)', textTransform: 'uppercase', letterSpacing: '.1em', marginTop: 2 }}>{s.l}</div>
              </div>
              {i < arr.length - 1 && <div style={{ width: 1, background: 'rgba(250,246,239,0.1)', alignSelf: 'stretch' }}/>}
            </React.Fragment>
          ))}
        </div>
      </div>

      {/* Actions */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        <button className="btn btn-sm" style={{ background: 'rgba(250,246,239,0.08)', color: 'var(--cream)', border: '1px solid rgba(250,246,239,0.18)' }}
          onClick={() => onTab('ai')}>View AI Visibility →</button>
        <button className="btn btn-sm" style={{ background: 'rgba(250,246,239,0.04)', color: 'rgba(250,246,239,0.7)', border: '1px solid rgba(250,246,239,0.12)' }}
          onClick={onScan} disabled={scanning}>{scanning ? 'Scanning…' : '↺ Scan now'}</button>
      </div>
    </div>
  );
}

// ---------- Lenny alert ----------
function IssueAlert({ d, onFix }) {
  return (
    <div style={{ background: 'var(--marigold-soft)', border: '1px solid var(--marigold)',
      borderRadius: 14, padding: '14px 18px', marginBottom: 16,
      display: 'flex', gap: 14, alignItems: 'flex-start' }}>
      <div style={{ width: 40, height: 40, background: 'var(--paper)', borderRadius: '50%',
        border: '2px solid var(--marigold)', overflow: 'hidden', flexShrink: 0 }}>
        <window.LennyLlama size={40} mood="alert" bg="circle"/>
      </div>
      <div style={{ flex: 1 }}>
        <div style={{ fontSize: 13, fontWeight: 700, color: '#725900', marginBottom: 3 }}>
          2 issues found that could hurt your local search rankings
        </div>
        <div style={{ fontSize: 12, color: '#ad8406', lineHeight: 1.55 }}>
          <strong>GBP URL missing</strong>, the #1 field AI platforms use to verify your location. <strong>Google Sunday hours</strong> show 8 PM but your profile says 9 PM, publisher overwrite detected.
        </div>
      </div>
      <button className="btn btn-terra btn-sm" onClick={onFix}>Fix all issues →</button>
    </div>
  );
}

// ====================================================================
// TAB: OVERVIEW
// ====================================================================
function OverviewTab({ data, d, onTab, onForceSync }) {
  return (
    <>
      <SectionLabel>Issues requiring attention</SectionLabel>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 4 }}>
        {d.issues.map((iss, i) => (
          <div key={i} className="card" style={{
            padding: '14px 18px', display: 'flex', gap: 14, alignItems: 'center',
            borderColor: iss.severity === 'high' ? 'var(--terra)' : 'var(--marigold)',
            borderWidth: 1.5,
          }}>
            <div style={{ width: 38, height: 38, borderRadius: '50%',
              background: iss.severity === 'high' ? 'var(--terra-soft)' : 'var(--marigold-soft)',
              display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
              {iss.severity === 'high' ? (
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--terra)" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
              ) : (
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#917200" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
              )}
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>{iss.title}</div>
              <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 2, lineHeight: 1.5 }}>{iss.body}</div>
            </div>
            <Pill tone={iss.severity === 'high' ? 'bad' : 'warn'}>{iss.impact}</Pill>
            <button className="btn btn-sm" style={{
              background: iss.severity === 'high' ? 'var(--terra)' : 'var(--marigold)',
              color: iss.severity === 'high' ? '#fff' : '#725900', border: 'none',
            }} onClick={() => iss.kind === 'missing' ? onTab('edit') : T('Force sync queued', 'Google will sync within 4 hours.', 'info')}>
              {iss.kind === 'missing' ? 'Fix now →' : 'Force sync'}
            </button>
          </div>
        ))}
      </div>

      {/* Publisher grid */}
      <SectionLabel count={data.listings.length + ' directories'}>All directories, live status</SectionLabel>
      <div className="card" style={{ padding: 14 }}>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: 8 }}>
          {data.listings.map((l, i) => {
            const tone = l.status === 'live' ? 'ok' : 'warn';
            return (
              <div key={i} title={`${l.name}, ${l.status}`} style={{
                padding: '10px 12px', borderRadius: 10, background: 'var(--cream)',
                border: '1px solid var(--cream-3)', display: 'flex', flexDirection: 'column', gap: 4,
              }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 6 }}>
                  <div style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--ink)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{l.name}</div>
                  <div style={{ width: 8, height: 8, borderRadius: '50%',
                    background: l.status === 'live' ? 'var(--sage)' : 'var(--marigold)', flexShrink: 0,
                  }}/>
                </div>
                <div style={{ fontSize: 10, color: 'var(--ink-4)', fontFamily: 'var(--ff-mono)' }}>{l.fields}</div>
              </div>
            );
          })}
        </div>
      </div>

      <SourceNote>Source: Google Business Profile</SourceNote>

      {/* NAP trend */}
      <SectionLabel>NAP accuracy, last 6 months</SectionLabel>
      <div className="card card-pad">
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', marginBottom: 16 }}>
          <div>
            <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>Name · Address · Phone consistency</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>Checked daily across all 42 directories</div>
          </div>
          <div style={{ fontFamily: 'var(--ff-display)', fontSize: 28, fontWeight: 500, color: 'var(--sage)', lineHeight: 1 }}>
            {d.napAccuracy}%
            <span style={{ fontSize: 11, fontWeight: 400, color: 'var(--ink-4)', marginLeft: 6, fontFamily: 'var(--ff-ui)' }}>current</span>
          </div>
        </div>
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 8, height: 72, marginBottom: 6 }}>
          {d.napTrend.map((v, i) => (
            <div key={i} style={{ flex: 1, height: `${v}%`, background: 'linear-gradient(to top, var(--sage), var(--sage-soft))', borderRadius: '6px 6px 0 0', position: 'relative' }}>
              <div style={{ position: 'absolute', top: -16, left: '50%', transform: 'translateX(-50%)', fontSize: 10, color: 'var(--ink-3)', fontFamily: 'var(--ff-mono)' }}>{v}</div>
            </div>
          ))}
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10.5, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.05em', fontWeight: 600 }}>
          <span>Oct</span><span>Nov</span><span>Dec</span><span>Jan</span><span>Feb</span><span>Mar</span>
        </div>
      </div>
    </>
  );
}

// ====================================================================
// TAB: DIRECTORIES (filterable, controlled vs influence)
// ====================================================================
function DirectoriesTab({ data, onDetail }) {
  const [statusFilter, setStatusFilter] = useState('all');
  const [typeFilter, setTypeFilter] = useState('all');
  const [search, setSearch] = useState('');

  const filter = (rows) => rows.filter(l =>
    (statusFilter === 'all' || l.status === statusFilter) &&
    (typeFilter === 'all' || l.type === typeFilter) &&
    (!search || l.name.toLowerCase().includes(search.toLowerCase()))
  );

  const controlled = filter(data.listings.filter(l => l.controlled));
  const influence = filter(data.listings.filter(l => !l.controlled));

  const renderRow = (l, i, kind) => (
    <tr key={i} onClick={() => onDetail(l)} style={{ cursor: 'pointer' }}>
      <td className="strong">{l.name}</td>
      <td><Pill tone="neutral">{l.type}</Pill></td>
      <td><Pill tone={l.status === 'live' ? 'ok' : 'warn'}>{l.status === 'live' ? '● Live' : '⚠ ' + (l.controlled ? 'Issue' : 'Drift')}</Pill></td>
      <td className="mono muted">{l.lastSync}</td>
      <td className="mono">{l.fields}</td>
      <td>
        <button className="btn btn-quiet btn-xs" onClick={e => {
          e.stopPropagation();
          if (kind === 'controlled') onDetail(l);
          else T('Force sync queued', `${l.name} will sync in ~15 min.`, 'info');
        }}>{kind === 'controlled' ? 'View' : 'Force sync'}</button>
      </td>
    </tr>
  );

  return (
    <>
      {/* Filter bar */}
      <div style={{ display: 'flex', gap: 8, marginBottom: 16, flexWrap: 'wrap', alignItems: 'center' }}>
        <select value={statusFilter} onChange={e => setStatusFilter(e.target.value)}
          style={{ padding: '7px 12px', border: '1px solid var(--cream-3)', borderRadius: 8, fontSize: 12, fontFamily: 'var(--ff-ui)', background: 'var(--paper)' }}>
          <option value="all">All status</option>
          <option value="live">Live ✓</option>
          <option value="issue">Issues ⚠</option>
        </select>
        <select value={typeFilter} onChange={e => setTypeFilter(e.target.value)}
          style={{ padding: '7px 12px', border: '1px solid var(--cream-3)', borderRadius: 8, fontSize: 12, fontFamily: 'var(--ff-ui)', background: 'var(--paper)' }}>
          <option value="all">All types</option>
          <option value="Search">Search Engines</option>
          <option value="Maps">Maps</option>
          <option value="Social">Social</option>
          <option value="Review">Review Sites</option>
          <option value="AI">AI Platforms</option>
          <option value="Directory">Directories</option>
        </select>
        <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search directories…"
          style={{ flex: 1, minWidth: 200, padding: '7px 12px', border: '1px solid var(--cream-3)', borderRadius: 8, fontSize: 12, fontFamily: 'var(--ff-ui)', background: 'var(--paper)' }}/>
        <span style={{ fontSize: 11, color: 'var(--ink-3)', fontFamily: 'var(--ff-mono)' }}>{controlled.length + influence.length} of {data.listings.length}</span>
        <button className="btn btn-primary btn-sm" onClick={() => T('Sync queued', 'All directories will refresh within 15 min.', 'info')}>↺ Sync all</button>
      </div>

      {/* Controlled */}
      <div style={{ marginBottom: 6 }}>
        <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink-2)', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 4, display: 'flex', alignItems: 'center', gap: 7 }}>
          <span style={{ width: 9, height: 9, borderRadius: '50%', background: 'var(--sage)', display: 'inline-block' }}/>
          Directories we control
        </div>
        <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginBottom: 10, paddingLeft: 16, lineHeight: 1.55 }}>
          Direct API connection, edits sync within minutes. If data is wrong here, we fix it immediately.
        </div>
      </div>
      <div className="card" style={{ overflow: 'hidden', marginBottom: 18 }}>
        <table className="tbl">
          <thead><tr><th>Directory</th><th>Type</th><th>Status</th><th>Last sync</th><th>Fields</th><th/></tr></thead>
          <tbody>{controlled.map((l, i) => renderRow(l, i, 'controlled'))}</tbody>
        </table>
      </div>

      {/* Influence */}
      <div style={{ marginBottom: 6 }}>
        <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink-2)', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 4, display: 'flex', alignItems: 'center', gap: 7 }}>
          <span style={{ width: 9, height: 9, borderRadius: '50%', background: 'var(--marigold)', display: 'inline-block' }}/>
          Directories we influence
        </div>
        <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginBottom: 10, paddingLeft: 16, lineHeight: 1.55 }}>
          We push data but can't control timing, these run on their own refresh cycles (days to weeks). Outdated data here means the directory hasn't processed our update yet.
        </div>
      </div>
      <div className="card" style={{ overflow: 'hidden' }}>
        <table className="tbl">
          <thead><tr><th>Directory</th><th>Type</th><th>Status</th><th>Last sync</th><th>Fields</th><th/></tr></thead>
          <tbody>{influence.map((l, i) => renderRow(l, i, 'influence'))}</tbody>
        </table>
      </div>
    </>
  );
}

// ====================================================================
// TAB: EDIT INFO
// ====================================================================
function EditTab({ d, dirty, setDirty, onSave }) {
  const [info, setInfo] = useState(d.info);
  useEffect(() => { setInfo(d.info); }, [d.info]);

  const upd = (k, v) => { setInfo({ ...info, [k]: v }); setDirty(true); };
  const updHour = (i, k, v) => {
    const next = [...info.hours];
    next[i] = { ...next[i], [k]: v };
    setInfo({ ...info, hours: next });
    setDirty(true);
  };

  const Field = ({ label, locked, error, children, hint }) => (
    <div>
      <label style={{ fontSize: 10, fontWeight: 700, color: error ? 'var(--terra)' : 'var(--ink-3)', display: 'block', marginBottom: 4, textTransform: 'uppercase', letterSpacing: '.06em' }}>
        {error && '⚠ '}{label}{locked && ' 🔒'}
      </label>
      {children}
      {hint && <div style={{ fontSize: 11, color: error ? 'var(--terra)' : 'var(--ink-4)', marginTop: 4, fontWeight: error ? 600 : 400 }}>{hint}</div>}
    </div>
  );

  const inp = (val, on, locked) => ({
    value: val, onChange: e => on(e.target.value), readOnly: locked,
    style: {
      width: '100%', padding: '9px 11px', fontSize: 13, fontFamily: 'var(--ff-ui)',
      border: '1px solid var(--cream-3)', borderRadius: 8,
      background: locked ? 'var(--cream)' : 'var(--paper)',
      color: locked ? 'var(--ink-4)' : 'var(--ink)', boxSizing: 'border-box', outline: 'none',
    },
  });

  return (
    <>
      <div style={{ background: 'var(--sky-soft)', border: '1px solid var(--sky)', borderRadius: 12,
        padding: '11px 16px', marginBottom: 16, fontSize: 12, color: 'var(--sky)', display: 'flex', alignItems: 'center', gap: 10 }}>
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
          <span><strong>Update once, sync everywhere.</strong> Changes push to all 42 directories within minutes. Fields marked 🔒 are controlled by your brand.</span>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
        {/* LEFT */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>

          {/* Business info */}
          <div className="card card-pad">
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)', marginBottom: 14, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
              <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 21h18M3 7v14M21 7v14M6 21V11M18 21V11M9 9V5l3-2 3 2v4M9 9h6"/></svg>
                Business information
              </span>
              <Pill tone="ok">Synced 7:02 AM</Pill>
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
              <Field label="Business name" locked>
                <input {...inp(info.name, () => {}, true)}/>
              </Field>
              <Field label="Street address">
                <input {...inp(info.address, v => upd('address', v))}/>
              </Field>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 60px 90px', gap: 8 }}>
                <Field label="City"><input {...inp(info.city, v => upd('city', v))}/></Field>
                <Field label="State"><input {...inp(info.state, v => upd('state', v))}/></Field>
                <Field label="ZIP"><input {...inp(info.zip, v => upd('zip', v))}/></Field>
              </div>
              <Field label="Phone">
                <input {...inp(info.phone, v => upd('phone', v))}/>
              </Field>
              <Field label="Website URL">
                <input {...inp(info.website, v => upd('website', v))}/>
              </Field>
              <Field label="Google Business Profile URL, missing"
                error
                hint="Required for AI citation accuracy. Find in your Google Business Profile → Share location.">
                <input
                  value={info.gbpUrl}
                  onChange={e => upd('gbpUrl', e.target.value)}
                  placeholder="https://g.co/kgs/..."
                  style={{
                    width: '100%', padding: '9px 11px', fontSize: 13, fontFamily: 'var(--ff-ui)',
                    border: '2px solid var(--terra)', borderRadius: 8,
                    background: 'var(--paper)', boxSizing: 'border-box', outline: 'none',
                  }}/>
              </Field>
            </div>
          </div>

          {/* Hours */}
          <div className="card card-pad">
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)', marginBottom: 14, display: 'flex', alignItems: 'center', gap: 8 }}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
              Business hours
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '90px 1fr 1fr', gap: 8, alignItems: 'center', fontSize: 12 }}>
              <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.06em' }}>Day</div>
              <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.06em' }}>Opens</div>
              <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.06em' }}>Closes</div>
              {info.hours.map((h, i) => (
                <React.Fragment key={i}>
                  <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>{h.day}</div>
                  <input value={h.open} onChange={e => updHour(i, 'open', e.target.value)} style={{ padding: '6px 8px', border: '1px solid var(--cream-3)', borderRadius: 6, fontSize: 12, fontFamily: 'var(--ff-ui)', outline: 'none', background: 'var(--paper)' }}/>
                  <input value={h.close} onChange={e => updHour(i, 'close', e.target.value)}
                    style={{ padding: '6px 8px', border: h.flag ? '2px solid var(--marigold)' : '1px solid var(--cream-3)', borderRadius: 6, fontSize: 12, fontFamily: 'var(--ff-ui)', outline: 'none', background: 'var(--paper)' }}/>
                </React.Fragment>
              ))}
            </div>
            {info.hours.find(h => h.flag) && (
              <div style={{ marginTop: 10, fontSize: 11, color: '#725900', fontWeight: 600, background: 'var(--marigold-soft)', padding: '8px 10px', borderRadius: 8 }}>
                ⚠ {info.hours.find(h => h.flag).flag}
              </div>
            )}
          </div>

          {/* Special hours */}
          <div className="card card-pad">
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)', marginBottom: 6, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
                Special hours &amp; closures
              </span>
              <button className="btn btn-ghost btn-xs" onClick={() => T('Add special hours', 'Date picker would open here.', 'info')}>+ Add</button>
            </div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginBottom: 10 }}>Override regular hours on specific dates, synced to Google, Apple Maps, Yelp.</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
              {info.specialHours.map((sh, i) => (
                <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px',
                  background: sh.closed ? 'var(--terra-soft)' : 'var(--cream)', borderRadius: 8 }}>
                  <span style={{ fontSize: 12, fontWeight: 600, color: sh.closed ? '#8c4a13' : 'var(--ink)', minWidth: 130 }}>{sh.date}</span>
                  <span style={{ fontSize: 12, color: sh.closed ? '#8c4a13' : 'var(--ink-2)' }}>{sh.hours}</span>
                </div>
              ))}
            </div>
          </div>
        </div>

        {/* RIGHT */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {/* Description */}
          <div className="card card-pad">
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)', marginBottom: 14, display: 'flex', alignItems: 'center', gap: 8 }}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
              Description &amp; categories
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
              <Field label={`Short description (${info.shortDesc.length} / 160)`}>
                <textarea rows={3} value={info.shortDesc} onChange={e => upd('shortDesc', e.target.value)}
                  style={{ width: '100%', padding: '9px 11px', fontSize: 13, fontFamily: 'var(--ff-ui)', border: '1px solid var(--cream-3)', borderRadius: 8, background: 'var(--paper)', boxSizing: 'border-box', outline: 'none', resize: 'none', lineHeight: 1.5 }}/>
              </Field>
              <Field label="Long description">
                <textarea rows={5} value={info.longDesc} onChange={e => upd('longDesc', e.target.value)}
                  style={{ width: '100%', padding: '9px 11px', fontSize: 12.5, fontFamily: 'var(--ff-ui)', border: '1px solid var(--cream-3)', borderRadius: 8, background: 'var(--paper)', boxSizing: 'border-box', outline: 'none', resize: 'vertical', lineHeight: 1.6 }}/>
              </Field>
              <Field label="Primary category" locked>
                <input {...inp(info.category, () => {}, true)}/>
              </Field>
              <Field label="Additional categories" hint="Comma-separated. Matched to publisher taxonomy where available.">
                <input {...inp(info.moreCategories, v => upd('moreCategories', v))}/>
              </Field>
            </div>
          </div>

          {/* Links */}
          <div className="card card-pad">
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)', marginBottom: 14, display: 'flex', alignItems: 'center', gap: 8 }}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
              Links &amp; social profiles
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              {info.links.map((lnk, i) => (
                <Field key={i} label={lnk.label}>
                  <input {...inp(lnk.val, v => {
                    const next = [...info.links]; next[i] = { ...next[i], val: v };
                    setInfo({ ...info, links: next }); setDirty(true);
                  })}/>
                </Field>
              ))}
            </div>
          </div>

          {/* AI keywords */}
          <div className="card card-pad">
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)', marginBottom: 4, display: 'flex', alignItems: 'center', gap: 8 }}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--violet)" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><circle cx="12" cy="5" r="2"/><path d="M12 7v4"/><line x1="8" y1="16" x2="8" y2="16"/><line x1="16" y1="16" x2="16" y2="16"/></svg>
              AI-optimized keywords
            </div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginBottom: 10 }}>AI platforms use these to understand your services and cite you correctly. Comma-separated.</div>
            <textarea rows={3} value={info.keywords} onChange={e => upd('keywords', e.target.value)}
              style={{ width: '100%', padding: '9px 11px', fontSize: 12, fontFamily: 'var(--ff-ui)', border: '1px solid var(--cream-3)', borderRadius: 8, background: 'var(--paper)', boxSizing: 'border-box', outline: 'none', resize: 'none', lineHeight: 1.6 }}/>
            <div style={{ marginTop: 10, background: 'var(--sage-soft)', borderRadius: 10, padding: '10px 12px',
              display: 'flex', gap: 10, alignItems: 'flex-start' }}>
              <div style={{ width: 28, height: 28, borderRadius: '50%', overflow: 'hidden', flexShrink: 0, border: '1.5px solid var(--sage)', background: 'var(--paper)' }}>
                <window.LennyLlama size={28} mood="happy" bg="circle"/>
              </div>
              <div>
                <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--sage)', marginBottom: 2 }}>Lenny says</div>
                <div style={{ fontSize: 11.5, color: 'var(--ink-2)', lineHeight: 1.55 }}>Add "house cleaning Austin", your #1 revenue keyword from SEO but missing from AI fields. Adding it could boost your AI Citation Score by 8 points.</div>
              </div>
            </div>
          </div>
        </div>
      </div>

      {/* Sticky save bar */}
      {dirty && (
        <div style={{ position: 'sticky', bottom: 16, marginTop: 18, zIndex: 10,
          background: 'var(--ink)', borderRadius: 14, padding: '12px 18px',
          display: 'flex', alignItems: 'center', gap: 14,
          boxShadow: '0 12px 32px rgba(20,15,10,0.32)' }}>
          <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--cream)', flex: 1 }}>
            Unsaved changes, will sync to all 42 directories when saved
          </div>
          <button className="btn btn-sm" style={{ background: 'var(--sage)', color: 'var(--paper)', border: 'none', fontWeight: 700 }}
            onClick={() => { onSave(info); setDirty(false); }}>Save &amp; sync all →</button>
          <button className="btn btn-sm" style={{ background: 'rgba(250,246,239,0.08)', color: 'var(--cream)', border: 'none' }}
            onClick={() => { setInfo(d.info); setDirty(false); }}>Discard</button>
        </div>
      )}
    </>
  );
}

// ====================================================================
// TAB: PHOTOS
// ====================================================================
function PhotosTab({ d }) {
  return (
    <>
      <div style={{ background: 'var(--sky-soft)', border: '1px solid var(--sky)', borderRadius: 12,
        padding: '11px 16px', marginBottom: 16, fontSize: 12, color: 'var(--sky)', lineHeight: 1.55 }}>
        <strong>Photos sync to Google, Yelp, Facebook, and Apple Maps.</strong> Cover photo and logo are managed at brand level. High-quality photos improve click-through by up to 35%.
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: 16 }}>
        {/* Photo grid */}
        <div className="card card-pad">
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 14 }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)' }}>Your location photos ({d.photos.length})</div>
            <button className="btn btn-primary btn-xs" onClick={() => T('Photo upload', 'File picker would open here.', 'info')}>+ Upload</button>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8 }}>
            {d.photos.map((p, i) => (
              <div key={i} title={p.label} style={{ aspectRatio: '1', background: p.grad, borderRadius: 10,
                display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 28, position: 'relative', cursor: 'pointer' }}>
                {p.emoji}
                <div style={{ position: 'absolute', bottom: 4, left: 6, right: 6, fontSize: 9, fontWeight: 700, color: 'rgba(0,0,0,0.55)', textTransform: 'uppercase', letterSpacing: '.04em', textAlign: 'left' }}>{p.label}</div>
              </div>
            ))}
            <div style={{ aspectRatio: '1', background: 'var(--cream)', borderRadius: 10,
              display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 11,
              color: 'var(--ink-3)', cursor: 'pointer', border: '2px dashed var(--cream-3)' }}>+ Add</div>
          </div>
        </div>

        {/* Sync status + guidelines */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <div className="card card-pad">
            <div style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--ink)', marginBottom: 10 }}>Photo sync by publisher</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {d.photoSync.map((p, i) => (
                <div key={i} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                  <span style={{ fontSize: 12.5, color: 'var(--ink-2)' }}>{p.pub}</span>
                  <Pill tone={p.status === 'ok' ? 'ok' : 'warn'}>
                    {p.count} photos {p.status === 'ok' ? '✓' : '⚠'}
                  </Pill>
                </div>
              ))}
            </div>
          </div>

          <div className="card card-pad">
            <div style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--ink)', marginBottom: 8 }}>Photo guidelines</div>
            <div style={{ fontSize: 11.5, color: 'var(--ink-2)', lineHeight: 1.85 }}>
              ✓ Minimum 720×720px JPG or PNG<br/>
              ✓ Show your actual store and services<br/>
              ✓ Good lighting, no dark or blurry<br/>
              ✓ Include team, interior, exterior<br/>
              <span style={{ color: 'var(--terra)' }}>✗ No stock photos, penalized by publishers</span><br/>
              <span style={{ color: 'var(--terra)' }}>✗ No text overlays on product shots</span>
            </div>
          </div>

          <div style={{ background: 'var(--sage-soft)', border: '1px solid rgba(110,140,98,0.35)', borderRadius: 12, padding: '12px 14px',
            display: 'flex', gap: 10, alignItems: 'flex-start' }}>
            <div style={{ width: 32, height: 32, borderRadius: '50%', overflow: 'hidden', flexShrink: 0, border: '1.5px solid var(--sage)', background: 'var(--paper)' }}>
              <window.LennyLlama size={32} mood="happy" bg="circle"/>
            </div>
            <div>
              <div style={{ fontSize: 11.5, fontWeight: 700, color: 'var(--sage)', marginBottom: 2 }}>Lenny tip</div>
              <div style={{ fontSize: 11.5, color: 'var(--ink-2)', lineHeight: 1.55 }}>
                Add an office-cleaning photo, you get 8 office-cleaning leads/mo from SEO but zero photos of it. Apple Maps has 0 photos right now too, fast win.
              </div>
            </div>
          </div>
        </div>
      </div>
    </>
  );
}

// ====================================================================
// TAB: AI VISIBILITY
// ====================================================================
function AiTab({ d, onTab }) {
  return (
    <>
      {/* Hero */}
      <div className="card card-pad" style={{
        background: 'linear-gradient(135deg, #001e40 0%, #001732 100%)', color: 'var(--cream)',
        border: 'none', marginBottom: 16, display: 'grid', gridTemplateColumns: '180px 1fr', gap: 24, alignItems: 'center',
      }}>
        <div style={{ textAlign: 'center', borderRight: '1px solid rgba(250,246,239,0.12)', paddingRight: 22 }}>
          <div style={{ fontFamily: 'var(--ff-display)', fontSize: 56, fontWeight: 500, lineHeight: 1, color: 'var(--violet)' }}>{d.aiScore}</div>
          <div style={{ fontSize: 10, color: 'rgba(250,246,239,0.5)', textTransform: 'uppercase', letterSpacing: '.1em', marginTop: 4 }}>AI Citation Score</div>
          <div style={{ fontSize: 11, color: 'rgba(250,246,239,0.35)', marginTop: 3, fontFamily: 'var(--ff-mono)' }}>out of 100</div>
        </div>
        <div>
          <div style={{ fontSize: 14, fontWeight: 700, marginBottom: 6 }}>How AI search sees your business</div>
          <div style={{ fontSize: 12, color: 'rgba(250,246,239,0.6)', lineHeight: 1.6, marginBottom: 14 }}>
            AI platforms (ChatGPT, Perplexity, Google AI) cross-check your data across sources before citing you. Score {d.aiScore} = cited correctly ~{d.aiScore}% of the time. The missing GBP URL is your biggest gap, fixing it could push you to 84+.
          </div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {d.aiPlatforms.map((p, i) => (
              <div key={i} style={{ background: 'rgba(250,246,239,0.06)', borderRadius: 10, padding: '10px 16px', textAlign: 'center', minWidth: 78 }}>
                <div style={{ fontFamily: 'var(--ff-display)', fontSize: 18, fontWeight: 500, color: p.accuracy < 70 ? 'var(--marigold)' : 'var(--cream)' }}>{p.accuracy}%</div>
                <div style={{ fontSize: 9.5, color: 'rgba(250,246,239,0.4)', marginTop: 2 }}>{p.name.split(' ')[0].split('/')[0]}</div>
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Recommendations */}
      <SectionLabel>Optimization recommendations, ranked by impact</SectionLabel>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 4 }}>
        {d.aiRecommendations.map((r, i) => {
          const dot = r.sev === 'high' ? '#b35f18' : r.sev === 'med' ? '#d6b84d' : 'var(--sage)';
          const bdr = r.sev === 'high' ? 'var(--terra)' : r.sev === 'med' ? 'var(--marigold)' : 'rgba(110,140,98,0.45)';
          return (
            <div key={i} className="card" style={{ padding: '13px 16px', display: 'flex', gap: 12, alignItems: 'center', borderColor: bdr, borderWidth: 1.5 }}>
              <div style={{ width: 12, height: 12, borderRadius: '50%', background: dot, flexShrink: 0 }}/>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>
                  {r.title}
                  {r.impact > 0 && <span style={{ fontSize: 11, fontWeight: 700, color: 'var(--sage)', marginLeft: 8 }}>+{r.impact} pts</span>}
                </div>
                <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 2, lineHeight: 1.55 }}>{r.body}</div>
              </div>
              {r.goto ? (
                <button className="btn btn-sm" style={{
                  background: r.sev === 'high' ? 'var(--terra)' : r.sev === 'med' ? 'var(--marigold)' : 'var(--sage)',
                  color: r.sev === 'med' ? '#725900' : '#fff', border: 'none', fontWeight: 700, fontSize: 11,
                }} onClick={() => onTab(r.goto)}>{r.cta}</button>
              ) : (
                <Pill tone="ok">✓ {r.cta}</Pill>
              )}
            </div>
          );
        })}
      </div>

      <SourceNote>Source: Google Business Profile</SourceNote>

      {/* Per-platform table */}
      <SectionLabel>How each AI platform sees you</SectionLabel>
      <div className="card" style={{ overflow: 'hidden' }}>
        <table className="tbl">
          <thead><tr><th>AI Platform</th><th>Primary data sources</th><th style={{ width: 100 }}>Accuracy</th><th>Top gap</th><th>Recommended fix</th></tr></thead>
          <tbody>
            {d.aiPlatforms.map((p, i) => {
              const tone = p.status === 'bad' ? 'bad' : p.status === 'warn' ? 'warn' : 'ok';
              return (
                <tr key={i}>
                  <td className="strong">{p.name}</td>
                  <td className="muted" style={{ fontSize: 11.5 }}>{p.sources}</td>
                  <td><Pill tone={tone}>{p.accuracy}%</Pill></td>
                  <td style={{ fontSize: 11.5, color: p.status === 'bad' ? 'var(--terra)' : 'var(--ink-2)', fontWeight: p.status === 'bad' ? 600 : 400 }}>{p.gap}</td>
                  <td style={{ fontSize: 11.5, color: p.status === 'bad' ? 'var(--terra)' : 'var(--ink-3)', fontWeight: p.status === 'bad' ? 600 : 400 }}>{p.fix}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </>
  );
}

// ====================================================================
// TAB: ANALYTICS
// ====================================================================
function AnalyticsTab({ data, d }) {
  const a = d.analytics;
  const cards = [
    { l: 'Listing views', v: a.views.v.toLocaleString(), delta: a.views.delta },
    { l: 'Direction requests', v: a.directions.v.toLocaleString(), delta: a.directions.delta },
    { l: 'Call clicks', v: a.calls.v.toLocaleString(), delta: a.calls.delta },
    { l: 'Website clicks', v: a.websiteClicks.v.toLocaleString(), delta: a.websiteClicks.delta },
  ];
  const byPub = data.listings.filter(l => l.controlled && l.views).sort((a, b) => b.views - a.views);
  return (
    <>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginBottom: 4 }}>
        {cards.map((c, i) => (
          <div key={i} className="card card-pad">
            <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 6 }}>{c.l}</div>
            <div style={{ fontFamily: 'var(--ff-display)', fontSize: 30, fontWeight: 500, color: 'var(--ink)', lineHeight: 1.1 }}>{c.v}</div>
            <div style={{ fontSize: 11.5, color: 'var(--sage)', fontWeight: 600, marginTop: 4 }}>↑ {c.delta}% vs last month</div>
          </div>
        ))}
      </div>

      <SectionLabel>Top search terms driving listing views</SectionLabel>
      <div className="card" style={{ overflow: 'hidden' }}>
        <table className="tbl">
          <thead><tr><th>Search term</th><th>Platform</th><th style={{ width: 110 }}>Impressions</th><th style={{ width: 110 }}>Actions</th><th style={{ width: 110 }}>Trend</th></tr></thead>
          <tbody>
            {d.topSearchTerms.map((t, i) => (
              <tr key={i}>
                <td className="strong">"{t.term}"</td>
                <td className="muted">{t.platform}</td>
                <td className="mono">{t.impressions.toLocaleString()}</td>
                <td className="mono">{t.actions}</td>
                <td>
                  {t.delta > 0 ? <span style={{ color: 'var(--sage)', fontWeight: 600, fontSize: 12 }}>↑ +{t.delta}%</span> :
                   t.delta < 0 ? <span style={{ color: 'var(--terra)', fontWeight: 600, fontSize: 12 }}>↓ {t.delta}%</span> :
                   <span style={{ color: 'var(--ink-4)', fontWeight: 600, fontSize: 12 }}>→ Flat</span>}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      <SectionLabel>Performance by publisher, this month</SectionLabel>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10 }}>
        {byPub.slice(0, 3).map((p, i) => (
          <div key={i} className="card card-pad">
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)', marginBottom: 10 }}>{p.name}</div>
            <div style={{ fontSize: 11.5, color: 'var(--ink-3)', display: 'flex', flexDirection: 'column', gap: 6 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                <span>Views</span>
                <span style={{ fontWeight: 600, color: 'var(--ink)', fontFamily: 'var(--ff-mono)' }}>{p.views.toLocaleString()}</span>
              </div>
              <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                <span>Actions</span>
                <span style={{ fontWeight: 600, color: 'var(--ink)', fontFamily: 'var(--ff-mono)' }}>{p.actions}</span>
              </div>
              <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                <span>Conversion</span>
                <span style={{ fontWeight: 600, color: 'var(--sage)', fontFamily: 'var(--ff-mono)' }}>{((p.actions / p.views) * 100).toFixed(1)}%</span>
              </div>
            </div>
          </div>
        ))}
      </div>
    </>
  );
}

// ====================================================================
// APPLE BUSINESS CONNECT, sibling module to Google Business Profile.
// Showcases (Apple's posts), content publishing, place-card insights.
// ====================================================================
const AppleMark = ({ size = 18, color = 'currentColor' }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill={color} aria-hidden="true">
    <path d="M17.05 12.04c-.03-2.6 2.12-3.85 2.22-3.91-1.21-1.77-3.09-2.01-3.76-2.04-1.6-.16-3.12.94-3.93.94-.81 0-2.06-.92-3.39-.89-1.74.03-3.35 1.01-4.25 2.57-1.81 3.14-.46 7.78 1.3 10.33.86 1.25 1.88 2.65 3.22 2.6 1.29-.05 1.78-.83 3.34-.83s2 .83 3.37.81c1.39-.03 2.27-1.27 3.12-2.53.98-1.45 1.39-2.85 1.41-2.92-.03-.01-2.7-1.04-2.72-4.12zM14.6 4.6c.71-.86 1.19-2.06 1.06-3.25-1.02.04-2.26.68-2.99 1.54-.66.76-1.23 1.98-1.08 3.15 1.14.09 2.3-.58 3.01-1.44z"/>
  </svg>
);

function ApplePlatformHeader({ ap, onCompose }) {
  const connected = ap.status === 'connected';
  return (
    <div className="card card-pad" style={{
      background: 'linear-gradient(135deg, #2a2a2b 0%, #3d3d3d 100%)',
      color: 'var(--cream)', border: 'none', marginBottom: 16,
      display: 'grid', gridTemplateColumns: 'auto 1fr auto', gap: 18, alignItems: 'center',
    }}>
      <div style={{ width: 52, height: 52, borderRadius: 13, background: 'var(--paper)',
        display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
        <AppleMark size={28} color="#2a2a2b"/>
      </div>
      <div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 3 }}>
          <span style={{ fontFamily: 'var(--ff-display)', fontSize: 18, fontWeight: 500 }}>Apple Business Connect</span>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '2px 9px', borderRadius: 999,
            fontSize: 10.5, fontWeight: 700,
            background: connected ? 'rgba(82,184,148,0.2)' : 'rgba(214,184,77,0.22)',
            color: connected ? '#9cd4b4' : '#d6b84d' }}>
            <span style={{ width: 6, height: 6, borderRadius: '50%', background: connected ? '#0d8a8a' : '#d6b84d' }}/>
            {connected ? 'Connected' : 'Verification pending'}
          </span>
          <span style={{ fontSize: 10.5, color: 'rgba(250,246,239,0.4)', padding: '2px 8px', borderRadius: 999,
            border: '1px solid rgba(250,246,239,0.16)' }}>Direct API · we control</span>
        </div>
        <div style={{ fontSize: 12.5, color: 'rgba(250,246,239,0.6)' }}>
          {ap.placeCard} · Apple ID {ap.appleId} · {connected ? `Synced ${ap.lastSync}` : ap.lastSync}
        </div>
      </div>
      <div style={{ display: 'flex', gap: 8 }}>
        <button className="btn btn-sm" style={{ background: 'var(--paper)', color: '#2a2a2b', border: 'none', fontWeight: 700 }}
          onClick={onCompose} disabled={!connected}>+ Compose Showcase</button>
      </div>
    </div>
  );
}

function AppleTab({ apple }) {
  const ap = apple;
  const [compose, setCompose] = useState(false);
  const connected = ap.status === 'connected';
  const ins = ap.insights;
  const cards = [
    { l: ins.views.label, v: ins.views.v, delta: ins.views.delta },
    { l: ins.directions.label, v: ins.directions.v, delta: ins.directions.delta },
    { l: ins.calls.label, v: ins.calls.v, delta: ins.calls.delta },
    { l: ins.website.label, v: ins.website.v, delta: ins.website.delta },
  ];
  const statusTone = { live: 'ok', scheduled: 'info', draft: 'neutral' };
  const statusLabel = { live: '● Live', scheduled: '◔ Scheduled', draft: '○ Draft' };

  return (
    <>
      <div style={{ background: 'var(--sky-soft)', border: '1px solid var(--sky)', borderRadius: 12,
        padding: '11px 16px', marginBottom: 16, fontSize: 12, color: 'var(--sky)', display: 'flex', alignItems: 'center', gap: 10 }}>
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
        <span><strong>Apple Business Connect is managed here, full parity with Google.</strong> Showcases publish to Apple Maps, Spotlight &amp; Siri. Content edits sync within minutes.</span>
      </div>

      <ApplePlatformHeader ap={ap} onCompose={() => setCompose(true)}/>

      {!connected && (
        <div style={{ background: 'var(--marigold-soft)', border: '1px solid var(--marigold)', borderRadius: 12,
          padding: '12px 16px', marginBottom: 16, fontSize: 12.5, color: '#725900', display: 'flex', alignItems: 'center', gap: 12 }}>
          <span style={{ fontWeight: 700 }}>⚠ Claim pending</span>
          <span style={{ flex: 1, color: '#ad8406' }}>Apple is verifying ownership of this place card. Showcases unlock once approved.</span>
          <button className="btn btn-sm" style={{ background: 'var(--marigold)', color: '#725900', border: 'none' }}
            onClick={() => T('Verification nudged', 'Apple usually confirms within 3–5 business days.', 'info')}>Check status</button>
        </div>
      )}

      <SourceNote>Calculated by LOCALACT · AI-visibility index</SourceNote>

      {/* Insights, parity with Google insights */}
      <SectionLabel count="Last 30 days">Apple insights</SectionLabel>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginBottom: 4 }}>
        {cards.map((c, i) => (
          <div key={i} className="card card-pad">
            <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 6 }}>{c.l}</div>
            <div style={{ fontFamily: 'var(--ff-display)', fontSize: 30, fontWeight: 500, color: 'var(--ink)', lineHeight: 1.1 }}>{c.v.toLocaleString()}</div>
            {c.delta > 0
              ? <div style={{ fontSize: 11.5, color: 'var(--sage)', fontWeight: 600, marginTop: 4 }}>↑ {c.delta}% vs last month</div>
              : <div style={{ fontSize: 11.5, color: 'var(--ink-4)', fontWeight: 600, marginTop: 4 }}>→ New this month</div>}
          </div>
        ))}
      </div>

      <SourceNote>Source: Apple Business Connect</SourceNote>

      {/* Showcases, parity with Google Posts */}
      <SectionLabel count={`${ap.showcases.filter(s => s.status === 'live').length} live · ${ap.showcases.filter(s => s.status === 'scheduled').length} scheduled`}>
        Showcases &amp; posts
      </SectionLabel>
      {ap.showcases.length === 0 ? (
        <div className="card card-pad" style={{ textAlign: 'center', color: 'var(--ink-4)', fontSize: 13, padding: '32px 20px' }}>
          No Showcases yet. {connected ? 'Compose your first one to appear in Apple Maps & Spotlight.' : 'Available once your place card is verified.'}
        </div>
      ) : (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 12 }}>
          {ap.showcases.map((s) => (
            <div key={s.id} className="card" style={{ overflow: 'hidden', display: 'flex' }}>
              <div style={{ width: 92, flexShrink: 0, background: s.grad, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 30 }}>{s.emoji}</div>
              <div style={{ flex: 1, padding: '12px 14px' }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, marginBottom: 4 }}>
                  <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)' }}>{s.title}</div>
                  <Pill tone={statusTone[s.status]} size="xs">{statusLabel[s.status]}</Pill>
                </div>
                <div style={{ fontSize: 11.5, color: 'var(--ink-3)', lineHeight: 1.5, marginBottom: 8,
                  display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{s.body}</div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, fontSize: 10.5, color: 'var(--ink-4)', fontFamily: 'var(--ff-mono)' }}>
                  <span>{s.cta}</span>
                  <span>·</span>
                  <span>{s.start}{s.end && s.end !== '–' ? `–${s.end}` : ''}</span>
                  {s.status === 'live' && <><span>·</span><span>{s.views.toLocaleString()} views</span><span>{s.taps} taps</span></>}
                </div>
                {s.alsoGoogle && (
                  <div style={{ marginTop: 8, display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 10, fontWeight: 600, color: 'var(--sky)', background: 'var(--sky-soft)', padding: '2px 8px', borderRadius: 999 }}>
                    ⇄ Cross-posted to Google Posts
                  </div>
                )}
              </div>
            </div>
          ))}
        </div>
      )}

      {/* Content management */}
      <SectionLabel count={`${ap.content.publishedFields}/${ap.content.totalFields} fields`}>Content, publishes to Apple Maps</SectionLabel>
      <div style={{ display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: 12 }}>
        <div className="card card-pad">
          <div style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--ink)', marginBottom: 12, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
            <span>Listing info &amp; hours</span>
            <button className="btn btn-quiet btn-xs" onClick={() => window.dispatchEvent(new CustomEvent('la-listings-tab', { detail: 'edit' }))}>Edit in source of truth →</button>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: '90px 1fr 1fr', gap: 6, alignItems: 'center', fontSize: 12 }}>
            <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.06em' }}>Day</div>
            <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.06em' }}>Opens</div>
            <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.06em' }}>Closes</div>
            {ap.content.hours.map((h, i) => (
              <React.Fragment key={i}>
                <div style={{ color: 'var(--ink-2)' }}>{h.day}</div>
                <div style={{ color: 'var(--ink-2)', fontFamily: 'var(--ff-mono)' }}>{h.open}</div>
                <div style={{ color: 'var(--ink-2)', fontFamily: 'var(--ff-mono)' }}>{h.close}</div>
              </React.Fragment>
            ))}
          </div>
          <div style={{ marginTop: 12, paddingTop: 12, borderTop: '1px solid var(--cream-2)', fontSize: 12, color: 'var(--ink-3)' }}>
            <span style={{ fontWeight: 600, color: 'var(--ink-2)' }}>Categories:</span> {ap.content.categories}
          </div>
        </div>
        <div className="card card-pad">
          <div style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--ink)', marginBottom: 12 }}>Apple action links</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {ap.content.actionLinks.map((a, i) => (
              <div key={i} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, padding: '8px 10px', background: 'var(--cream)', borderRadius: 8 }}>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink)' }}>{a.label}</div>
                  <div style={{ fontSize: 11, color: 'var(--ink-4)', fontFamily: 'var(--ff-mono)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{a.val}</div>
                </div>
                <Pill tone={a.live ? 'ok' : 'neutral'} size="xs">{a.live ? 'Live' : 'Off'}</Pill>
              </div>
            ))}
          </div>
          <div style={{ marginTop: 12, background: 'var(--sage-soft)', borderRadius: 10, padding: '10px 12px', display: 'flex', gap: 10, alignItems: 'flex-start' }}>
            <div style={{ width: 28, height: 28, borderRadius: '50%', overflow: 'hidden', flexShrink: 0, border: '1.5px solid var(--sage)', background: 'var(--paper)' }}>
              <window.LennyLlama size={28} mood="happy" bg="circle"/>
            </div>
            <div style={{ fontSize: 11.5, color: 'var(--ink-2)', lineHeight: 1.55 }}>{ap.content.note}</div>
          </div>
        </div>
      </div>

      {/* Compose modal (mock) */}
      <window.Modal open={compose} onClose={() => setCompose(false)} title="Compose Apple Showcase" sub="Publishes to Apple Maps, Spotlight & Siri"
        footer={<>
          <button className="btn btn-ghost btn-sm" onClick={() => setCompose(false)}>Cancel</button>
          <button className="btn btn-sm" style={{ background: '#2a2a2b', color: 'var(--cream)', border: 'none', fontWeight: 700 }}
            onClick={() => { setCompose(false); T('Showcase scheduled', 'Live on Apple Maps within minutes · also queued to Google Posts.', 'ok'); }}>Schedule Showcase →</button>
        </>}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          <div>
            <label style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.06em', display: 'block', marginBottom: 4 }}>Headline</label>
            <input defaultValue="" placeholder="e.g. Weekend Deep-Clean Special" style={{ width: '100%', padding: '9px 11px', fontSize: 13, fontFamily: 'var(--ff-ui)', border: '1px solid var(--cream-3)', borderRadius: 8, boxSizing: 'border-box', outline: 'none' }}/>
          </div>
          <div>
            <label style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.06em', display: 'block', marginBottom: 4 }}>Body</label>
            <textarea rows={3} placeholder="What's the offer? Keep it under 160 characters for Apple Maps." style={{ width: '100%', padding: '9px 11px', fontSize: 13, fontFamily: 'var(--ff-ui)', border: '1px solid var(--cream-3)', borderRadius: 8, boxSizing: 'border-box', outline: 'none', resize: 'none', lineHeight: 1.5 }}/>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
            <div>
              <label style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.06em', display: 'block', marginBottom: 4 }}>Call to action</label>
              <select style={{ width: '100%', padding: '9px 11px', fontSize: 13, fontFamily: 'var(--ff-ui)', border: '1px solid var(--cream-3)', borderRadius: 8, background: 'var(--paper)' }}>
                <option>Order</option><option>Reserve</option><option>Learn more</option><option>Call</option>
              </select>
            </div>
            <div>
              <label style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.06em', display: 'block', marginBottom: 4 }}>Go live</label>
              <input type="text" defaultValue="Now" style={{ width: '100%', padding: '9px 11px', fontSize: 13, fontFamily: 'var(--ff-ui)', border: '1px solid var(--cream-3)', borderRadius: 8, boxSizing: 'border-box', outline: 'none' }}/>
            </div>
          </div>
          <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5, color: 'var(--ink-2)', cursor: 'pointer', padding: '8px 10px', background: 'var(--sky-soft)', borderRadius: 8 }}>
            <input type="checkbox" defaultChecked/> Also publish to Google Posts (parity)
          </label>
        </div>
      </window.Modal>
    </>
  );
}

// ====================================================================
// LOCAL IMAGE LIBRARY (DK-06), shared by franchisee + HQ scopes.
// ====================================================================
function SurfaceChips({ liveOn, surfaces }) {
  const keys = Object.keys(surfaces);
  return (
    <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
      {keys.map(k => {
        const on = liveOn.includes(k);
        const s = surfaces[k];
        return (
          <span key={k} title={`${s.label}: ${on ? 'live' : 'not published'}`} style={{
            display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 7px', borderRadius: 999,
            fontSize: 9.5, fontWeight: 700, letterSpacing: '.02em',
            background: on ? 'var(--cream)' : 'transparent',
            color: on ? s.color : 'var(--ink-5)',
            border: on ? `1px solid ${s.color}` : '1px dashed var(--cream-3)',
            opacity: on ? 1 : 0.65,
          }}>
            <span style={{ width: 5, height: 5, borderRadius: '50%', background: on ? s.color : 'var(--ink-5)' }}/>
            {s.label}
          </span>
        );
      })}
    </div>
  );
}

// Creative category + lifecycle metadata
const CAT_META = {
  evergreen: { label: 'Evergreen', color: 'var(--sage)',   soft: 'var(--sage-soft)' },
  seasonal:  { label: 'Seasonal',  color: '#917200',        soft: 'var(--marigold-soft)' },
  holiday:   { label: 'Holiday',   color: 'var(--violet)',  soft: 'var(--violet-soft, rgba(77,105,137,0.12))' },
};
const STATUS_META = {
  expired:  { label: 'Expired',  color: 'var(--terra)',  soft: 'var(--terra-soft)' },
  archived: { label: 'Archived', color: 'var(--ink-4)',  soft: 'var(--cream-2)' },
};

function ImageCard({ img, surfaces, scope, onPush }) {
  const isBrand = img.scope === 'brand';
  const cat = CAT_META[img.category] || CAT_META.evergreen;
  const retired = img.status === 'expired' || img.status === 'archived';
  const st = STATUS_META[img.status];

  return (
    <div className="card" style={{ overflow: 'hidden', display: 'flex', flexDirection: 'column', opacity: retired ? 0.82 : 1 }}>
      <div style={{ aspectRatio: '4/3', background: img.grad, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 40, position: 'relative', filter: retired ? 'grayscale(0.35)' : 'none' }}>
        <span style={{ position: 'absolute', top: 8, left: 8, display: 'flex', gap: 5, flexWrap: 'wrap' }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 8px', borderRadius: 999, fontSize: 9.5, fontWeight: 700,
            background: isBrand ? 'var(--brand-navy)' : 'rgba(255,255,255,0.92)', color: isBrand ? 'var(--cream)' : 'var(--ink-2)' }}>
            {isBrand ? ' Brand' : ' ' + (img.location || 'Local')}
          </span>
        </span>
        <span style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 5, flexDirection: 'column', alignItems: 'flex-end' }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 8px', borderRadius: 999, fontSize: 9.5, fontWeight: 700, background: cat.soft, color: cat.color }}>
            {cat.label}
          </span>
          {retired && <span style={{ padding: '2px 8px', borderRadius: 999, fontSize: 9.5, fontWeight: 700, background: st.soft, color: st.color }}>{st.label}</span>}
          {!retired && img.upcoming && <span style={{ padding: '2px 8px', borderRadius: 999, fontSize: 9.5, fontWeight: 700, background: 'rgba(255,255,255,0.92)', color: 'var(--ink-2)' }}>Scheduled</span>}
        </span>
      </div>
      <div style={{ padding: '11px 13px', display: 'flex', flexDirection: 'column', gap: 8, flex: 1 }}>
        <div>
          <div style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--ink)' }}>{img.name}</div>
          <div style={{ fontSize: 10.5, color: 'var(--ink-4)', marginTop: 1 }}>
            {img.owner} · {img.added}{img.pushedTo ? ` · ${img.pushedTo}` : ''}
          </div>
          {img.window && (
            <div style={{ fontSize: 10.5, fontWeight: 700, color: retired ? st.color : cat.color, marginTop: 3, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
              {img.window}
            </div>
          )}
        </div>
        <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
          {img.tags.map((t, i) => <Pill key={i} tone="neutral" size="xs">{t}</Pill>)}
        </div>
        {!retired && (
          <div>
            <div style={{ fontSize: 9, fontWeight: 700, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 4 }}>
              {img.upcoming ? 'Publishes on launch' : 'Live on'}
            </div>
            <SurfaceChips liveOn={img.liveOn} surfaces={surfaces}/>
          </div>
        )}
        <div style={{ marginTop: 'auto', paddingTop: 8, borderTop: '1px solid var(--cream-2)', display: 'flex', gap: 6 }}>
          {retired ? (
            <>
              <button className="btn btn-ghost btn-xs" style={{ flex: 1 }} onClick={() => T('Restored', `"${img.name}" moved back to active creative.`, 'ok')}>Restore</button>
              <button className="btn btn-quiet btn-xs" onClick={() => T('Duplicated', `Created an editable copy of "${img.name}".`, 'info')}>Duplicate</button>
            </>
          ) : scope === 'hq' && isBrand ? (
            <>
              <button className="btn btn-primary btn-xs" style={{ flex: 1 }} onClick={() => onPush(img)}>{img.upcoming ? 'Schedule push →' : 'Push to locations →'}</button>
              <button className="btn btn-quiet btn-xs" onClick={() => T('Archive', `Move "${img.name}" to Expired & archived?`, 'info')}>Archive</button>
            </>
          ) : scope === 'franchisee' && isBrand ? (
            <span style={{ fontSize: 10.5, color: 'var(--ink-4)', display: 'flex', alignItems: 'center', gap: 5, padding: '4px 0' }}>
              🔒 From corporate
            </span>
          ) : (
            <>
              <button className="btn btn-ghost btn-xs" style={{ flex: 1 }} onClick={() => T('Manage surfaces', `Choose where "${img.name}" publishes, GBP, Apple, social, website.`, 'info')}>Manage surfaces</button>
              <button className="btn btn-quiet btn-xs" onClick={() => T('Archive', `Move "${img.name}" to Expired & archived?`, 'info')}>Archive</button>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

function LibrarySection({ title, hint, accent, images, surfaces, scope, onPush, uploadTile }) {
  if (!images.length && !uploadTile) return null;
  return (
    <div style={{ marginBottom: 26 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
        {accent && <span style={{ width: 8, height: 8, borderRadius: 3, background: accent }}/>}
        <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)' }}>{title}</span>
        <span style={{ fontFamily: 'var(--ff-mono)', fontSize: 11.5, color: 'var(--ink-4)', background: 'var(--cream)', padding: '1px 7px', borderRadius: 999 }}>{images.length}</span>
        {hint && <span style={{ fontSize: 11.5, color: 'var(--ink-4)' }}>{hint}</span>}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 }}>
        {uploadTile}
        {images.map(img => <ImageCard key={img.id} img={img} surfaces={surfaces} scope={scope} onPush={onPush}/>)}
      </div>
    </div>
  );
}

function ImageLibrary({ scope, data }) {
  const lib = data.imageLibrary || { images: [], surfaces: {} };
  const surfaces = lib.surfaces;
  const thisLocation = data.variant === 'bare' ? 'Plano Legacy' : 'Austin Downtown';
  const [scopeFilter, setScopeFilter] = useState('all'); // all | brand | local
  const [q, setQ] = useState('');
  const [showRetired, setShowRetired] = useState(false);
  const [pushImg, setPushImg] = useState(null);

  // Scope the pool: franchisee sees brand assets + their own location's images.
  let pool = lib.images;
  if (scope === 'franchisee') pool = pool.filter(i => i.scope === 'brand' || i.location === thisLocation);

  const matches = (i) => {
    if (scopeFilter === 'brand' && i.scope !== 'brand') return false;
    if (scopeFilter === 'local' && i.scope !== 'location') return false;
    if (q.trim()) {
      const n = q.trim().toLowerCase();
      if (!(i.name.toLowerCase().includes(n) || i.tags.join(' ').toLowerCase().includes(n) || (i.location || '').toLowerCase().includes(n))) return false;
    }
    return true;
  };

  const visible = pool.filter(matches);
  const active = visible.filter(i => i.status === 'active');
  const evergreen = active.filter(i => i.category === 'evergreen');
  const seasonal = active.filter(i => i.category === 'seasonal');
  const holiday = active.filter(i => i.category === 'holiday');
  const retired = visible.filter(i => i.status === 'expired' || i.status === 'archived');
  const expiredN = retired.filter(i => i.status === 'expired').length;
  const archivedN = retired.filter(i => i.status === 'archived').length;

  const brandCount = pool.filter(i => i.scope === 'brand').length;
  const localCount = pool.filter(i => i.scope === 'location').length;
  const chips = [
    { v: 'all', l: 'All', n: pool.length },
    { v: 'brand', l: 'Brand / national', n: brandCount },
    { v: 'local', l: scope === 'franchisee' ? 'My location' : 'Location', n: localCount },
  ];

  const uploadTile = (
    <div onClick={() => T(scope === 'hq' ? 'Upload brand image' : 'Upload image', 'Drag images here or browse, mock.', 'info')}
      style={{ aspectRatio: '4/3', border: '2px dashed var(--cream-3)', borderRadius: 12, display: 'flex', flexDirection: 'column',
        alignItems: 'center', justifyContent: 'center', gap: 6, cursor: 'pointer', color: 'var(--ink-4)', background: 'var(--cream)' }}>
      <svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
      <span style={{ fontSize: 11.5, fontWeight: 600 }}>Upload {scope === 'hq' ? 'brand' : 'local'} image</span>
    </div>
  );

  return (
    <>
      {/* Filter + actions bar */}
      <div style={{ display: 'flex', gap: 8, marginBottom: 18, flexWrap: 'wrap', alignItems: 'center' }}>
        <div style={{ display: 'flex', gap: 6 }}>
          {chips.map(c => (
            <button key={c.v} onClick={() => setScopeFilter(c.v)} style={{
              padding: '6px 12px', borderRadius: 999, fontSize: 11.5, fontWeight: 600, cursor: 'pointer',
              border: scopeFilter === c.v ? '1px solid var(--ink-3)' : '1px solid var(--cream-3)',
              background: scopeFilter === c.v ? 'var(--ink)' : 'var(--paper)',
              color: scopeFilter === c.v ? 'var(--paper)' : 'var(--ink-3)',
            }}>{c.l} <span style={{ fontFamily: 'var(--ff-mono)', opacity: 0.7, marginLeft: 3 }}>{c.n}</span></button>
          ))}
        </div>
        <input value={q} onChange={e => setQ(e.target.value)} placeholder="Search by name or tag…"
          style={{ flex: 1, minWidth: 200, padding: '7px 12px', border: '1px solid var(--cream-3)', borderRadius: 8, fontSize: 12, fontFamily: 'var(--ff-ui)', background: 'var(--paper)' }}/>
        <button className="btn btn-primary btn-sm" onClick={() => T(scope === 'hq' ? 'Upload brand image' : 'Upload image', 'File picker would open here, drag & drop supported.', 'info')}>
          + Upload {scope === 'hq' ? 'brand image' : 'image'}
        </button>
      </div>

      {active.length === 0 && (
        <div style={{ padding: '32px 20px', textAlign: 'center', color: 'var(--ink-4)', fontSize: 13, border: '1px dashed var(--cream-3)', borderRadius: 12, marginBottom: 20 }}>
          No active creative matches your filters.
        </div>
      )}

      <LibrarySection title="Evergreen" hint="Always-on brand & location creative" accent={CAT_META.evergreen.color}
        images={evergreen} surfaces={surfaces} scope={scope} onPush={setPushImg} uploadTile={scopeFilter !== 'brand' || scope === 'hq' ? uploadTile : null}/>
      <LibrarySection title="Seasonal & upcoming" hint="Scheduled to go live with the season" accent={CAT_META.seasonal.color}
        images={seasonal} surfaces={surfaces} scope={scope} onPush={setPushImg}/>
      <LibrarySection title="Holiday" hint="Tied to holidays & key dates" accent={CAT_META.holiday.color}
        images={holiday} surfaces={surfaces} scope={scope} onPush={setPushImg}/>

      {/* Expired & archived */}
      {retired.length > 0 && (
        <div style={{ borderTop: '1px solid var(--cream-2)', paddingTop: 16, marginTop: 4 }}>
          <button onClick={() => setShowRetired(s => !s)} style={{
            display: 'flex', alignItems: 'center', gap: 8, width: '100%', background: 'none', border: 'none', cursor: 'pointer', padding: 0, marginBottom: showRetired ? 14 : 0,
          }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--ink-3)" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" style={{ transform: showRetired ? 'rotate(90deg)' : 'none', transition: 'transform .15s' }}><path d="M9 6l6 6-6 6"/></svg>
            <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)' }}>Expired & archived</span>
            <span style={{ fontFamily: 'var(--ff-mono)', fontSize: 11.5, color: 'var(--ink-4)', background: 'var(--cream)', padding: '1px 7px', borderRadius: 999 }}>{retired.length}</span>
            <span style={{ fontSize: 11.5, color: 'var(--ink-4)' }}>{expiredN} expired · {archivedN} archived</span>
          </button>
          {showRetired && (
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 }}>
              {retired.map(img => <ImageCard key={img.id} img={img} surfaces={surfaces} scope={scope} onPush={setPushImg}/>)}
            </div>
          )}
        </div>
      )}

      {/* Push to locations modal (corporate) */}
      <PushToLocationsModal img={pushImg} data={data} onClose={() => setPushImg(null)}/>
    </>
  );
}

window.ImageLibrary = ImageLibrary;

function PushToLocationsModal({ img, data, onClose }) {
  const total = (data.appleCoverage && data.appleCoverage.total) || 24;
  const [scope, setScope] = useState('all');
  if (!img) return null;
  const count = scope === 'all' ? total : scope === 'flagship' ? 6 : 3;
  return (
    <window.Modal open={!!img} onClose={onClose} title={`Push "${img.name}"`} sub="Distribute this brand image to franchisee libraries & surfaces"
      footer={<>
        <button className="btn btn-ghost btn-sm" onClick={onClose}>Cancel</button>
        <button className="btn btn-primary btn-sm" onClick={() => { onClose(); T('Image pushed', `"${img.name}" sent to ${count} location${count === 1 ? '' : 's'} · publishing to selected surfaces.`, 'ok'); }}>Push to {count} location{count === 1 ? '' : 's'} →</button>
      </>}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
          <div style={{ width: 72, height: 54, borderRadius: 8, background: img.grad, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 26, flexShrink: 0 }}>{img.emoji}</div>
          <div>
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)' }}>{img.name}</div>
            <div style={{ fontSize: 11.5, color: 'var(--ink-4)' }}>Currently on {img.pushedTo || '–'}</div>
          </div>
        </div>
        <div>
          <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 6 }}>Which locations</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {[['all', `All locations (${total})`], ['flagship', 'Flagship cohort (6)'], ['select', 'Select locations (3)']].map(([v, l]) => (
              <label key={v} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 10, cursor: 'pointer',
                border: scope === v ? '2px solid var(--brand-navy)' : '1px solid var(--cream-3)' }}>
                <input type="radio" checked={scope === v} onChange={() => setScope(v)}/>
                <span style={{ fontSize: 12.5, color: 'var(--ink)' }}>{l}</span>
              </label>
            ))}
          </div>
        </div>
        <div>
          <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 6 }}>Publish to surfaces</div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {Object.keys(data.imageLibrary.surfaces).map(k => (
              <label key={k} style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--ink-2)', padding: '6px 10px', background: 'var(--cream)', borderRadius: 8, cursor: 'pointer' }}>
                <input type="checkbox" defaultChecked={['gbp', 'apple'].includes(k)}/> {data.imageLibrary.surfaces[k].label}
              </label>
            ))}
          </div>
        </div>
      </div>
    </window.Modal>
  );
}

// ====================================================================
// HQ, Apple Business Connect coverage rollup across all locations
// ====================================================================
function HqAppleCoverage({ data }) {
  const cov = data.appleCoverage || {};
  const rollup = data.hqRollup || [];
  const seedHash = (str) => { let h = 0; for (let i = 0; i < str.length; i++) h = ((h << 5) - h) + str.charCodeAt(i); return Math.abs(h); };
  const appleStatusFor = (unit) => {
    const n = seedHash(unit + 'apple');
    if (n % 11 === 0) return 'Unclaimed';
    if (n % 5 === 0) return 'Pending';
    return 'Connected';
  };
  const derived = rollup.map(r => {
    const st = appleStatusFor(r.unit);
    const n = seedHash(r.unit + 'apple');
    return {
      unit: r.unit, region: r.region, city: r.city, status: st,
      showcases: st === 'Connected' ? n % 4 : 0,
      views: st === 'Connected' ? 2000 + (n % 8000) : st === 'Pending' ? n % 600 : 0,
    };
  });
  const tone = { Connected: { fg: 'var(--sage)', bg: 'var(--sage-soft)' }, Pending: { fg: '#917200', bg: 'var(--marigold-soft)' }, Unclaimed: { fg: 'var(--terra)', bg: 'var(--terra-soft)' } };
  const [statusFilter, setStatusFilter] = useState('all');
  const rows = derived.filter(d => statusFilter === 'all' || d.status === statusFilter)
    .sort((a, b) => b.views - a.views);

  const summary = [
    { l: 'Connected', v: cov.connected, sub: `of ${cov.total} locations`, c: 'var(--sage)' },
    { l: 'Verification pending', v: cov.pending, sub: 'Apple reviewing', c: 'var(--marigold)' },
    { l: 'Showcases live', v: cov.showcasesLive, sub: 'across the brand', c: 'var(--ink)' },
    { l: 'Place-card views', v: (cov.views30d || 0).toLocaleString(), sub: `↑ ${cov.viewsDelta}% · 30d`, c: 'var(--violet)' },
  ];

  return (
    <>
      <div className="card card-pad" style={{ background: 'linear-gradient(135deg, #2a2a2b 0%, #3d3d3d 100%)', color: 'var(--cream)', border: 'none', marginBottom: 16, display: 'flex', alignItems: 'center', gap: 16 }}>
        <div style={{ width: 48, height: 48, borderRadius: 12, background: 'var(--paper)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
          <AppleMark size={26} color="#2a2a2b"/>
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontFamily: 'var(--ff-display)', fontSize: 18, fontWeight: 500 }}>Apple Business Connect coverage</div>
          <div style={{ fontSize: 12.5, color: 'rgba(250,246,239,0.6)' }}>{cov.connected}/{cov.total} locations live · {cov.parityGoogle}% at parity with Google · directories we control</div>
        </div>
        <button className="btn btn-sm" style={{ background: 'var(--paper)', color: '#2a2a2b', border: 'none', fontWeight: 700 }}
          onClick={() => T('Claiming queued', `Submitting ${cov.unclaimed + cov.pending} unclaimed/pending place cards to Apple for verification.`, 'ok')}>Claim remaining →</button>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginBottom: 4 }}>
        {summary.map((s, i) => (
          <div key={i} className="card card-pad">
            <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 6 }}>{s.l}</div>
            <div style={{ fontFamily: 'var(--ff-display)', fontSize: 30, fontWeight: 500, color: s.c, lineHeight: 1.1 }}>{s.v}</div>
            <div style={{ fontSize: 11.5, color: 'var(--ink-4)', marginTop: 4 }}>{s.sub}</div>
          </div>
        ))}
      </div>

      <div style={{ display: 'flex', gap: 6, margin: '18px 0 10px' }}>
        {['all', 'Connected', 'Pending', 'Unclaimed'].map(s => (
          <button key={s} onClick={() => setStatusFilter(s)} style={{
            padding: '6px 12px', borderRadius: 999, fontSize: 11.5, fontWeight: 600, cursor: 'pointer',
            border: statusFilter === s ? '1px solid var(--ink-3)' : '1px solid var(--cream-3)',
            background: statusFilter === s ? 'var(--ink)' : 'var(--paper)',
            color: statusFilter === s ? 'var(--paper)' : 'var(--ink-3)',
          }}>{s === 'all' ? 'All locations' : s}</button>
        ))}
      </div>

      <div className="card" style={{ overflow: 'hidden' }}>
        <table className="tbl">
          <thead><tr><th>Location</th><th>Region</th><th>Apple status</th><th style={{ width: 110 }}>Showcases</th><th style={{ width: 130 }}>Place-card views</th><th/></tr></thead>
          <tbody>
            {rows.map((r, i) => (
              <tr key={i}>
                <td className="strong">{r.unit}</td>
                <td className="muted">{r.region}</td>
                <td>
                  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '2px 9px', borderRadius: 999, fontSize: 10.5, fontWeight: 700, background: tone[r.status].bg, color: tone[r.status].fg }}>
                    <span style={{ width: 6, height: 6, borderRadius: '50%', background: tone[r.status].fg }}/>{r.status}
                  </span>
                </td>
                <td className="mono">{r.status === 'Connected' ? r.showcases : '–'}</td>
                <td className="mono">{r.views ? r.views.toLocaleString() : '–'}</td>
                <td>
                  {r.status === 'Connected'
                    ? <button className="btn btn-quiet btn-xs" onClick={() => T(r.unit, 'Opening Apple place card · showcases, content, insights.', 'info')}>View</button>
                    : <button className="btn btn-quiet btn-xs" onClick={() => T(`${r.unit} · claim`, 'Submitting to Apple for verification.', 'info')}>Claim</button>}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </>
  );
}

// ====================================================================
// MAIN
// ====================================================================
function ListingsPage({ data, persona }) {
  if (persona === 'hq') return <ListingsPageHq data={data}/>;
  return <ListingsPageFranchisee data={data}/>;
}
window.ListingsPage = ListingsPage;

// ====================================================================
// PORTFOLIO GENERATOR, scale the 24 curated locations up to the full
// 700-location FreshNest footprint the corporate story describes.
// The 24 hand-authored rows (used by Leads / Results / Portfolio) are
// preserved verbatim and lead the list; the remaining ~676 are generated
// deterministically (stable across reloads) so corporate rollups,
// regional breakdowns, and the locations table reflect all 700.
// ====================================================================
// Concept-level marketing-readiness signal (Ready / Needs attention / Blocked).
// Deterministic per location, loosely correlated with score so it reads
// realistically — most Ready, some Needs attention, a few Blocked. No
// validation engine behind it; it is a canned status label only.
function marketingReadinessFor(unit, score) {
  let x = 0; const s = unit + '~mr'; for (let i = 0; i < s.length; i++) x = ((x << 5) - x) + s.charCodeAt(i);
  const r = Math.abs(x) % 100;
  if (score < 64)  return r < 22 ? 'blocked' : r < 72 ? 'attention' : 'ready';
  if (score < 72)  return r < 5  ? 'blocked' : r < 42 ? 'attention' : 'ready';
  if (score < 82)  return r < 16 ? 'attention' : 'ready';
  return r < 6 ? 'attention' : 'ready';
}
window.marketingReadinessFor = marketingReadinessFor;

function buildPortfolio(base, target = 700) {
  const h = (s) => { let x = 0; for (let i = 0; i < s.length; i++) x = ((x << 5) - x) + s.charCodeAt(i); return Math.abs(x); };
  const real = base.map(r => ({ ...r, readiness: marketingReadinessFor(r.unit, r.score) }));

  // Metros to spread synthetic locations across, by region.
  const metros = {
    Central:   ['Austin, TX','Dallas, TX','Houston, TX','San Antonio, TX','Fort Worth, TX','El Paso, TX','Oklahoma City, OK','Tulsa, OK','Wichita, KS','Omaha, NE','New Orleans, LA','Baton Rouge, LA','Little Rock, AR'],
    Southeast: ['Atlanta, GA','Savannah, GA','Charlotte, NC','Raleigh, NC','Nashville, TN','Memphis, TN','Jacksonville, FL','Orlando, FL','Miami, FL','Tampa, FL','Charleston, SC','Columbia, SC','Birmingham, AL','Louisville, KY'],
    West:      ['Phoenix, AZ','Tucson, AZ','Denver, CO','Colorado Springs, CO','Las Vegas, NV','Reno, NV','Salt Lake City, UT','Boise, ID','Portland, OR','Seattle, WA','Albuquerque, NM','Sacramento, CA','San Diego, CA'],
    Midwest:   ['Chicago, IL','Detroit, MI','Grand Rapids, MI','Minneapolis, MN','Kansas City, MO','St. Louis, MO','Milwaukee, WI','Madison, WI','Indianapolis, IN','Columbus, OH','Cleveland, OH','Cincinnati, OH','Des Moines, IA'],
    Northeast: ['New York, NY','Brooklyn, NY','Buffalo, NY','Boston, MA','Philadelphia, PA','Pittsburgh, PA','Newark, NJ','Jersey City, NJ','Hartford, CT','Providence, RI','Baltimore, MD','Washington, DC','Portland, ME'],
  };
  const hoods = ['Downtown','Northgate','Westfield','Eastside','Midtown','Heights','Riverside','Crossing','Commons','Marketplace','Town Center','Plaza','Junction','Square','Landing','Gateway','Promenade','Village','Station','Harbor','Ridge','Summit','Meadows','Uptown','Old Town','Lakeside','Pointe','Terrace','Parkway','Crossroads'];
  const labelsByTier = { star: ['Flagship','Urban'], mid: ['Suburban'], low: ['Suburban'] };

  // Target headcount per region (sums to `target`).
  const targetByRegion = { Central: 150, Southeast: 160, West: 130, Midwest: 140, Northeast: 120 };

  const out = [...real];
  const used = new Set(real.map(r => r.unit));
  const regionCount = (rg) => out.filter(r => r.region === rg).length;

  Object.keys(targetByRegion).forEach(region => {
    const cities = metros[region];
    let need = targetByRegion[region] - regionCount(region);
    let i = 0;
    while (need > 0) {
      const city = cities[i % cities.length];
      const cityShort = city.split(',')[0];
      const hood = hoods[(h(region) + i * 7) % hoods.length];
      let unit = `${cityShort} ${hood}`;
      let dup = 2;
      while (used.has(unit)) { unit = `${cityShort} ${hood} ${dup++}`; }
      used.add(unit);

      const seed = h(unit);
      // Score distribution skewed healthy with a meaningful long tail.
      const roll = seed % 100;
      const score = roll < 58 ? 80 + (seed % 15)      // 58% healthy 80–94
                  : roll < 84 ? 68 + (seed % 12)      // 26% watch  68–79
                              : 52 + (seed % 16);     // 16% at-risk 52–67
      const tier = score >= 84 ? 'star' : score >= 68 ? 'mid' : 'low';
      const status = score < 68 ? 'attention' : (score >= 90 && (seed % 4 === 0) ? 'star' : 'on-track');
      const rating = Math.round((3.4 + (score - 52) / 43 * 1.5) * 10) / 10;
      const reviews = 60 + (seed % 620);
      const leads = Math.round(score * (0.9 + (seed % 80) / 100));
      const spend = 2600 + (seed % 1500);
      // ~80% of locations trend up, ~20% trend down — independent of score
      // so even strong performers occasionally show a decline.
      const trend = (seed % 5 === 0) ? -(1 + (seed % 5)) : (1 + (seed % 9));
      const services = score >= 84 ? ['seo','ppc','meta','listings','web','reviews']
                     : score >= 76 ? ['seo','ppc','listings','web']
                     : score >= 68 ? ['seo','listings','web']
                     : ['listings'];

      out.push({ unit, region, city, score, leads, spend, rating, reviews, services, labels: labelsByTier[tier], trend, tier, status, readiness: marketingReadinessFor(unit, score) });
      need--; i++;
    }
  });

  return out;
}
window.buildPortfolio = buildPortfolio;

// ====================================================================
// CORPORATE ROLLUP, portfolio-wide aggregates for the HQ Locations tab
// ====================================================================
// Aggregates the same per-location model used by the drill-down detail
// page (window.buildLocationModel) so corporate numbers reconcile exactly
// with what HQ sees when they open a single location.
function CorporateRollup({ enriched, onOpen }) {
  const { useMemo, useState } = React;
  const [view, setView] = useState('summary'); // summary | regions

  const agg = useMemo(() => {
    const build = window.buildLocationModel;
    if (!build || !enriched.length) return null;
    const models = enriched.map(e => ({ loc: e, m: build(e) }));
    const n = models.length;
    const sum = (f) => models.reduce((a, x) => a + f(x.m), 0);

    const avgScore   = Math.round(sum(m => m.score) / n);
    const avgQuality = Math.round(sum(m => m.quality) / n);
    const totReviews = sum(m => m.reviewCount);
    const avgRating  = (sum(m => m.rating * m.reviewCount) / Math.max(1, totReviews));
    const citAccuracy= Math.round(sum(m => m.citLive) / Math.max(1, sum(m => m.citTotal)) * 100);
    const openIssues = sum(m => m.issues.length);
    const presenceGaps = sum(m => m.gapCount);
    const avgPos     = Math.round(sum(m => m.pos) / n);
    const avgNeu     = Math.round(sum(m => m.neu) / n);
    const avgNeg     = 100 - avgPos - avgNeu;

    // health distribution
    const healthy = models.filter(x => x.m.healthy).length;
    const atRisk  = models.filter(x => x.m.weak).length;
    const watch   = n - healthy - atRisk;

    // platform coverage, % of locations where each platform is connected
    const pKeys = models[0].m.platforms.map(p => ({ key: p.key, name: p.name }));
    const coverage = pKeys.map(pk => {
      const live = models.filter(x => x.m.platforms.find(p => p.key === pk.key && p.status === 'connected')).length;
      return { ...pk, live, pct: Math.round(live / n * 100) };
    });

    // attention queue, lowest scoring locations with open issues
    const queue = [...models]
      .filter(x => x.m.issues.length || x.m.weak)
      .sort((a, b) => a.m.score - b.m.score)
      .slice(0, 5);

    // regional rollup
    const byRegion = {};
    models.forEach(x => {
      const r = x.loc.region || 'Unassigned';
      (byRegion[r] = byRegion[r] || []).push(x);
    });
    const regions = Object.entries(byRegion).map(([region, list]) => {
      const rn = list.length;
      const rRev = list.reduce((a, x) => a + x.m.reviewCount, 0);
      return {
        region, count: rn,
        score:   Math.round(list.reduce((a, x) => a + x.m.score, 0) / rn),
        quality: Math.round(list.reduce((a, x) => a + x.m.quality, 0) / rn),
        rating:  (list.reduce((a, x) => a + x.m.rating * x.m.reviewCount, 0) / Math.max(1, rRev)),
        reviews: rRev,
        issues:  list.reduce((a, x) => a + x.m.issues.length, 0),
        atRisk:  list.filter(x => x.m.weak).length,
      };
    }).sort((a, b) => a.score - b.score);

    return { n, avgScore, avgQuality, totReviews, avgRating, citAccuracy, openIssues, presenceGaps,
      avgPos, avgNeu, avgNeg, healthy, watch, atRisk, coverage, queue, regions };
  }, [enriched]);

  if (!agg) return null;

  const fmtNum = (x) => x >= 1000 ? (x / 1000).toFixed(x >= 10000 ? 0 : 1) + 'k' : '' + x;
  const scoreColor = (s) => s >= 80 ? 'var(--sage)' : s >= 68 ? 'var(--marigold)' : 'var(--terra)';
  const statusTone = { 'Active': { fg: 'var(--sage)', bg: 'var(--sage-soft)' }, 'Onboarding': { fg: 'var(--marigold)', bg: 'var(--marigold-soft)' }, 'At-risk': { fg: 'var(--terra)', bg: 'var(--terra-soft)' } };

  const kpis = [
    { v: agg.n, l: 'Locations', sub: 'across portfolio', c: 'var(--ink)' },
    { v: agg.avgScore, l: 'Avg listing health', sub: '+3 vs last mo', c: scoreColor(agg.avgScore) },
    { v: agg.avgQuality + '%', l: 'Avg data quality', sub: 'field completeness', c: 'var(--ink)' },
    { v: fmtNum(agg.totReviews), l: 'Total reviews', sub: 'all platforms', c: 'var(--ink)' },
    { v: agg.avgRating.toFixed(2) + '★', l: 'Avg rating', sub: 'review-weighted', c: 'var(--marigold)' },
    { v: agg.citAccuracy + '%', l: 'Citation accuracy', sub: 'NAP in sync', c: scoreColor(agg.citAccuracy) },
  ];

  const lbl = (t, hint) => (
    <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', margin: '0 0 12px' }}>
      <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--ink-3)' }}>{t}</div>
      {hint && <span style={{ fontSize: 11, color: 'var(--ink-4)' }}>{hint}</span>}
    </div>
  );

  return (
    <div style={{ marginBottom: 18 }}>
      {/* KPI band */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: 10, marginBottom: 12 }}>
        {kpis.map((k, i) => (
          <div key={i} className="card card-pad" style={{ padding: '14px 16px' }}>
            <div style={{ fontFamily: 'var(--ff-display)', fontSize: 27, fontWeight: 500, color: k.c, lineHeight: 1 }}>{k.v}</div>
            <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.08em', fontWeight: 600, marginTop: 8 }}>{k.l}</div>
            <div style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 3 }}>{k.sub}</div>
          </div>
        ))}
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1.15fr 1fr 1fr', gap: 12, marginBottom: 12 }}>
        {/* Listing health distribution */}
        <div className="card card-pad">
          {lbl('Listing health', agg.n + ' locations')}
          <div style={{ display: 'flex', height: 14, borderRadius: 999, overflow: 'hidden', marginBottom: 14 }}>
            <div style={{ width: (agg.healthy / agg.n * 100) + '%', background: 'var(--sage)' }}/>
            <div style={{ width: (agg.watch / agg.n * 100) + '%', background: 'var(--marigold)' }}/>
            <div style={{ width: (agg.atRisk / agg.n * 100) + '%', background: 'var(--terra)' }}/>
          </div>
          {[['Healthy (80+)', agg.healthy, 'var(--sage)'], ['Needs attention', agg.watch, 'var(--marigold)'], ['At-risk (<68)', agg.atRisk, 'var(--terra)']].map(([l, v, c]) => (
            <div key={l} style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '6px 0', borderBottom: l.startsWith('At-risk') ? 'none' : '1px solid var(--cream-2)' }}>
              <span style={{ width: 8, height: 8, borderRadius: '50%', background: c }}/>
              <span style={{ flex: 1, fontSize: 12.5, color: 'var(--ink-2)' }}>{l}</span>
              <span style={{ fontFamily: 'var(--ff-mono)', fontSize: 13, fontWeight: 700, color: c }}>{v}</span>
            </div>
          ))}
        </div>

        {/* Platform coverage */}
        <div className="card card-pad">
          {lbl('Platform coverage', '% live')}
          <div style={{ display: 'flex', flexDirection: 'column', gap: 9 }}>
            {agg.coverage.map(p => (
              <div key={p.key} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <span style={{ fontSize: 12, color: 'var(--ink-2)', width: 96, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{p.name.replace(' Business','').replace(' Places','').replace(' Connect','')}</span>
                <div style={{ flex: 1, height: 6, background: 'var(--cream-2)', borderRadius: 3, overflow: 'hidden' }}>
                  <div style={{ width: p.pct + '%', height: '100%', background: p.pct >= 85 ? 'var(--sage)' : p.pct >= 65 ? 'var(--marigold)' : 'var(--terra)' }}/>
                </div>
                <span style={{ fontFamily: 'var(--ff-mono)', fontSize: 11.5, color: 'var(--ink-3)', width: 32, textAlign: 'right' }}>{p.pct}%</span>
              </div>
            ))}
          </div>
        </div>

        {/* Action rollup: issues, gaps, sentiment */}
        <div className="card card-pad">
          {lbl('Needs action', 'portfolio-wide')}
          <div style={{ display: 'flex', gap: 16, marginBottom: 14 }}>
            <div>
              <div style={{ fontFamily: 'var(--ff-display)', fontSize: 24, fontWeight: 500, color: agg.openIssues ? 'var(--terra)' : 'var(--sage)', lineHeight: 1 }}>{agg.openIssues}</div>
              <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.07em', marginTop: 5, fontWeight: 600 }}>Open issues</div>
            </div>
            <div>
              <div style={{ fontFamily: 'var(--ff-display)', fontSize: 24, fontWeight: 500, color: agg.presenceGaps ? 'var(--marigold)' : 'var(--sage)', lineHeight: 1 }}>{agg.presenceGaps}</div>
              <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.07em', marginTop: 5, fontWeight: 600 }}>Presence gaps</div>
            </div>
          </div>
          <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.07em', fontWeight: 600, marginBottom: 7 }}>Sentiment mix</div>
          <div style={{ display: 'flex', height: 12, borderRadius: 999, overflow: 'hidden', marginBottom: 7 }}>
            <div style={{ width: agg.avgPos + '%', background: 'var(--sage)' }}/>
            <div style={{ width: agg.avgNeu + '%', background: 'var(--marigold)' }}/>
            <div style={{ width: agg.avgNeg + '%', background: 'var(--terra)' }}/>
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--ink-3)' }}>
            <span style={{ color: 'var(--sage)', fontWeight: 600 }}>{agg.avgPos}% pos</span>
            <span style={{ color: 'var(--marigold)', fontWeight: 600 }}>{agg.avgNeu}% neu</span>
            <span style={{ color: 'var(--terra)', fontWeight: 600 }}>{agg.avgNeg}% neg</span>
          </div>
        </div>
      </div>

      <SourceNote>Source: Google Business Profile</SourceNote>

      {/* Region rollup + attention queue */}
      <div style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr', gap: 12 }}>
        <div className="card" style={{ overflow: 'hidden' }}>
          <div style={{ padding: '13px 16px 11px', borderBottom: '1px solid var(--cream-2)' }}>
            <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--ink-3)' }}>Rollup by region</div>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: '1.3fr 0.6fr 0.8fr 0.7fr 0.8fr 0.7fr', gap: 0, padding: '9px 16px', background: 'var(--cream)', fontSize: 9.5, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '.06em', fontWeight: 600 }}>
            <span>Region</span><span>Sites</span><span>Health</span><span>Rating</span><span>Reviews</span><span>Issues</span>
          </div>
          {agg.regions.map((r, i) => (
            <div key={r.region} style={{ display: 'grid', gridTemplateColumns: '1.3fr 0.6fr 0.8fr 0.7fr 0.8fr 0.7fr', gap: 0, padding: '11px 16px', borderBottom: i === agg.regions.length - 1 ? 'none' : '1px solid var(--cream-2)', alignItems: 'center', fontSize: 12.5 }}>
              <span style={{ fontWeight: 600, color: 'var(--ink)' }}>{r.region}</span>
              <span style={{ fontFamily: 'var(--ff-mono)', color: 'var(--ink-2)' }}>{r.count}</span>
              <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <span style={{ width: 7, height: 7, borderRadius: '50%', background: scoreColor(r.score) }}/>
                <span style={{ fontFamily: 'var(--ff-mono)', color: scoreColor(r.score), fontWeight: 700 }}>{r.score}</span>
              </span>
              <span style={{ fontFamily: 'var(--ff-mono)', color: 'var(--ink-2)' }}>{r.rating.toFixed(1)}★</span>
              <span style={{ fontFamily: 'var(--ff-mono)', color: 'var(--ink-3)' }}>{fmtNum(r.reviews)}</span>
              <span style={{ fontFamily: 'var(--ff-mono)', color: r.issues ? 'var(--terra)' : 'var(--ink-4)', fontWeight: r.issues ? 700 : 400 }}>{r.issues}</span>
            </div>
          ))}
        </div>

        <div className="card" style={{ overflow: 'hidden' }}>
          <div style={{ padding: '13px 16px 11px', borderBottom: '1px solid var(--cream-2)', display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
            <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--ink-3)' }}>Attention queue</div>
            <span style={{ fontSize: 11, color: 'var(--ink-4)' }}>lowest health</span>
          </div>
          {agg.queue.length === 0 ? (
            <div style={{ padding: '20px 16px', fontSize: 12.5, color: 'var(--ink-4)' }}>All locations healthy, nothing needs attention.</div>
          ) : agg.queue.map((x, i) => (
            <div key={x.loc.unit} onClick={() => onOpen(x.loc)} className="loc-row"
                 style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '11px 16px', borderBottom: i === agg.queue.length - 1 ? 'none' : '1px solid var(--cream-2)', cursor: 'pointer' }}>
              <span style={{ fontFamily: 'var(--ff-mono)', fontSize: 13, fontWeight: 700, color: scoreColor(x.m.score), width: 24 }}>{x.m.score}</span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--ink)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{x.loc.unit}</div>
                <div style={{ fontSize: 10.5, color: 'var(--ink-4)' }}>{x.m.issues.length} issue{x.m.issues.length === 1 ? '' : 's'}{x.m.gapCount ? ` · ${x.m.gapCount} gap${x.m.gapCount === 1 ? '' : 's'}` : ''}</div>
              </div>
              <span style={{ color: 'var(--ink-4)', display: 'flex' }}>{window.Icon.arrow}</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}
window.CorporateRollup = CorporateRollup;

// ====================================================================
// LOCATION MANAGEMENT, HQ source-of-truth view
// ====================================================================
// Derives editable address/phone/owner/lastUpdated client-side from the
// existing hqRollup mock, keeps data.js untouched while still giving the
// table real-feeling content. Status comes straight from the rollup
// (status: 'on-track'/'attention') and is mapped to friendlier labels.
function ListingsPageHq({ data }) {
  const rollup = useMemo(() => buildPortfolio(data.hqRollup || [], 700), [data.hqRollup]);

  // ---- Stable derivations from existing data ----
  const owners = ['Maya Chen','Jordan Reyes','Sam Patel','Priya Raman','Kai Watanabe','Noor Hassan','Eli Brooks','Tomas Vega','Reese Park','Quinn Rivera'];
  const streetTypes = ['St','Ave','Blvd','Pkwy','Way','Rd','Dr'];
  const streetNames = ['Main','Congress','Riverwalk','Galleria','Commerce','Oak','Maple','Stockyards','Mockingbird','Legacy','Peachtree','Broadway','Tryon','Beale','Beach','International','Camelback','Larimer','State','Strip','Capitol','Loop','Woodward','Hennepin','Country Club','Forest Park','Wisconsin'];

  const seedHash = (str) => { let h = 0; for (let i = 0; i < str.length; i++) h = ((h << 5) - h) + str.charCodeAt(i); return Math.abs(h); };
  const phoneFromUnit = (unit) => {
    const n = seedHash(unit);
    const a = 200 + (n % 800);
    const b = 100 + ((n >>> 4) % 900);
    const c = 1000 + ((n >>> 8) % 9000);
    return `(${a}) ${b}-${c}`;
  };
  const streetFromUnit = (unit) => {
    const n = seedHash(unit);
    const num = 100 + (n % 9000);
    const name = streetNames[n % streetNames.length];
    const type = streetTypes[(n >>> 5) % streetTypes.length];
    return `${num} ${name} ${type}`;
  };
  const ownerFromUnit = (unit) => owners[seedHash(unit) % owners.length];
  const updatedFromUnit = (unit, status) => {
    if (status === 'attention') {
      const n = seedHash(unit) % 60;            // attention locations are stale
      return n + 30;                             // 30–90 days
    }
    return seedHash(unit) % 14 + 1;              // 1–14 days
  };

  // Status mapping: 'attention' → mix of At-risk / Onboarding for variety.
  const statusFromRow = (row) => {
    if (row.status === 'attention') {
      return seedHash(row.unit) % 3 === 0 ? 'Onboarding' : 'At-risk';
    }
    return 'Active';
  };

  const enriched = useMemo(() => rollup.map(r => ({
    unit: r.unit, region: r.region, city: r.city,
    address: streetFromUnit(r.unit),
    phone:   phoneFromUnit(r.unit),
    owner:   ownerFromUnit(r.unit),
    status:  statusFromRow(r),
    daysAgo: updatedFromUnit(r.unit, r.status),
    // carried through to the location detail page
    score:   r.score, rating: r.rating, reviewsCount: r.reviews, leads: r.leads, services: r.services,
  })), [rollup]);

  const openDetail = (row) => window.dispatchEvent(new CustomEvent('open-location', { detail: row }));

  // ---- Filter state ----
  const [q, setQ] = useState('');
  const [region, setRegion] = useState('all');
  const [statusFilter, setStatusFilter] = useState('all');
  const [sortBy, setSortBy] = useState('unit');
  const [editing, setEditing] = useState(null);
  const [page, setPage] = useState(1);
  const PAGE_SIZE = 25;
  const [hqTab, setHqTab] = useState(() => {
    const t = window.__listingsInitialTab;
    if (t) { window.__listingsInitialTab = null; return t === 'edit' ? 'locations' : t; }
    return 'locations';
  });

  const regions = useMemo(() => ['all', ...Array.from(new Set(enriched.map(e => e.region)))], [enriched]);

  const filtered = useMemo(() => {
    let list = enriched;
    if (region !== 'all') list = list.filter(e => e.region === region);
    if (statusFilter !== 'all') list = list.filter(e => e.status === statusFilter);
    if (q.trim()) {
      const needle = q.trim().toLowerCase();
      list = list.filter(e =>
        e.unit.toLowerCase().includes(needle) ||
        e.city.toLowerCase().includes(needle) ||
        e.address.toLowerCase().includes(needle) ||
        e.owner.toLowerCase().includes(needle)
      );
    }
    if (sortBy === 'unit')    list = [...list].sort((a, b) => a.unit.localeCompare(b.unit));
    if (sortBy === 'updated') list = [...list].sort((a, b) => a.daysAgo - b.daysAgo);
    if (sortBy === 'status')  {
      const order = { 'At-risk': 0, 'Onboarding': 1, 'Active': 2 };
      list = [...list].sort((a, b) => order[a.status] - order[b.status]);
    }
    return list;
  }, [enriched, q, region, statusFilter, sortBy]);

  // Reset to page 1 whenever the filtered set changes.
  useEffect(() => { setPage(1); }, [q, region, statusFilter, sortBy]);
  const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
  const pageRows = useMemo(() => filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE), [filtered, page]);

  // ---- Status counts for the filter chips ----
  const counts = useMemo(() => {
    const c = { Active: 0, Onboarding: 0, 'At-risk': 0 };
    enriched.forEach(e => { c[e.status] = (c[e.status] || 0) + 1; });
    return c;
  }, [enriched]);

  const fmtUpdated = (d) => d === 1 ? 'yesterday' : d <= 14 ? `${d} days ago` : d <= 30 ? `${d} days ago` : `${Math.floor(d/30)} mo ago`;
  const statusTone = { 'Active': { fg: 'var(--sage)', bg: 'var(--sage-soft)' }, 'Onboarding': { fg: 'var(--marigold)', bg: 'var(--marigold-soft)' }, 'At-risk': { fg: 'var(--terra)', bg: 'var(--terra-soft)' } };

  return (
    <div className="page active">
      <div className="page-header">
        <div>
          <div className="page-title">Location Management.</div>
          <div className="page-sub">This is where you manage your Location detail source of truth.</div>
        </div>
        <div className="page-actions">
          {hqTab === 'locations' && <>
          <button className="btn btn-ghost btn-sm" onClick={() => window.toast('Add location', 'Spinning up onboarding flow, name, owner, address, phone first; everything else flows from there.', 'info')}>+ Add location</button>
          <button className="btn btn-primary btn-sm"
                  onClick={() => window.toast(
                    `Pushing updates for ${filtered.length} location${filtered.length === 1 ? '' : 's'}`,
                    'Pushing latest details to Google, Yelp, Apple Maps, Facebook · ETA ~6 minutes.',
                    'ok')}>
            Push updates to directories →
          </button>
          </>}
          {hqTab === 'analysis' && <button className="btn btn-primary btn-sm" onClick={() => window.toast('Report queued', 'Brand-wide Google review digest exporting to PDF · emailed to franchise marketing.', 'ok')}>Export review digest →</button>}
        </div>
      </div>

      <div className="tabs" style={{ marginBottom: 16 }}>
        {[
          ['locations', 'Locations'],
          ['analysis', 'Review Analysis'],
        ].map(([k, l]) => (
          <button key={k} className={'tab ' + (hqTab === k ? 'active' : '')} onClick={() => setHqTab(k)}>{l}</button>
        ))}
      </div>

      {hqTab === 'analysis' && <window.HqReviewAnalysis data={data} portfolio={enriched} onOpen={openDetail}/>}

      {hqTab === 'locations' && <>
      {/* ---- Corporate rollup ---- */}
      <CorporateRollup enriched={enriched} onOpen={openDetail}/>

      {/* ---- Filter strip ---- */}
      <div className="card card-pad" style={{ marginBottom: 16, padding: '14px 16px' }}>
        <div style={{ display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}>
          {/* Search */}
          <div style={{ position: 'relative', flex: '1 1 280px', minWidth: 240 }}>
            <span style={{ position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)', color: 'var(--ink-4)', pointerEvents: 'none', display: 'flex' }}>{window.Icon.search}</span>
            <input
              value={q}
              onChange={e => setQ(e.target.value)}
              placeholder="Search by location, city, address, or owner…"
              style={{ width: '100%', padding: '9px 12px 9px 38px', border: '1px solid var(--cream-3)', borderRadius: 8, fontSize: 13, fontFamily: 'var(--ff-sans)', background: 'var(--paper)', color: 'var(--ink)', boxSizing: 'border-box' }}
            />
          </div>

          {/* Region */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ fontSize: 11, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>Region</span>
            <window.DropMenu value={region} onPick={setRegion} options={regions.map(r => ({ v: r, l: r === 'all' ? `All regions (${enriched.length})` : `${r} (${enriched.filter(e => e.region === r).length})` }))}/>
          </div>

          {/* Status chips */}
          <div style={{ display: 'flex', gap: 6 }}>
            {['all','Active','Onboarding','At-risk'].map(s => {
              const isActive = statusFilter === s;
              const tone = statusTone[s];
              const count = s === 'all' ? enriched.length : counts[s] || 0;
              return (
                <button key={s} onClick={() => setStatusFilter(s)}
                        style={{
                          padding: '6px 10px', borderRadius: 999, fontSize: 11.5, fontWeight: 600, cursor: 'pointer',
                          border: isActive ? `1px solid ${s === 'all' ? 'var(--ink-3)' : tone.fg}` : '1px solid var(--cream-3)',
                          background: isActive ? (s === 'all' ? 'var(--ink)' : tone.bg) : 'var(--paper)',
                          color: isActive ? (s === 'all' ? 'var(--paper)' : tone.fg) : 'var(--ink-3)',
                        }}>
                  {s === 'all' ? 'All' : s} <span style={{ fontFamily: 'var(--ff-mono)', opacity: 0.7, marginLeft: 4 }}>{count}</span>
                </button>
              );
            })}
          </div>

          <div style={{ flex: 1 }}/>

          {/* Sort */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ fontSize: 11, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>Sort</span>
            <window.DropMenu value={sortBy} onPick={setSortBy} options={[
              { v: 'unit',    l: 'Location name (A–Z)' },
              { v: 'updated', l: 'Most recently updated' },
              { v: 'status',  l: 'Status (At-risk first)' },
            ]}/>
          </div>
        </div>
      </div>

      {/* ---- Table ---- */}
      <div className="card" style={{ overflow: 'hidden' }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1.6fr 1.0fr 1.0fr 0.9fr 1.0fr 0.6fr', gap: 0, padding: '12px 16px', borderBottom: '1px solid var(--cream-3)', background: 'var(--cream)', fontSize: 10.5, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '0.06em', fontWeight: 600 }}>
          <span>Location</span>
          <span>Address</span>
          <span>Phone</span>
          <span>Owner</span>
          <span>Status</span>
          <span>Last updated</span>
          </div>
        {filtered.length === 0 && (
          <div style={{ padding: '40px 20px', textAlign: 'center', color: 'var(--ink-4)', fontSize: 13 }}>
            No locations match, clear filters or adjust your search.
          </div>
        )}
        {pageRows.map((r, i) => {
          const tone = statusTone[r.status];
          return (
            <div key={r.unit}
                 onClick={() => openDetail(r)}
                 className="loc-row"
                 style={{ display: 'grid', gridTemplateColumns: '1.4fr 1.6fr 1.0fr 1.0fr 0.9fr 1.0fr 0.6fr', gap: 0, padding: '14px 16px', borderBottom: i === pageRows.length - 1 ? 'none' : '1px solid var(--cream-2)', alignItems: 'center', fontSize: 13, cursor: 'pointer' }}>
              <div>
                <div style={{ color: 'var(--ink)', fontWeight: 600 }}>{r.unit}</div>
                <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 1 }}>{r.region}</div>
              </div>
              <div style={{ color: 'var(--ink-2)' }}>
                <div>{r.address}</div>
                <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 1 }}>{r.city}</div>
              </div>
              <div style={{ fontFamily: 'var(--ff-mono)', color: 'var(--ink-2)', fontSize: 12.5 }}>{r.phone}</div>
              <div style={{ color: 'var(--ink-2)' }}>{r.owner}</div>
              <div>
                <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '3px 9px', borderRadius: 999, background: tone.bg, color: tone.fg, fontSize: 11, fontWeight: 600 }}>
                  <span style={{ width: 6, height: 6, borderRadius: '50%', background: tone.fg }}/>
                  {r.status}
                </span>
              </div>
              <div style={{ color: r.daysAgo > 30 ? 'var(--terra)' : 'var(--ink-3)', fontSize: 12 }}>
                {fmtUpdated(r.daysAgo)}
              </div>
              <div style={{ textAlign: 'right' }}>
                <button className="btn btn-quiet btn-xs" onClick={(e) => { e.stopPropagation(); openDetail(r); }}>Open →</button>
              </div>
            </div>
          );
        })}
      </div>

      <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
        <span style={{ fontSize: 11.5, color: 'var(--ink-4)', display: 'flex', alignItems: 'center', gap: 8 }}>
          <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--violet)' }}/>
          Showing {filtered.length === 0 ? 0 : (page - 1) * PAGE_SIZE + 1}–{Math.min(page * PAGE_SIZE, filtered.length)} of {filtered.length.toLocaleString()}{filtered.length !== enriched.length ? ` filtered (${enriched.length.toLocaleString()} total)` : ' locations'} · last directory sync 4 min ago
        </span>
        <span style={{ flex: 1 }}/>
        {totalPages > 1 && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <button className="btn btn-ghost btn-xs" disabled={page === 1} onClick={() => setPage(p => Math.max(1, p - 1))} style={page === 1 ? { opacity: 0.4, cursor: 'default' } : {}}>← Prev</button>
            <span style={{ fontSize: 11.5, color: 'var(--ink-3)', fontFamily: 'var(--ff-mono)', padding: '0 4px' }}>Page {page} / {totalPages}</span>
            <button className="btn btn-ghost btn-xs" disabled={page === totalPages} onClick={() => setPage(p => Math.min(totalPages, p + 1))} style={page === totalPages ? { opacity: 0.4, cursor: 'default' } : {}}>Next →</button>
          </div>
        )}
      </div>

      {/* Apple Business Connect coverage, folded into the Locations tab */}
      <div style={{ marginTop: 24 }}>
        <HqAppleCoverage data={data}/>
      </div>
      </>}

      {/* Edit modal */}
      {editing && (
        <LocationEditModal
          row={editing}
          onClose={() => setEditing(null)}
          onSave={(updates) => {
            setEditing(null);
            window.toast(`${updates.unit} updated`, 'Saved to source of truth · queue sync to directories?', 'ok');
          }}
        />
      )}
    </div>
  );
}
window.ListingsPageHq = ListingsPageHq;

function LocationEditModal({ row, onClose, onSave }) {
  const [form, setForm] = useState({ ...row });
  const update = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const fieldStyle = { width: '100%', padding: '9px 12px', border: '1px solid var(--cream-3)', borderRadius: 8, fontSize: 13, fontFamily: 'var(--ff-sans)', background: 'var(--paper)', color: 'var(--ink)', boxSizing: 'border-box' };
  const labelStyle = { fontSize: 11, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: '0.06em', fontWeight: 600, marginBottom: 6, display: 'block' };
  return (
    <window.ModalPortal><div onClick={onClose}
         style={{ position: 'fixed', inset: 0, background: 'rgba(36,30,28,0.55)', zIndex: 9000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 }}>
      <div onClick={e => e.stopPropagation()}
           style={{ background: 'var(--paper)', borderRadius: 14, width: '100%', maxWidth: 580, padding: 24, boxShadow: '0 20px 60px rgba(0,0,0,0.25)', maxHeight: '90vh', overflowY: 'auto' }}>
        <div style={{ fontSize: 11, color: 'var(--violet)', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 700, marginBottom: 6 }}>Edit location</div>
        <div style={{ fontFamily: 'var(--ff-display)', fontSize: 22, fontWeight: 600, color: 'var(--ink)', marginBottom: 4 }}>{form.unit}</div>
        <div style={{ fontSize: 12.5, color: 'var(--ink-3)', marginBottom: 20 }}>Source of truth · pushed to all connected directories on save</div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
          <div style={{ gridColumn: '1 / -1' }}>
            <label style={labelStyle}>Location name</label>
            <input style={fieldStyle} value={form.unit} onChange={e => update('unit', e.target.value)}/>
          </div>
          <div style={{ gridColumn: '1 / -1' }}>
            <label style={labelStyle}>Address</label>
            <input style={fieldStyle} value={form.address} onChange={e => update('address', e.target.value)}/>
          </div>
          <div>
            <label style={labelStyle}>City / State</label>
            <input style={fieldStyle} value={form.city} onChange={e => update('city', e.target.value)}/>
          </div>
          <div>
            <label style={labelStyle}>Phone</label>
            <input style={fieldStyle} value={form.phone} onChange={e => update('phone', e.target.value)}/>
          </div>
          <div>
            <label style={labelStyle}>Owner</label>
            <input style={fieldStyle} value={form.owner} onChange={e => update('owner', e.target.value)}/>
          </div>
          <div>
            <label style={labelStyle}>Region</label>
            <input style={fieldStyle} value={form.region} onChange={e => update('region', e.target.value)}/>
          </div>
          <div>
            <label style={labelStyle}>Status</label>
            <select style={fieldStyle} value={form.status} onChange={e => update('status', e.target.value)}>
              <option>Active</option>
              <option>Onboarding</option>
              <option>At-risk</option>
            </select>
          </div>
          <div>
            <label style={labelStyle}>Last verified</label>
            <input style={{ ...fieldStyle, color: 'var(--ink-4)' }} value="Apr 27, 2026 · auto" disabled/>
          </div>
        </div>

        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 22, paddingTop: 16, borderTop: '1px solid var(--cream-3)' }}>
          <span style={{ fontSize: 11, color: 'var(--ink-4)' }}>Sync targets: Google · Yelp · Apple Maps · Facebook</span>
          <span style={{ flex: 1 }}/>
          <button className="btn btn-ghost btn-sm" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary btn-sm" onClick={() => onSave(form)}>Save changes →</button>
        </div>
      </div>
    </div></window.ModalPortal>
  );
}
window.LocationEditModal = LocationEditModal;

// ====================================================================
// LISTINGS, franchisee persona: original directory-presence dashboard
// ====================================================================
function ListingsPageFranchisee({ data }) {
  const d = data.listingsDetail;
  const apple = data.variant === 'bare' ? data.appleConnectB : data.appleConnect;
  const [tab, setTab] = useState(() => {
    const t = window.__listingsInitialTab;
    if (t) { window.__listingsInitialTab = null; return t; }
    return 'overview';
  });
  const [scanning, setScanning] = useState(false);
  const [fixing, setFixing] = useState(false);
  const [dirDetail, setDirDetail] = useState(null);
  const [dirty, setDirty] = useState(false);

  useEffect(() => {
    const h = (e) => setTab(e.detail);
    window.addEventListener('la-listings-tab', h);
    return () => window.removeEventListener('la-listings-tab', h);
  }, []);

  const scan = () => {
    setScanning(true);
    T('Scanning 42 directories…', 'This runs in the background, I\'ll ping you.', 'info');
    setTimeout(() => { setScanning(false); T('Scan complete', '40 live · 2 issues queued for auto-fix.', 'ok'); }, 2000);
  };

  return (
    <div className="page active">
      <div className="page-header">
        <div>
          <div className="page-title">Listings &amp; Local Presence.</div>
          <div className="page-sub">42 directories monitored daily. Updates push in minutes.</div>
        </div>
        <div className="page-actions">
          <button className="btn btn-ghost btn-sm" onClick={scan} disabled={scanning}>{scanning ? 'Scanning…' : '↺ Scan now'}</button>
          <button className="btn btn-primary btn-sm" onClick={() => setTab('edit')}>Edit my info</button>
        </div>
      </div>

      <ListingsHero d={d} onTab={setTab} onScan={scan} scanning={scanning}/>

      {tab === 'overview' && <IssueAlert d={d} onFix={() => setFixing(true)}/>}

      {/* Tabs */}
      <div className="tabs" style={{ marginBottom: 18 }}>
        {[
          ['overview', 'Overview'],
          ['dirs', 'All directories'],
          ['apple', 'Apple Business Connect'],
          ['edit', 'Edit info'],
          ['photos', 'Photos & media'],
          ['images', 'Image library'],
          ['ai', 'AI visibility'],
          ['analytics', 'Analytics'],
        ].map(([k, l]) => (
          <button key={k} className={'tab ' + (tab === k ? 'active' : '')} onClick={() => setTab(k)}>{l}</button>
        ))}
      </div>

      {tab === 'overview' && <OverviewTab data={data} d={d} onTab={setTab}/>}
      {tab === 'dirs' && <DirectoriesTab data={data} onDetail={setDirDetail}/>}
      {tab === 'apple' && <AppleTab apple={apple}/>}
      {tab === 'edit' && <EditTab d={d} dirty={dirty} setDirty={setDirty} onSave={() => T('Pushing updates', '42 directories being synced. ETA: 8 minutes.', 'ok')}/>}
      {tab === 'photos' && <PhotosTab d={d}/>}
      {tab === 'images' && <ImageLibrary scope="franchisee" data={data}/>}
      {tab === 'ai' && <AiTab d={d} onTab={setTab}/>}
      {tab === 'analytics' && <AnalyticsTab data={data} d={d}/>}

      {/* Fix-issues modal */}
      <window.Modal open={fixing} onClose={() => setFixing(false)} title="Fix 2 issues" sub="Lenny's draft, approve to push"
        footer={<>
          <button className="btn btn-ghost btn-sm" onClick={() => setFixing(false)}>Close</button>
          <button className="btn btn-terra btn-sm" onClick={() => { setFixing(false); T('Fixes queued', 'Google auto-sync in 4h · GBP update pushed now.', 'ok'); }}>Approve &amp; push</button>
        </>}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <div style={{ padding: '12px 14px', background: 'var(--marigold-soft)', borderRadius: 10 }}>
            <div style={{ fontSize: 13, fontWeight: 600, color: '#725900', marginBottom: 4 }}>Google, Sunday hours drift</div>
            <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>Currently: 12pm–8pm · Fix: 12pm–9pm</div>
          </div>
          <div style={{ padding: '12px 14px', background: 'var(--terra-soft)', borderRadius: 10 }}>
            <div style={{ fontSize: 13, fontWeight: 600, color: '#8c4a13', marginBottom: 4 }}>Google Business Profile, missing URL</div>
            <div style={{ fontSize: 12, color: 'var(--ink-2)' }}>Proposed: https://freshnest.com/austin-downtown</div>
          </div>
        </div>
      </window.Modal>

      {/* Directory detail */}
      <window.Modal open={!!dirDetail} onClose={() => setDirDetail(null)} title={dirDetail?.name} sub={`Last sync: ${dirDetail?.lastSync}`}>
        {dirDetail && (
          <div style={{ fontSize: 13, color: 'var(--ink-2)', lineHeight: 1.7 }}>
            <div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: '8px 14px', marginBottom: 14 }}>
              <div style={{ color: 'var(--ink-3)' }}>Type</div><div>{dirDetail.type}</div>
              <div style={{ color: 'var(--ink-3)' }}>Status</div><div>{dirDetail.status === 'live' ? '● Live' : '⚠ Issue'}</div>
              <div style={{ color: 'var(--ink-3)' }}>Connection</div><div>{dirDetail.controlled ? 'Direct API (we control)' : '3rd-party cadence (we influence)'}</div>
              <div style={{ color: 'var(--ink-3)' }}>Fields synced</div><div className="mono">{dirDetail.fields}</div>
              {dirDetail.views != null && <>
                <div style={{ color: 'var(--ink-3)' }}>Views (mo)</div><div className="mono">{dirDetail.views.toLocaleString()}</div>
              </>}
              {dirDetail.actions != null && <>
                <div style={{ color: 'var(--ink-3)' }}>Actions (mo)</div><div className="mono">{dirDetail.actions.toLocaleString()}</div>
              </>}
            </div>
            <div style={{ padding: '12px 14px', background: 'var(--cream)', borderRadius: 10, fontSize: 12, color: 'var(--ink-3)' }}>
              {dirDetail.controlled
                ? 'We hold a direct API connection, edits sync in under 4 minutes.'
                : 'We push data here but the publisher controls refresh cadence (days to weeks).'}
            </div>
          </div>
        )}
      </window.Modal>
    </div>
  );
}

})();
