// Share view — read-only client gallery (used both for internal preview and public share)

// Responsive column count hook
function useColumns() {
  const [cols, setCols] = React.useState(() => {
    const w = window.innerWidth;
    return w < 540 ? 1 : w < 800 ? 2 : w < 1100 ? 3 : 4;
  });
  React.useEffect(() => {
    const fn = () => {
      const w = window.innerWidth;
      setCols(w < 540 ? 1 : w < 800 ? 2 : w < 1100 ? 3 : 4);
    };
    window.addEventListener('resize', fn);
    return () => window.removeEventListener('resize', fn);
  }, []);
  return cols;
}

// Pin icon (thumbtack)
function IconPin({ size = 14, filled = false }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={filled ? 'currentColor' : 'none'}
      stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <line x1="12" y1="17" x2="12" y2="22"/>
      <path d="M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z"/>
    </svg>
  );
}

const MAX_PINS = 10;

function ShareView({ files, share, onOpen, onClose, onFeedback, feedback, onApprove, approvals, token, pins, onPin }) {
  const [photoErr, setPhotoErr]   = React.useState(false);
  const [logoErr, setLogoErr]     = React.useState(false);
  const [noteTarget, setNoteTarget] = React.useState(null); // filename for note modal
  const [noteText, setNoteText]   = React.useState('');
  const [showMoodBoard, setShowMoodBoard] = React.useState(false);
  const cols = useColumns();

  const hasLogo    = share.has_logo && !logoErr;
  const studioName = share.studio_name || '';
  const tagline    = share.tagline || '';
  const photographer = share.photographer || '';

  const likedCount    = feedback ? feedback.size : 0;
  const approvedCount = approvals ? Object.values(approvals).filter(a => a.status === 'approved').length : 0;
  const changesCount  = approvals ? Object.values(approvals).filter(a => a.status === 'changes').length : 0;
  const pinCount      = pins ? pins.size : 0;
  const atPinLimit    = pinCount >= MAX_PINS;

  const submitNote = () => {
    if (onApprove && noteTarget) {
      onApprove(noteTarget, 'changes', noteText.trim());
    }
    setNoteTarget(null);
    setNoteText('');
  };

  return (
    <div style={{ minHeight: '100vh', background: 'var(--bg-0)' }}>
      {/* Header */}
      <header style={{
        padding: '24px 24px 20px', borderBottom: '1px solid var(--line-1)',
        display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', flexWrap: 'wrap', gap: 16,
      }}>
        <div>
          {(hasLogo || studioName) && (
            <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
              {hasLogo && (
                <img src={`/api/branding/logo/${share.username}`}
                  style={{ height: 36, maxWidth: 160, objectFit: 'contain' }}
                  alt={studioName || photographer} onError={() => setLogoErr(true)}/>
              )}
              {!hasLogo && studioName && (
                <div style={{ fontSize: 16, fontWeight: 700, color: 'var(--ink-0)', letterSpacing: -0.3 }}>{studioName}</div>
              )}
              {tagline && (
                <div style={{ fontSize: 11, color: 'var(--ink-3)', borderLeft: '1px solid var(--line-2)', paddingLeft: 12 }}>{tagline}</div>
              )}
            </div>
          )}
          <div className="mono" style={{ fontSize: 9.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: 0.14, marginBottom: 4 }}>
            Client Gallery
          </div>
          <div className="serif" style={{ fontSize: cols === 1 ? 28 : 38, letterSpacing: -0.8, color: 'var(--ink-0)', lineHeight: 1 }}>
            {share.label || 'Gallery'}
          </div>
          <div style={{ fontSize: 12, color: 'var(--ink-2)', marginTop: 10, display: 'flex', alignItems: 'center', gap: 10 }}>
            {share.username && (
              <div style={{ width: 24, height: 24, borderRadius: '50%', background: 'var(--accent)', overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, fontWeight: 700, color: '#1a1200', flexShrink: 0 }}>
                {share.has_photo && !photoErr
                  ? <img src={`/api/profile/photo/${share.username}`} style={{ width: '100%', height: '100%', objectFit: 'cover' }} onError={() => setPhotoErr(true)}/>
                  : (photographer || '?')[0].toUpperCase()}
              </div>
            )}
            <span>{files.length} photos{photographer ? ` · by ${photographer}` : ''}</span>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
          {/* Mood board button */}
          {onPin && (
            <button onClick={() => setShowMoodBoard(true)}
              style={{
                display: 'inline-flex', alignItems: 'center', gap: 7,
                height: 34, padding: '0 14px', borderRadius: 6,
                background: pinCount > 0 ? 'var(--accent)' : 'var(--bg-3)',
                border: `1px solid ${pinCount > 0 ? 'var(--accent)' : 'var(--line-2)'}`,
                color: pinCount > 0 ? '#1a1200' : 'var(--ink-1)',
                fontWeight: 600, fontSize: 13, cursor: 'pointer', fontFamily: 'inherit',
              }}>
              <IconPin size={13} filled={pinCount > 0}/>
              {pinCount === 0 ? 'My Selection' : `My Selection · ${pinCount}/${MAX_PINS}`}
            </button>
          )}
          {likedCount > 0 && token && (
            <a href={`/share/${token}/download-favorites.zip`}
              style={{ display: 'inline-flex', alignItems: 'center', gap: 6, height: 34, padding: '0 14px', borderRadius: 6, background: 'var(--bg-3)', border: '1px solid var(--line-2)', color: 'var(--ink-0)', fontWeight: 600, fontSize: 13, textDecoration: 'none' }}>
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
              </svg>
              Download {likedCount} favorite{likedCount !== 1 ? 's' : ''}
            </a>
          )}
          {onClose && <Btn size="lg" variant="outline" onClick={onClose}>Exit preview</Btn>}
        </div>
      </header>

      {/* Instructions bar */}
      <div style={{ padding: '10px 24px', background: 'var(--bg-1)', borderBottom: '1px solid var(--line-1)',
        display: 'flex', alignItems: 'center', gap: 12, fontSize: 12, color: 'var(--ink-2)', flexWrap: 'wrap',
      }}>
        <IconInfo size={13}/>
        <span>
          {onPin && <><IconPin size={11} style={{ verticalAlign: 'middle' }}/>{' '}pin your favorites · </>}
          <IconStar size={11} style={{ verticalAlign: 'middle', color: 'var(--accent)' }}/> to like ·
          {onApprove ? ' ✓ approve · ✗ request changes' : ' tap to view full size'}
        </span>
        <div style={{ flex: 1 }}/>
        <div style={{ display: 'flex', gap: 10, fontFamily: 'var(--mono)', fontSize: 10.5, alignItems: 'center' }}>
          {/* Mood board progress */}
          {onPin && pinCount > 0 && (
            <button onClick={() => setShowMoodBoard(true)} style={{
              background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'var(--mono)', fontSize: 10.5,
              display: 'flex', alignItems: 'center', gap: 5, padding: 0,
              color: atPinLimit ? 'var(--accent)' : 'var(--ink-2)',
            }}>
              <IconPin size={10} filled={true}/>
              <span>{pinCount}/{MAX_PINS} pinned</span>
              {/* progress bar */}
              <div style={{ width: 40, height: 3, background: 'var(--line-2)', borderRadius: 2 }}>
                <div style={{ width: `${(pinCount / MAX_PINS) * 100}%`, height: '100%', background: 'var(--accent)', borderRadius: 2, transition: 'width 200ms' }}/>
              </div>
            </button>
          )}
          {likedCount > 0 && <span style={{ color: 'var(--accent)' }}>{likedCount} ★</span>}
          {approvedCount > 0 && <span style={{ color: '#88ddaa' }}>{approvedCount} ✓</span>}
          {changesCount > 0  && <span style={{ color: '#f4a442' }}>{changesCount} ✗</span>}
        </div>
      </div>

      {/* Masonry grid */}
      <div style={{ columnCount: cols, columnGap: 8, padding: `14px ${cols === 1 ? 0 : 16}px 60px` }}>
        {files.map((f, i) => {
          const status = approvals && approvals[f.filename] ? approvals[f.filename].status : null;
          const note   = approvals && approvals[f.filename] ? approvals[f.filename].note   : '';
          const liked  = feedback && feedback.has(f.filename);
          const pinned = pins && pins.has(f.filename);
          return (
            <div key={f.filename} onClick={() => onOpen(i)} style={{
              breakInside: 'avoid', marginBottom: 8, position: 'relative',
              background: 'var(--bg-2)', borderRadius: cols === 1 ? 0 : 4, overflow: 'hidden',
              cursor: 'zoom-in',
              outline: pinned ? '2px solid var(--accent)' : status === 'approved' ? '2px solid #88ddaa' : status === 'changes' ? '2px solid #f4a442' : 'none',
            }}
            onMouseEnter={e => { if (cols > 1) e.currentTarget.style.transform = 'translateY(-2px)'; }}
            onMouseLeave={e => e.currentTarget.style.transform = 'none'}>
              <img src={f.thumb_url} alt="" loading="lazy" style={{ width: '100%', display: 'block' }}/>

              {/* Status badge */}
              {status && (
                <div style={{
                  position: 'absolute', top: 8, left: 8,
                  background: status === 'approved' ? 'rgba(136,221,170,0.9)' : 'rgba(244,164,66,0.9)',
                  color: '#111', fontSize: 10, fontWeight: 700, padding: '2px 7px', borderRadius: 20,
                  backdropFilter: 'blur(4px)',
                }}>
                  {status === 'approved' ? '✓ Approved' : '✗ Changes'}
                </div>
              )}

              {/* Top-right action buttons: pin + favorite */}
              <div style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 5 }} onClick={e => e.stopPropagation()}>
                {/* Pin button */}
                {onPin && (
                  <button onClick={() => onPin(f.filename)}
                    disabled={atPinLimit && !pins.has(f.filename)}
                    title={atPinLimit && !pins.has(f.filename) ? 'Selection full (10/10)' : pins.has(f.filename) ? 'Remove from selection' : 'Add to selection'}
                    style={{
                      width: 30, height: 30, borderRadius: '50%',
                      background: pins.has(f.filename) ? 'var(--accent)' : 'rgba(10,10,12,0.72)',
                      border: `1px solid ${pins.has(f.filename) ? 'var(--accent)' : 'rgba(255,255,255,0.18)'}`,
                      color: pins.has(f.filename) ? '#1a1200' : (atPinLimit ? 'var(--ink-4)' : 'var(--ink-1)'),
                      backdropFilter: 'blur(8px)', cursor: atPinLimit && !pins.has(f.filename) ? 'not-allowed' : 'pointer',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      opacity: atPinLimit && !pins.has(f.filename) ? 0.45 : 1,
                      transition: 'all 150ms',
                    }}>
                    <IconPin size={13} filled={pins.has(f.filename)}/>
                  </button>
                )}
                {/* Favorite button */}
                {onFeedback && (
                  <button onClick={() => onFeedback(f.filename)}
                    style={{
                      width: 30, height: 30, borderRadius: '50%',
                      background: liked ? 'rgba(244,185,66,0.25)' : 'rgba(10,10,12,0.72)',
                      border: `1px solid ${liked ? 'var(--accent)' : 'rgba(255,255,255,0.18)'}`,
                      color: liked ? 'var(--accent)' : 'var(--ink-1)',
                      backdropFilter: 'blur(8px)', cursor: 'pointer',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                    }}>
                    <IconStar size={13} fill={liked ? 'var(--accent)' : 'none'}/>
                  </button>
                )}
              </div>

              {/* Approval buttons — bottom bar */}
              {onApprove && (
                <div onClick={e => e.stopPropagation()}
                  style={{ position: 'absolute', bottom: 0, left: 0, right: 0,
                    background: 'linear-gradient(to top, rgba(5,5,7,0.85) 60%, transparent)',
                    padding: '20px 8px 8px', display: 'flex', gap: 5, justifyContent: 'center',
                  }}>
                  <button onClick={() => {
                    if (status === 'approved') onApprove(f.filename, null, '');
                    else onApprove(f.filename, 'approved', '');
                  }}
                    style={{
                      flex: 1, height: 28, borderRadius: 5, border: 'none', fontSize: 11, fontWeight: 700,
                      cursor: 'pointer', fontFamily: 'inherit',
                      background: status === 'approved' ? '#88ddaa' : 'rgba(136,221,170,0.18)',
                      color: status === 'approved' ? '#111' : '#88ddaa',
                    }}>✓ Approve</button>
                  <button onClick={() => {
                    if (status === 'changes') { onApprove(f.filename, null, ''); }
                    else { setNoteTarget(f.filename); setNoteText(note || ''); }
                  }}
                    style={{
                      flex: 1, height: 28, borderRadius: 5, border: 'none', fontSize: 11, fontWeight: 700,
                      cursor: 'pointer', fontFamily: 'inherit',
                      background: status === 'changes' ? '#f4a442' : 'rgba(244,164,66,0.18)',
                      color: status === 'changes' ? '#111' : '#f4a442',
                    }}>✗ Changes</button>
                  {/* Per-photo download */}
                  {f.download_url && (
                    <a href={f.download_url} download={f.filename}
                      onClick={e => e.stopPropagation()} title="Download"
                      style={{
                        width: 28, height: 28, borderRadius: 5, flexShrink: 0,
                        background: 'rgba(255,255,255,0.1)', color: 'var(--ink-1)',
                        display: 'flex', alignItems: 'center', justifyContent: 'center', textDecoration: 'none',
                      }}>
                      <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                        <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
                      </svg>
                    </a>
                  )}
                </div>
              )}

              {/* Download only (no approval) */}
              {!onApprove && f.download_url && (
                <a href={f.download_url} download={f.filename}
                  onClick={e => e.stopPropagation()} title="Download original"
                  style={{
                    position: 'absolute', top: 8, left: 8,
                    width: 30, height: 30, borderRadius: '50%',
                    background: 'rgba(10,10,12,0.72)', border: '1px solid rgba(255,255,255,0.18)',
                    color: 'var(--ink-1)', backdropFilter: 'blur(8px)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center', textDecoration: 'none',
                  }}>
                  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
                  </svg>
                </a>
              )}

              {/* Filename */}
              <div style={{ position: 'absolute', bottom: onApprove ? 44 : 0, left: 0, right: 0, padding: '12px 8px 4px',
                background: onApprove ? 'none' : 'linear-gradient(to top, rgba(10,10,12,0.65), transparent)',
                color: 'var(--ink-1)', fontFamily: 'var(--mono)', fontSize: 9.5, opacity: 0.7,
                pointerEvents: 'none',
              }}>
                {!onApprove && f.filename}
                {note && status === 'changes' && (
                  <div style={{ fontSize: 10, color: '#f4a442', marginTop: 2, fontFamily: 'inherit' }}>"{note}"</div>
                )}
              </div>
            </div>
          );
        })}
      </div>

      {/* Studio footer */}
      <div style={{
        padding: '16px 24px', borderTop: '1px solid var(--line-1)',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        fontSize: 11, color: 'var(--ink-3)',
      }}>
        <span>Powered by Live Gallery</span>
        <a href="/" style={{ color: 'var(--ink-3)', textDecoration: 'none', opacity: 0.6 }}
          onMouseEnter={e => e.target.style.opacity = '1'}
          onMouseLeave={e => e.target.style.opacity = '0.6'}>
          Studio login →
        </a>
      </div>

      {/* Mood Board panel */}
      {showMoodBoard && onPin && (
        <div onClick={() => setShowMoodBoard(false)} style={{
          position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.75)', zIndex: 500,
          display: 'flex', alignItems: 'flex-end', justifyContent: 'center',
          padding: '0 0 0 0',
        }}>
          <div onClick={e => e.stopPropagation()} style={{
            background: 'var(--bg-1)', borderTop: '1px solid var(--line-2)',
            borderRadius: '14px 14px 0 0',
            width: '100%', maxWidth: 680, maxHeight: '80vh',
            display: 'flex', flexDirection: 'column',
            animation: 'fade-up 220ms ease',
          }}>
            {/* Panel header */}
            <div style={{ padding: '18px 20px 14px', borderBottom: '1px solid var(--line-1)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <div>
                <div style={{ fontSize: 16, fontWeight: 700, color: 'var(--ink-0)', display: 'flex', alignItems: 'center', gap: 8 }}>
                  <IconPin size={15} filled={pinCount > 0}/>
                  My Selection
                </div>
                <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 3 }}>
                  {pinCount === 0 ? 'No photos pinned yet — tap 📌 on photos to add them here'
                    : pinCount >= MAX_PINS ? `${pinCount}/${MAX_PINS} photos selected · selection complete`
                    : `${pinCount}/${MAX_PINS} photos · ${MAX_PINS - pinCount} more to go`}
                </div>
              </div>
              <button onClick={() => setShowMoodBoard(false)}
                style={{ background: 'none', border: 'none', color: 'var(--ink-3)', cursor: 'pointer', fontSize: 20, lineHeight: 1, padding: 4 }}>×</button>
            </div>

            {/* Progress bar */}
            <div style={{ height: 3, background: 'var(--line-1)' }}>
              <div style={{ height: '100%', width: `${(pinCount / MAX_PINS) * 100}%`, background: 'var(--accent)', transition: 'width 300ms ease' }}/>
            </div>

            {/* Pinned photos grid */}
            <div style={{ flex: 1, overflowY: 'auto', padding: 16 }}>
              {pinCount === 0 ? (
                <div style={{ textAlign: 'center', padding: '40px 0', color: 'var(--ink-3)', fontSize: 13 }}>
                  Browse the gallery and tap <IconPin size={12} style={{ verticalAlign: 'middle' }}/> on photos you love
                </div>
              ) : (
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))', gap: 8 }}>
                  {files.filter(f => pins.has(f.filename)).map(f => (
                    <div key={f.filename} style={{ position: 'relative', borderRadius: 6, overflow: 'hidden', background: 'var(--bg-2)', border: '1.5px solid var(--accent)' }}>
                      <img src={f.thumb_url} alt="" style={{ width: '100%', display: 'block', aspectRatio: '1', objectFit: 'cover' }}/>
                      <button onClick={() => onPin(f.filename)}
                        style={{
                          position: 'absolute', top: 4, right: 4, width: 22, height: 22, borderRadius: '50%',
                          background: 'rgba(0,0,0,0.7)', border: 'none', color: 'var(--accent)',
                          cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
                          fontSize: 14, lineHeight: 1,
                        }}>×</button>
                      <div style={{ padding: '4px 6px', fontFamily: 'var(--mono)', fontSize: 9, color: 'var(--ink-3)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                        {f.filename}
                      </div>
                    </div>
                  ))}
                </div>
              )}
            </div>

            {/* Panel footer */}
            <div style={{ padding: '14px 20px', borderTop: '1px solid var(--line-1)', display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
              <button onClick={() => setShowMoodBoard(false)}
                style={{ height: 36, padding: '0 18px', borderRadius: 6, border: '1px solid var(--line-2)', background: 'transparent', color: 'var(--ink-1)', fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>
                Continue browsing
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Note modal for "Request Changes" */}
      {noteTarget && (
        <div onClick={() => setNoteTarget(null)} style={{
          position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.7)', zIndex: 500,
          display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
        }}>
          <div onClick={e => e.stopPropagation()} style={{
            background: 'var(--bg-1)', border: '1px solid var(--line-2)', borderRadius: 10,
            padding: 22, width: '100%', maxWidth: 380,
          }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--ink-0)', marginBottom: 6 }}>Request Changes</div>
            <div style={{ fontSize: 12, color: 'var(--ink-3)', marginBottom: 14 }}>
              Add a note for the photographer (optional)
            </div>
            <textarea
              value={noteText} onChange={e => setNoteText(e.target.value)}
              placeholder="e.g. Can you crop tighter? The lighting feels a bit dark…"
              maxLength={500} autoFocus
              style={{
                width: '100%', minHeight: 90, resize: 'vertical',
                background: 'var(--bg-0)', border: '1px solid var(--line-2)', borderRadius: 6,
                color: 'var(--ink-0)', fontSize: 13, fontFamily: 'inherit', padding: '8px 10px',
                outline: 'none',
              }}
            />
            <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 14 }}>
              <button onClick={() => setNoteTarget(null)}
                style={{ height: 32, padding: '0 14px', borderRadius: 6, border: '1px solid var(--line-1)', background: 'transparent', color: 'var(--ink-2)', fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' }}>
                Cancel
              </button>
              <button onClick={submitNote}
                style={{ height: 32, padding: '0 14px', borderRadius: 6, border: 'none', background: '#f4a442', color: '#111', fontSize: 12, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit' }}>
                Send feedback
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// Public share view — loads data from the real API
function PublicShareView({ token }) {
  const [info, setInfo]           = React.useState(null);
  const [items, setItems]         = React.useState([]);
  const [feedback, setFeedback]   = React.useState(new Set());
  const [approvals, setApprovals] = React.useState({});
  const [pins, setPins]           = React.useState(new Set());
  const [error, setError]         = React.useState(null);
  const [lightboxIdx, setLightboxIdx] = React.useState(null);

  React.useEffect(() => {
    fetch(`/api/share/${token}/info`)
      .then(r => r.ok ? r.json() : Promise.reject('not found'))
      .then(data => setInfo(data))
      .catch(() => setError('This gallery link has expired or is no longer available.'));

    fetch(`/api/share/${token}/gallery`)
      .then(r => r.ok ? r.json() : Promise.reject())
      .then(data => {
        const list = data.items || [];
        const fixed = list.map(item => ({
          ...item,
          download_url: `/share/${token}/download/${encodeURIComponent(item.filename)}`,
        }));
        setItems(fixed);
        const liked = new Set(list.filter(i => i.client_liked).map(i => i.filename));
        setFeedback(liked);
        const ap = {};
        list.forEach(i => {
          if (i.approval_status) ap[i.filename] = { status: i.approval_status, note: i.approval_note || '' };
        });
        setApprovals(ap);
      })
      .catch(() => {});

    // Load existing pins
    fetch(`/api/share/${token}/moodboard`)
      .then(r => r.ok ? r.json() : { pins: [] })
      .then(data => setPins(new Set(data.pins || [])))
      .catch(() => {});
  }, [token]);

  if (error) {
    return (
      <div style={{ position: 'fixed', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--bg-0)', flexDirection: 'column', gap: 12 }}>
        <div style={{ width: 48, height: 48, borderRadius: '50%', background: 'var(--bg-2)', border: '1px solid var(--line-1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <IconX size={18}/>
        </div>
        <div className="serif" style={{ fontSize: 28, color: 'var(--ink-1)', letterSpacing: -0.3 }}>Gallery not found</div>
        <div style={{ fontSize: 13, color: 'var(--ink-3)', maxWidth: 320, textAlign: 'center' }}>{error}</div>
      </div>
    );
  }

  if (!info) {
    return (
      <div style={{ position: 'fixed', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--bg-0)' }}>
        <div className="mono" style={{ color: 'var(--ink-3)', fontSize: 11, letterSpacing: 0.1 }}>LOADING…</div>
      </div>
    );
  }

  const handleFeedback = (filename) => {
    const isLiked = feedback.has(filename);
    if (isLiked) {
      fetch(`/api/share/${token}/feedback/${encodeURIComponent(filename)}`, { method: 'DELETE' });
      setFeedback(s => { const n = new Set(s); n.delete(filename); return n; });
    } else {
      fetch(`/api/share/${token}/feedback/${encodeURIComponent(filename)}`, { method: 'POST' });
      setFeedback(s => { const n = new Set(s); n.add(filename); return n; });
    }
  };

  const handleApprove = (filename, status, note) => {
    fetch(`/api/share/${token}/approval/${encodeURIComponent(filename)}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ status, note }),
    });
    setApprovals(prev => {
      const next = { ...prev };
      if (status) next[filename] = { status, note };
      else delete next[filename];
      return next;
    });
  };

  const handlePin = (filename) => {
    const isPinned = pins.has(filename);
    if (isPinned) {
      fetch(`/api/share/${token}/moodboard/${encodeURIComponent(filename)}`, { method: 'DELETE' });
      setPins(s => { const n = new Set(s); n.delete(filename); return n; });
    } else {
      if (pins.size >= MAX_PINS) return; // enforce limit
      fetch(`/api/share/${token}/moodboard/${encodeURIComponent(filename)}`, { method: 'POST' });
      setPins(s => { const n = new Set(s); n.add(filename); return n; });
    }
  };

  if (lightboxIdx !== null) {
    return (
      <Lightbox
        items={items} index={lightboxIdx}
        exifCache={{}}
        onClose={() => setLightboxIdx(null)}
        onNav={(d) => setLightboxIdx(i => Math.max(0, Math.min(items.length - 1, i + d)))}
        onPick={() => {}} onReject={() => {}} onRate={() => {}} onColor={() => {}}
        devOpen={false} setDevOpen={() => {}}
        edits={{}} onEditChange={() => {}} onEditReset={() => {}}
        showExif={false} setShowExif={() => {}}
        baMode={false} setBaMode={() => {}}
        zoom={false} setZoom={() => {}}
        readOnly={true}
      />
    );
  }

  return (
    <ShareView
      files={items}
      share={info}
      token={token}
      onOpen={(i) => setLightboxIdx(i)}
      onClose={null}
      onFeedback={handleFeedback}
      feedback={feedback}
      onApprove={handleApprove}
      approvals={approvals}
      pins={pins}
      onPin={handlePin}
    />
  );
}

Object.assign(window, { ShareView, PublicShareView });
