window.AdCard = function AdCard({
  ad,
  selected,
  onToggle,
  onExpand,
  onDislike,
  minimal,
  showPassVerdict = true,
}) {
  const p = PLATFORMS[ad.platform] || PLATFORMS.foreplay;
  const [thumbnailBroken, setThumbnailBroken] = React.useState(false);
  const [measuredDuration, setMeasuredDuration] = React.useState('');
  const raw = ad.raw && typeof ad.raw === 'object' ? ad.raw : {};
  const hasTranscript = ad.transcript && ad.transcript.length > 0;
  const isFallbackTranscript = ad.transcriptSource === 'copy_fallback'
    || ad.transcript_source === 'copy_fallback'
    || ad.isVoiceTranscript === false;
  const hasVoiceTranscript = hasTranscript && !isFallbackTranscript;
  const transcriptProvider = ad.transcriptProvider
    || ad.transcript_provider
    || raw.transcript_provider
    || (raw.brandsearch_transcript && raw.brandsearch_transcript.provider)
    || null;
  const transcriptModel = ad.transcriptModel
    || ad.transcript_model
    || raw.transcript_model
    || (raw.brandsearch_transcript && raw.brandsearch_transcript.model)
    || null;
  const transcriptSourceLabel = isFallbackTranscript
    ? 'fallback copy'
    : transcriptProvider
      ? `via ${transcriptProvider}`
      : hasVoiceTranscript
        ? 'source transcript'
        : null;
  const creativeUrl = ad.creativeUrl
    || ad.videoUrl
    || raw.creative_url
    || raw.creativeUrl
    || raw.video
    || raw.video_url
    || null;
  const thumbnailUrl = ad.thumbnailUrl
    || raw.thumbnail_url
    || raw.thumbnail
    || raw.thumb_url
    || raw.thumb
    || raw.image_url
    || raw.image
    || null;
  const hasVideo = !!creativeUrl;
  const hasThumbnail = !!thumbnailUrl && !thumbnailBroken;
  const truncationWarning = !!ad.hasTruncationWarning
    || String(ad.truncationStatus || ad.truncation_status || '').toLowerCase() === 'truncated';
  const truncationTitle = ad.truncationReason
    || ad.truncation_reason
    || 'Video file appears incomplete; retained as discovery signal but skipped for remodel.';
  // AdSpy / BrandSearch transcripts are generated server-side via Whisper /
  // ElevenLabs after the ad is fetched. Until the transcript hydration call
  // returns we badge the card so users know something is in flight \u2014
  // otherwise the missing transcript looks like a permanent gap.
  const transcriptStatus = ad.transcriptStatus || null;
  const transcriptPending = hasVideo
    && !hasVoiceTranscript
    && (ad.platform === 'adspy' || ad.platform === 'brandsearch')
    && transcriptStatus !== 'fallback'
    && transcriptStatus !== 'unavailable'
    && transcriptStatus !== 'empty';
  const showVideoThumb = hasVideo && !hasThumbnail;
  const thumbBackground = (hasVideo || thumbnailUrl) ? '#111827' : ad.thumb;
  const score = ad.score;
  const scoreText = (typeof score === 'number')
    ? (score > 10 ? Math.round(score) : score.toFixed(1))
    : null;
  const passThresholdRaw = Number(
    ad.passThreshold ?? ad.pass_threshold ?? ad.scoreThreshold ?? ad.score_threshold,
  );
  const passThreshold = Number.isFinite(passThresholdRaw) ? passThresholdRaw : null;
  const passVerdict = (typeof ad.passes === 'boolean')
    ? ad.passes
    : ((typeof score === 'number' && passThreshold !== null) ? score >= passThreshold : null);
  const passVerdictTitle = passThreshold !== null
    ? `Configured winner threshold: ${passThreshold.toFixed(1)}`
    : 'Pass/fail verdict from scorer';
  const metricChips = RESILIA_ADAPT.metricChips(ad);
  const isCurrentRun = !!(ad.currentSearchRun || ad.currentAutoresearchRun || ad.currentPipelineMove);

  const displayDuration = ad.duration || measuredDuration;
  const formatDuration = (seconds) => {
    if (!Number.isFinite(seconds) || seconds <= 0) return '';
    const total = Math.round(seconds);
    const h = Math.floor(total / 3600);
    const m = Math.floor((total % 3600) / 60);
    const s = total % 60;
    if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
    return `${m}:${String(s).padStart(2, '0')}`;
  };
  const captureDuration = (video) => {
    const formatted = formatDuration(video.duration);
    if (formatted) setMeasuredDuration(formatted);
  };

  const openExpand = (e, opts = {}) => { e.stopPropagation(); onExpand(ad, opts); };
  const dislikeAd = (e) => {
    e.stopPropagation();
    if (typeof onDislike === 'function') onDislike(ad);
  };

  return (
    <div
      className={`ad ${selected ? 'sel' : ''} ${minimal ? 'minimal' : ''} ${isCurrentRun ? 'current-run' : ''} ${truncationWarning ? 'truncation-warning' : ''}`}
      data-ad-id={ad.id}
      title={truncationWarning ? truncationTitle : undefined}
      tabIndex={-1}
      onClick={onToggle}
    >
      <div className="ad-thumb" style={{ background: thumbBackground }}>
        {hasThumbnail && (
          <img
            className="ad-thumb-img"
            src={thumbnailUrl}
            alt={`${ad.brand || 'Ad'} thumbnail`}
            loading="lazy"
            onError={() => setThumbnailBroken(true)}
          />
        )}
        {showVideoThumb && (
          <video
            className="ad-thumb-video"
            src={creativeUrl}
            muted
            playsInline
            preload="metadata"
            onLoadedMetadata={(e) => {
              captureDuration(e.currentTarget);
              try { e.currentTarget.currentTime = 0.15; } catch { /* preview seek can fail on some streams */ }
            }}
            onError={(e) => { e.currentTarget.style.display = 'none'; }}
          />
        )}
        {hasVideo && hasThumbnail && !displayDuration && (
          <video
            src={creativeUrl}
            preload="metadata"
            muted
            playsInline
            style={{ position: 'absolute', width: 1, height: 1, opacity: 0, pointerEvents: 'none' }}
            onLoadedMetadata={(e) => captureDuration(e.currentTarget)}
          />
        )}
        <div className={`ad-cbx ${selected ? 'on' : ''}`} onClick={(e) => { e.stopPropagation(); onToggle(); }}>
          {selected && <Icon name="check" size={10} />}
        </div>
        <span className="ad-plat" style={{ background: p.bg, color: p.color }}>
          <span className="ad-plat-dot" style={{ background: p.color }} />
          {p.label}
        </span>
        {isCurrentRun && <span className="ad-current-run">{ad.currentRunLabel || 'Current'}</span>}
        {onDislike && (
          <button
            type="button"
            className="ad-dislike"
            onClick={dislikeAd}
            title="Dislike and hide this creative permanently"
          >
            <Icon name="thumbDown" size={13} />
          </button>
        )}
        <button
          className="ad-play"
          onClick={(e) => openExpand(e, { autoPlay: hasVideo })}
          title={hasVideo ? 'Play in drawer' : 'Open details'}
          style={{ border: 'none', cursor: 'pointer' }}
        >
          <Icon name="play" size={14} />
        </button>
        {displayDuration && <span className="ad-duration">{displayDuration}</span>}
        {truncationWarning && (
          <span className="ad-truncation-badge" title={truncationTitle}>
            Incomplete video
          </span>
        )}
        <button
          className="ad-expand"
          onClick={openExpand}
          title="Open details + transcript"
        >
          <Icon name="expand" size={12} />
        </button>
      </div>
      {!minimal && (
        <div className="ad-body">
          <div className="ad-brand-row">
            <span className="ad-brand">{ad.brand}</span>
            {hasTranscript && (
              <span
                className="ad-has-transcript"
                title={isFallbackTranscript ? 'Caption/description fallback, not a real video transcript' : 'Real transcript available'}
              >
                <Icon name="caption" size={11} />
              </span>
            )}
            {isFallbackTranscript && hasTranscript && (
              <span
                className="ad-transcript-fallback"
                title="Using platform caption/description fallback"
                style={{ fontSize: 11, color: 'var(--fg-3)' }}
              >
                fallback
              </span>
            )}
            {transcriptSourceLabel && (
              <span
                className="ad-transcript-source"
                title={transcriptModel ? `Transcript model: ${transcriptModel}` : 'Transcript source'}
                style={{ fontSize: 11, color: 'var(--fg-3)' }}
              >
                {transcriptSourceLabel}
              </span>
            )}
            {!hasTranscript && transcriptPending && (
              <span
                className="ad-transcript-pending"
                title="Generating transcript from video (Whisper). Typically 5–20s per ad depending on length."
                style={{
                  display: 'inline-flex',
                  alignItems: 'center',
                  gap: 4,
                  fontSize: 11,
                  color: 'var(--fg-3)',
                }}
              >
                <Icon name="loader" size={11} />
                Transcribing… (~5–20s)
              </span>
            )}
          </div>
          {ad.directionLabel && (
            <div className="ad-direction" title={`Direction: ${ad.directionLabel}`}>
              <Icon name="target" size={10} />
              <span>{ad.directionLabel}</span>
            </div>
          )}
          {ad.searchReturnLabel && (
            <div className="ad-search-return" title={`Returned by: ${ad.searchReturnLabel}`}>
              <Icon name="search" size={10} />
              <span>{ad.searchReturnLabel}</span>
            </div>
          )}
          <div className="ad-copy">{ad.copy}</div>
          <div className="ad-meta">
            {scoreText !== null && (
              <span
                className={`ad-score${ad.fallbackSource ? ' ad-score-fallback' : ''}`}
                title={ad.fallbackSource
                  ? `No ${ad.fallbackSource} ads matched the query — this is a top-of-platform fallback. Score reflects general quality, not query match.`
                  : 'Evaluator score (0–10)'}
                style={ad.fallbackSource ? { color: '#a01c1c', borderColor: '#f5c2c2' } : undefined}
              >
                <Icon name="trend" size={10} />
                {scoreText}
                {ad.fallbackSource ? ' · fallback' : ''}
              </span>
            )}
            {showPassVerdict && passVerdict !== null && (
              <span
                className={`ad-pass-verdict ${passVerdict ? 'pass' : 'fail'}`}
                title={passVerdictTitle}
                style={{
                  color: passVerdict ? '#047857' : '#b42318',
                  border: `1px solid ${passVerdict ? '#a7f3d0' : '#fecaca'}`,
                  borderRadius: 999,
                  padding: '2px 6px',
                  fontSize: 11,
                  fontWeight: 700,
                  background: passVerdict ? '#ecfdf5' : '#fff1f2',
                }}
              >
                {passVerdict ? 'Pass' : 'Fail'}
              </span>
            )}
            {metricChips.map(({ label, value, title, className }) => (
              <span key={label} className={`ad-metric-chip ${className}`.trim()} title={title}>
                <span className="ad-metric-label">{label}</span>
                {value}
              </span>
            ))}
            {ad.daysRunning ? <span>{ad.daysRunning}d running</span> : null}
            {ad.arrivedAtMs ? (
              <TimeAgo
                ms={ad.arrivedAtMs}
                className="ad-arrived-at"
                title={`Arrived in this section ${new Date(ad.arrivedAtMs).toLocaleString()}`}
              />
            ) : null}
          </div>
        </div>
      )}
      {minimal && (
        <div className="ad-body ad-body-min">
          <div className="ad-brand">{ad.brand}</div>
          {scoreText !== null && (
            <span className="ad-score"><Icon name="trend" size={10} />{scoreText}</span>
          )}
          {ad.arrivedAtMs ? (
            <TimeAgo ms={ad.arrivedAtMs} className="ad-arrived-at" iconSize={9} />
          ) : null}
        </div>
      )}
    </div>
  );
};
