// Shared Playground component — drives every "try the API" surface on the site.
// Reads from ENDPOINTS / GROUPS / playgroundEnabledIds() / priceFor / docsHrefFor.
// Replaces the hardcoded V1Playground / V2Console hero panels.

const PG_DARK = {
  bg:         '#0e1115',
  bgDeep:     '#0a0c0f',
  panel:      '#11151a',
  panelAlt:   '#0d1014',
  line:       '#1f2228',
  line2:      '#171a1f',
  ink:        '#e6e9ed',
  ink2:       '#cdd1d6',
  ink3:       '#8b929b',
  ink4:       '#5b6573',
};

const PG_DEMO_MEDIA_URL = 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4';
const PG_WORKFLOWS = [
  { name: 'Find a funny clip', endpoint: 'audio/detect-laughter', steps: ['detect-laughter', 'detect-silence', 'clip-near'] },
  { name: 'Create a thumbnail', endpoint: 'video/best-frames', steps: ['best-frames', 'thumbnail-score'] },
  { name: 'Find exact words', endpoint: 'audio/find-words', steps: ['transcribe', 'find-words', 'clip-window'] },
  { name: 'Build topic sections', endpoint: 'audio/topic-segments', steps: ['transcribe', 'topic-segments', 'timeline-merge'] },
  { name: 'Cut dead space', endpoint: 'audio/detect-silence', steps: ['detect-silence', 'clip-window'] },
  { name: 'Find readable moments', endpoint: 'video/text-frames', steps: ['text-frames', 'dedupe-frames'] },
];

const pgIsTimeControl = (ctl) =>
  ctl && ctl.type === 'number' && /^(start|end|t|window|before|after|target_duration|min_duration|max_duration|min_gap|min_segment|resolution|min_clip|max_clip)$/.test(ctl.key);

const pgControlLabel = (ctl) => ({
  media_url: 'Source media URL',
  local_path: 'Source media',
  start: 'Start time',
  end: 'End time',
  t: 'Target timestamp',
  window: 'Search window',
  target_duration: 'Target duration',
  min_duration: 'Minimum duration',
  max_frames: 'Frames to return',
  min_gap: 'Minimum gap',
  min_segment: 'Minimum segment',
  resolution: 'Time resolution',
  quality: 'Processing quality',
  format: 'Output format',
  threshold_db: 'Silence threshold',
  speech_threshold: 'Speech threshold',
  threshold: 'Threshold',
  method: 'Method',
  match: 'Match mode',
  words: 'Timestamped words',
  queries: 'Search queries',
  transcript: 'Transcript segments',
  non_speech_volume: 'Non-speech volume',
  min_speakers: 'Min speakers',
  max_speakers: 'Max speakers',
  num_speakers: 'Known speakers',
  before: 'Context before',
  after: 'Context after',
  max_duration: 'Maximum duration',
  sensitivity: 'Sensitivity',
  estimated_speakers: 'Estimated speakers',
}[ctl.key] || ctl.label || ctl.key);

const pgControlHint = (ctl) => {
  if (ctl.key === 'start') return 'where processing begins';
  if (ctl.key === 'end') return 'blank means end of media';
  if (ctl.key === 'before') return 'context before';
  if (ctl.key === 'after') return 'context after';
  if (pgIsTimeControl(ctl)) return 'DD:HH:MM:SS';
  return ctl.hint;
};

const pgFormatDuration = (seconds) => {
  const n = Number(seconds);
  if (!Number.isFinite(n)) return '';
  const whole = Math.max(0, Math.floor(n));
  const d = Math.floor(whole / 86400);
  const h = Math.floor((whole % 86400) / 3600);
  const m = Math.floor((whole % 3600) / 60);
  const s = whole % 60;
  const pad = (x) => String(x).padStart(2, '0');
  return `${pad(d)}:${pad(h)}:${pad(m)}:${pad(s)}`;
};

const pgParseDuration = (text) => {
  if (typeof text === 'number') return text;
  const raw = String(text || '').trim();
  if (!raw) return '';
  if (!raw.includes(':')) {
    const n = Number(raw);
    return Number.isFinite(n) ? n : 0;
  }
  const parts = raw.split(':').map(p => Number(p || 0));
  while (parts.length < 4) parts.unshift(0);
  const [d, h, m, s] = parts.slice(-4);
  return (d * 86400) + (h * 3600) + (m * 60) + s;
};

const pgDisplayRequest = (request, controls = []) => {
  const out = { ...request };
  controls.forEach(c => {
    if (pgIsTimeControl(c) && out[c.key] !== '' && out[c.key] != null) out[c.key] = pgFormatDuration(out[c.key]);
  });
  return out;
};

const PG_DEFAULT_MEDIA_SECONDS = 5;
const pgEndpointFromStep = (step) => {
  if (!step) return null;
  if (ENDPOINTS[step]) return step;
  return endpointIds().find(id => id.endsWith(`/${step}`) || id.replace('/', '-') === step) || null;
};
const pgWorkflowEndpointIds = (workflow) =>
  (workflow.steps || [workflow.endpoint]).map(pgEndpointFromStep).filter(Boolean);
const pgParamsForEndpoint = (endpointId, activeEndpointId, activeRequest, mediaUrl) => {
  const endpoint = ENDPOINTS[endpointId] || {};
  const base = endpointId === activeEndpointId ? { ...activeRequest } : pgDefaultParamsFor(endpoint);
  if ('media_url' in base) base.media_url = mediaUrl || base.media_url || PG_DEMO_MEDIA_URL;
  if ('local_path' in base) base.local_path = mediaUrl || base.local_path || PG_DEMO_MEDIA_URL;
  return base;
};
const pgResultPayload = (response) => response?.results || response?.job?.result || response?.result || response;
const pgApplyPreviousResultToParams = (endpointId, params, previousResult) => {
  const next = { ...(params || {}) };
  const controls = ENDPOINTS[endpointId]?.playgroundControls || [];
  const needsFrames = controls.some(control => control.key === 'frames');
  if (needsFrames && Array.isArray(previousResult?.frames)) next.frames = previousResult.frames;
  return next;
};
const pgBillableSecondsFor = (endpointId, mediaSeconds, params = {}) => {
  const unit = pricingUnit(endpointId);
  if (unit === 'request') return 0;
  if (unit === 'clip_min') {
    if (endpointId === 'video/clip-window') {
      const start = Number(params.start || 0);
      const end = Number(params.end || 0);
      if (Number.isFinite(start) && Number.isFinite(end) && end > start) return end - start;
    }
    if (endpointId === 'video/clip-near') {
      const target = Number(params.target_duration || params.max_duration || 0);
      if (Number.isFinite(target) && target > 0) return target;
    }
  }
  return Number.isFinite(mediaSeconds) && mediaSeconds > 0 ? mediaSeconds : PG_DEFAULT_MEDIA_SECONDS;
};
const pgEstimateEndpointUsd = (endpointId, mediaSeconds, params = {}) => {
  const unit = pricingUnit(endpointId);
  const rate = priceFor(endpointId);
  if (unit === 'request') return rate;
  return rate * (pgBillableSecondsFor(endpointId, mediaSeconds, params) / 60);
};

// ───────────────────────────────────────────────────────────── primitives ──

const PgInput = ({ ctl, value, onChange }) => {
  const inputBase = {
    width: '100%', boxSizing: 'border-box',
    background: PG_DARK.bgDeep, border: `1px solid ${PG_DARK.line}`, borderRadius: 7,
    padding: '8px 10px', minHeight: 34,
    fontFamily: wf.mono, fontSize: 12.5, color: PG_DARK.ink,
    outline: 'none',
  };
  if (ctl.type === 'toggle') {
    return (
      <div onClick={() => onChange(!value)} style={{
        display: 'inline-flex', alignItems: 'center', gap: 8,
        padding: '6px 10px', borderRadius: 7, cursor: 'pointer',
        border: `1px solid ${PG_DARK.line}`, background: PG_DARK.bgDeep,
        fontFamily: wf.mono, fontSize: 12, color: PG_DARK.ink2,
      }}>
        <span style={{
          width: 24, height: 14, borderRadius: 999, position: 'relative',
          background: value ? 'var(--accent)' : '#2a2f37',
          transition: 'background .12s',
        }}>
          <span style={{
            position: 'absolute', top: 1, left: value ? 11 : 1,
            width: 12, height: 12, borderRadius: 999, background: '#fff',
            transition: 'left .12s',
          }} />
        </span>
        <span>{value ? 'true' : 'false'}</span>
      </div>
    );
  }
  if (ctl.type === 'select') {
    return (
      <select value={value} onChange={e => onChange(e.target.value)} style={{
        ...inputBase, appearance: 'none',
        backgroundImage: `linear-gradient(45deg, transparent 50%, ${PG_DARK.ink3} 50%), linear-gradient(135deg, ${PG_DARK.ink3} 50%, transparent 50%)`,
        backgroundPosition: `calc(100% - 14px) 14px, calc(100% - 9px) 14px`,
        backgroundSize: '5px 5px, 5px 5px',
        backgroundRepeat: 'no-repeat',
        paddingRight: 28,
      }}>
        {ctl.options.map(o => <option key={o} value={o} style={{ background: PG_DARK.bgDeep }}>{o}</option>)}
      </select>
    );
  }
  if (ctl.type === 'json') {
    const text = typeof value === 'string' ? value : JSON.stringify(value);
    return (
      <input type="text" value={text} onChange={e => {
        try { onChange(JSON.parse(e.target.value)); }
        catch { onChange(e.target.value); }
      }} style={inputBase} spellCheck={false} />
    );
  }
  if (pgIsTimeControl(ctl)) {
    return (
      <input
        type="text"
        value={pgFormatDuration(value)}
        placeholder="00:00:00:00"
        onChange={e => onChange(pgParseDuration(e.target.value))}
        style={inputBase}
        spellCheck={false}
      />
    );
  }
  // url / text / number
  return (
    <input
      type={ctl.type === 'number' ? 'number' : 'text'}
      value={value ?? ''}
      min={ctl.min} max={ctl.max} step={ctl.step}
      onChange={e => onChange(ctl.type === 'number' ? Number(e.target.value) : e.target.value)}
      style={inputBase}
      spellCheck={false}
    />
  );
};

// JSON syntax-highlighter (very small)
const JsonBlock = ({ value, maxHeight = 280 }) => {
  const text = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
  return (
    <pre style={{
      margin: 0, padding: '12px 14px',
      fontFamily: wf.mono, fontSize: 12.5, lineHeight: 1.65,
      color: PG_DARK.ink2,
      background: PG_DARK.bgDeep,
      maxHeight, overflow: 'auto',
      whiteSpace: 'pre',
    }}>
      {text.split('\n').map((ln, i) => {
        // string highlighter
        const parts = []; let key = 0; let last = 0;
        const re = /"([^"]*)"(\s*:)?/g; let m;
        while ((m = re.exec(ln)) !== null) {
          if (m.index > last) parts.push(<span key={key++}>{ln.slice(last, m.index)}</span>);
          const isKey = !!m[2];
          parts.push(<span key={key++} style={{ color: isKey ? '#9bb0d4' : '#9bd17b' }}>"{m[1]}"</span>);
          if (isKey) parts.push(<span key={key++}>:</span>);
          last = m.index + m[0].length;
        }
        if (last < ln.length) {
          // number highlight in trailing chunk
          const tail = ln.slice(last);
          const nm = tail.split(/(-?\d+\.?\d*)/);
          parts.push(...nm.map((seg, i) =>
            /^-?\d+\.?\d*$/.test(seg)
              ? <span key={key++} style={{ color:'#e6c07b' }}>{seg}</span>
              : <span key={key++}>{seg}</span>
          ));
        }
        return <div key={i} style={{ minHeight: 18 }}>{parts.length ? parts : '\u00a0'}</div>;
      })}
    </pre>
  );
};

// ──────────────────────────────────────────────────────── response previews ──

const FrameTile = ({ t, score, label, url }) => (
  <div style={{ background: PG_DARK.panel, border: `1px solid ${PG_DARK.line}`, borderRadius: 8, overflow: 'hidden' }}>
    <div style={{
      aspectRatio: '16/9',
      background: `repeating-linear-gradient(45deg, #1a1f25 0 6px, #161a20 6px 12px)`,
      position: 'relative',
    }}>
      {url && <img src={url} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />}
      <span style={{
        position: 'absolute', top: 6, left: 6,
        fontFamily: wf.mono, fontSize: 10, padding: '1px 5px', borderRadius: 3,
        background: 'rgba(0,0,0,.55)', color: PG_DARK.ink,
      }}>t={pgFormatDuration(t)}</span>
      {label && <span style={{
        position:'absolute', bottom: 6, left: 6, right: 6,
        fontFamily: wf.mono, fontSize: 10, color: PG_DARK.ink2,
        overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
      }}>{label}</span>}
    </div>
    {typeof score === 'number' && (
      <div style={{ padding: '6px 8px', display: 'flex', justifyContent: 'space-between', fontFamily: wf.mono, fontSize: 10.5, color: PG_DARK.ink3 }}>
        <span>score</span><span style={{ color: 'var(--accent)' }}>{score.toFixed(2)}</span>
      </div>
    )}
  </div>
);

const PreviewFrames = ({ ep, response }) => {
  const frames = response.frames || (response.frame ? [response.frame] : []);
  return (
    <div>
      <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1, color: PG_DARK.ink3, textTransform: 'uppercase', marginBottom: 8 }}>
        {frames.length} frame{frames.length === 1 ? '' : 's'}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))', gap: 8 }}>
        {frames.slice(0, 6).map((f, i) => (
          <FrameTile key={i} t={f.t ?? 0} score={f.thumbnail_score ?? f.score} label={f.text || f.kind} url={f.url} />
        ))}
      </div>
    </div>
  );
};

const PreviewTimelineSegments = ({ ep, response }) => {
  const timelineValue = response.timeline?.segments || response.timeline || [];
  const segs = response.segments || response.chunks || response.ranges || (Array.isArray(timelineValue) ? timelineValue : []);
  const cuts = response.cuts || [];
  const totalEnd = Math.max(
    ...segs.map(s => s.end ?? (s.t ? s.t + (s.dur || 0) : 0)),
    ...cuts,
    response.media_seconds || 100,
  );
  const norm = (x) => `${(x / totalEnd) * 100}%`;
  const palette = ['var(--accent)', '#9bd17b', '#e6c07b', '#c678dd', '#56b6c2'];
  return (
    <div>
      <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1, color: PG_DARK.ink3, textTransform: 'uppercase', marginBottom: 8 }}>
        timeline · {segs.length || cuts.length} segment{(segs.length || cuts.length) === 1 ? '' : 's'}
      </div>
      <div style={{ position: 'relative', height: 56, borderRadius: 8, background: PG_DARK.bgDeep, border: `1px solid ${PG_DARK.line}`, overflow: 'hidden' }}>
        {segs.map((s, i) => {
          const start = s.start ?? s.t ?? 0;
          const end = s.end ?? (s.t ? s.t + (s.dur || 1) : start + 1);
          const c = palette[i % palette.length];
          return (
            <div key={i} style={{
              position: 'absolute', top: 8, bottom: 8,
              left: norm(start), width: norm(end - start),
              background: `color-mix(in oklch, ${c} 40%, transparent)`,
              borderLeft: `2px solid ${c}`, borderRight: `2px solid ${c}`,
              borderRadius: 3,
            }} title={`${pgFormatDuration(start)}-${pgFormatDuration(end)}`} />
          );
        })}
        {cuts.map((t, i) => (
          <span key={`c${i}`} style={{
            position: 'absolute', top: 0, bottom: 0, left: norm(t),
            width: 2, background: 'var(--accent)',
          }} />
        ))}
      </div>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 10 }}>
        {segs.slice(0, 5).map((s, i) => {
          const start = s.start ?? s.t ?? 0;
          const end = s.end ?? (s.t ? s.t + (s.dur || 1) : start + 1);
          return (
            <span key={i} style={{
              fontFamily: wf.mono, fontSize: 10.5, color: PG_DARK.ink2,
              padding: '3px 7px', borderRadius: 999,
              background: PG_DARK.panel, border: `1px solid ${PG_DARK.line}`,
            }}>
              {pgFormatDuration(start)}-{pgFormatDuration(end)} {s.topic || s.reason || s.kind ? `· ${s.topic || s.reason || s.kind}` : ''}
            </span>
          );
        })}
      </div>
    </div>
  );
};

const PreviewClip = ({ ep, response }) => {
  const outputUrl = response.url || response.output_file?.url || response.output_file?.storage?.url;
  const isAudioOutput = (ep?.group === 'audio') || /^audio\//.test(ep?.id || '') || /^audio\//.test(response?.source || '') || ['mp3', 'wav', 'm4a'].includes(String(response?.format || '').toLowerCase());
  return (
    <div>
      <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1, color: PG_DARK.ink3, textTransform: 'uppercase', marginBottom: 8 }}>
        playable clip
      </div>
      <div style={{
        borderRadius: 10, border: `1px solid ${PG_DARK.line}`, background: PG_DARK.panel, overflow: 'hidden',
      }}>
        {outputUrl ? (
          isAudioOutput ? (
            <div style={{ padding: 18, background: PG_DARK.bgDeep }}>
              <audio src={outputUrl} controls style={{ width: '100%', display: 'block' }} />
            </div>
          ) : (
            <video src={outputUrl} controls style={{
              width: '100%', aspectRatio: '16/9', display: 'block',
              background: PG_DARK.bgDeep,
            }} />
          )
        ) : (
          <div style={{
        aspectRatio: '16/9',
        background: `linear-gradient(135deg, #1a1f25 0%, #0f1318 100%),
                     repeating-linear-gradient(0deg, rgba(255,255,255,.02) 0 1px, transparent 1px 4px)`,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        position: 'relative',
      }}>
        <span style={{
          width: 56, height: 56, borderRadius: 999,
          background: 'rgba(255,255,255,.08)', border: `1px solid ${PG_DARK.line}`,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          color: 'var(--accent)', fontSize: 22, paddingLeft: 4,
        }}>▶</span>
        <span style={{
          position: 'absolute', bottom: 10, left: 12, right: 12, height: 4, borderRadius: 2,
          background: 'rgba(255,255,255,.08)',
        }}>
          <span style={{ display: 'block', height: '100%', width: '32%', borderRadius: 2, background: 'var(--accent)' }} />
        </span>
          </div>
        )}
        <div style={{ padding: '10px 12px', display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto', gap: 10, alignItems: 'center', fontFamily: wf.mono, fontSize: 11.5, color: PG_DARK.ink2 }}>
          <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
            {outputUrl || 'https://cdn.momentiq.dev/clips/abc123.mp4'}
          </span>
          <span>
            {typeof response.duration === 'number' ? pgFormatDuration(response.duration) :
             (response.start != null && response.end != null ? pgFormatDuration(response.end - response.start) : '00:00:00:32')}
          </span>
        </div>
        {outputUrl && (
          <div style={{ display: 'flex', gap: 8, padding: '0 12px 12px' }}>
            <a href={outputUrl} download style={{ ...pgBtn(true), textDecoration: 'none', display: 'inline-flex', padding: '6px 10px', fontSize: 12 }}>
              Download result
            </a>
          </div>
        )}
      </div>
    </div>
  );
};

const PreviewSignal = ({ ep, response }) => {
  // collect rows from any of: results, silences, peaks, music, mixed, cuts, speakers, overlaps, event
  const rows = []; const colorFor = (k) => ({
    laughter:'#9bd17b', silence:'#8b929b', music:'#c678dd', mixed:'#e6c07b',
    energy_peak:'var(--accent)', speaker:'var(--accent)', overlap:'#e08e6e',
    cut:'var(--accent)', signal:'var(--accent)',
  })[k] || 'var(--accent)';

  if (response.results) response.results.forEach(r => rows.push({ kind: ep.title.replace('detect-',''), t: r.t, dur: r.dur, conf: r.conf }));
  if (response.segments) response.segments.forEach(r => rows.push({ kind: r.kind || ep.title, t: r.start ?? r.t, dur: (r.end != null && r.start != null) ? r.end - r.start : r.dur, conf: r.confidence ?? r.score, label: r.metadata?.speaker_label || r.metadata?.title }));
  if (response.matches) response.matches.forEach(r => rows.push({ kind: r.kind || 'word_match', t: r.start, dur: (r.end || r.start) - r.start, conf: r.confidence ?? r.score, label: r.metadata?.text || r.metadata?.query }));
  if (response.silences) response.silences.forEach(r => rows.push({ kind:'silence', t: r.start, dur: r.end - r.start }));
  if (response.peaks) response.peaks.forEach(r => rows.push({ kind:'energy_peak', t: r.t, conf: r.score }));
  if (response.music) response.music.forEach(r => rows.push({ kind:'music', t: r.start, dur: r.end - r.start, conf: r.confidence }));
  if (response.cuts && Array.isArray(response.cuts)) response.cuts.forEach(c => rows.push({ kind:'cut', t: c.t ?? c, conf: c.quality, reason: c.reason }));
  if (response.speakers) response.speakers.forEach(s => rows.push({ kind:`speaker ${s.id}`, t: 0, dur: 0, label: `${(s.share*100).toFixed(0)}% · ${s.segments} segments` }));
  if (response.overlaps) response.overlaps.forEach(o => rows.push({ kind:'overlap', t: o.t, dur: o.dur, label: (o.ids||[]).join('+') }));
  if (response.event) rows.push({ kind: response.event.kind, t: response.event.t, dur: response.event.dur, label: `Δ ${pgFormatDuration(response.delta || 0)}` });

  if (rows.length === 0 && Array.isArray(response.timeline)) response.timeline.forEach(e => rows.push({ kind: e.kind, t: e.t ?? e.start, dur: e.dur ?? ((e.end || e.start) - e.start) }));
  if (rows.length === 0 && Array.isArray(response.timeline?.segments)) response.timeline.segments.forEach(e => rows.push({ kind: e.kind, t: e.t ?? e.start, dur: e.dur ?? ((e.end || e.start) - e.start) }));

  return (
    <div>
      <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1, color: PG_DARK.ink3, textTransform: 'uppercase', marginBottom: 8 }}>
        {rows.length} event{rows.length === 1 ? '' : 's'}
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {rows.slice(0, 6).map((r, i) => (
          <div key={i} style={{
            display: 'grid', gridTemplateColumns: '110px 90px 70px 1fr',
            alignItems: 'center', gap: 10,
            padding: '8px 10px', borderRadius: 8,
            background: PG_DARK.panel, border: `1px solid ${PG_DARK.line}`,
            fontFamily: wf.mono, fontSize: 12, color: PG_DARK.ink2,
          }}>
            <span style={{ display:'inline-flex', alignItems:'center', gap:6, color: PG_DARK.ink }}>
              <span style={{ width:6, height:6, borderRadius:999, background: colorFor(r.kind) }} />
              {r.kind}
            </span>
            <span style={{ color: PG_DARK.ink3 }}>t={pgFormatDuration(r.t || 0)}</span>
            <span style={{ color: PG_DARK.ink3 }}>{r.dur ? pgFormatDuration(r.dur) : '—'}</span>
            <span style={{ color: 'var(--accent)' }}>
              {r.conf != null ? `conf ${r.conf.toFixed(2)}` : (r.label || r.reason || '')}
            </span>
          </div>
        ))}
      </div>
    </div>
  );
};

const PREVIEWS = {
  'frames':             PreviewFrames,
  'timeline-segments':  PreviewTimelineSegments,
  'clip':               PreviewClip,
  'signal':             PreviewSignal,
};

const PreviewWaiting = ({ chain, mediaSeconds }) => (
  <div style={{
    minHeight: 210, borderRadius: 10, background: PG_DARK.panel,
    border: `1px solid ${PG_DARK.line}`, display: 'flex',
    alignItems: 'center', justifyContent: 'center', textAlign: 'center',
    padding: 18, boxSizing: 'border-box',
  }}>
    <div>
      <div style={{ fontFamily: wf.sans, fontSize: 15, color: PG_DARK.ink, fontWeight: 650 }}>
        Output appears here after processing
      </div>
      <div style={{ marginTop: 8, fontFamily: wf.sans, fontSize: 13, lineHeight: 1.45, color: PG_DARK.ink3 }}>
        Choose a source file, pick one or more endpoints, then run the chain.
      </div>
      <div style={{ marginTop: 10, fontFamily: wf.mono, fontSize: 11, color: PG_DARK.ink3 }}>
        {chain.length} endpoint{chain.length === 1 ? '' : 's'} selected - {pgFormatDuration(mediaSeconds)}
      </div>
    </div>
  </div>
);

// ───────────────────────────────────────────────────────── grouped selector ──

const GroupedSelect = ({ value, onChange }) => {
  return (
    <select value={value} onChange={e => onChange(e.target.value)} style={{
      width: '100%', boxSizing: 'border-box',
      background: PG_DARK.bgDeep, border: `1px solid ${PG_DARK.line}`, borderRadius: 7,
      padding: '8px 10px', minHeight: 36,
      fontFamily: wf.mono, fontSize: 13, color: PG_DARK.ink,
      appearance: 'none',
      backgroundImage: `linear-gradient(45deg, transparent 50%, ${PG_DARK.ink3} 50%), linear-gradient(135deg, ${PG_DARK.ink3} 50%, transparent 50%)`,
      backgroundPosition: `calc(100% - 14px) 16px, calc(100% - 9px) 16px`,
      backgroundSize: '5px 5px, 5px 5px',
      backgroundRepeat: 'no-repeat',
      paddingRight: 28,
    }}>
      {Object.values(GROUPS).map(g => {
        const ids = endpointIdsByGroup(g.id).filter(id => ENDPOINTS[id].playgroundEnabled !== false);
        if (ids.length === 0) return null;
        return (
          <optgroup key={g.id} label={g.name + '  (' + g.pathPrefix + ')'} style={{ background: PG_DARK.bgDeep, color: g.color }}>
            {ids.map(id => (
              <option key={id} value={id} style={{ background: PG_DARK.bgDeep, color: GROUPS[ENDPOINTS[id].group].color }}>
                {ENDPOINTS[id].path}
              </option>
            ))}
          </optgroup>
        );
      })}
    </select>
  );
};

// ───────────────────────────────────────────────────────────────── main ────

// ── analytics helpers ─────────────────────────────────────────────────────
// Safe metadata only — never includes the media_url or full request body.
const pgEpMeta = (id) => {
  const ep = ENDPOINTS[id] || {};
  return {
    endpoint_id:        id,
    endpoint_path:      ep.path,
    endpoint_group:     ep.group,
    pricing_unit:       (typeof pricingUnit === 'function') ? pricingUnit(id) : undefined,
    fake_response_type: ep.fakeResponseType,
  };
};
const safeFileExt = (s) => {
  if (typeof s !== 'string') return undefined;
  const m = s.match(/\.([a-z0-9]{2,5})(?:[?#]|$)/i);
  return m ? m[1].toLowerCase() : undefined;
};
const pgInitialEndpoint = (fallback) => {
  try {
    const hashQuery = String(window.location.hash || '').split('?')[1] || '';
    const fromHash = new URLSearchParams(hashQuery).get('endpoint');
    const fromUrl = fromHash || new URLSearchParams(window.location.search).get('endpoint');
    return fromUrl && ENDPOINTS[fromUrl] && ENDPOINTS[fromUrl].playgroundEnabled !== false ? fromUrl : fallback;
  } catch {
    return fallback;
  }
};
const pgDefaultParamsFor = (endpoint) => {
  const next = { ...(endpoint.defaultRequest || endpoint.requestExample) };
  (endpoint.playgroundControls || []).forEach(c => {
    if (next[c.key] == null && c.default != null) next[c.key] = c.default;
  });
  if ('media_url' in next) next.media_url = PG_DEMO_MEDIA_URL;
  if ('local_path' in next) next.local_path = PG_DEMO_MEDIA_URL;
  return next;
};
const pgTrack = (name, props) => {
  if (window.MIQAnalytics) window.MIQAnalytics.track(name, props || {});
};

const Playground = ({
  initialId = 'audio/detect-speakers',
  defaultMinutes = 18,
  height = 'auto',
}) => {
  const [id, setId]               = React.useState(() => pgInitialEndpoint(initialId));
  const ep                         = ENDPOINTS[id];
  const [params, setParams]       = React.useState(() => pgDefaultParamsFor(ep));
  const [chain, setChain]         = React.useState(() => [pgInitialEndpoint(initialId)]);
  const [mediaSeconds, setMediaSeconds] = React.useState(PG_DEFAULT_MEDIA_SECONDS);
  const [sourcePreviewUrl, setSourcePreviewUrl] = React.useState(PG_DEMO_MEDIA_URL);
  const [copied, setCopied]       = React.useState('');
  const [running, setRunning]     = React.useState(false);
  const [liveResponse, setLiveResponse] = React.useState(null);
  const [liveError, setLiveError]       = React.useState(null);
  const [liveJob, setLiveJob]           = React.useState(null);
  const [selectedFile, setSelectedFile] = React.useState(null);
  const [mediaSourceMode, setMediaSourceMode] = React.useState('demo');
  const [uploadInfo, setUploadInfo]     = React.useState(null);
  const [uploadPhase, setUploadPhase]   = React.useState('');
  const [labSession, setLabSession]     = React.useState(null);

  // playground_viewed — once per mount
  React.useEffect(() => {
    pgTrack('playground_viewed', pgEpMeta(id));
    if (window.MomentIQ?.getLabSession) {
      window.MomentIQ.getLabSession()
        .then(payload => setLabSession(payload.lab_session || null))
        .catch(() => {});
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  React.useEffect(() => {
    setParams(pgDefaultParamsFor(ep));
    setLiveResponse(null);
    setLiveError(null);
    setLiveJob(null);
  }, [id]);

  React.useEffect(() => {
    if (!selectedFile) {
      setSourcePreviewUrl(PG_DEMO_MEDIA_URL);
      return undefined;
    }
    const url = URL.createObjectURL(selectedFile);
    setSourcePreviewUrl(url);
    return () => URL.revokeObjectURL(url);
  }, [selectedFile]);

  // Wrapped setter so every endpoint switch becomes a tracked event.
  const selectEndpoint = (next) => {
    if (next === id) return;
    pgTrack('playground_endpoint_selected', { ...pgEpMeta(next), from_endpoint_id: id });
    setId(next);
    setChain(current => current.length <= 1 ? [next] : current);
  };
  const mediaKey = (ep.playgroundControls || []).find(c => c.key === 'media_url' || c.key === 'local_path')?.key;
  const useDemoMedia = () => {
    setMediaSourceMode('demo');
    setSelectedFile(null);
    setUploadInfo(null);
    setUploadPhase('');
    setMediaSeconds(PG_DEFAULT_MEDIA_SECONDS);
    if (mediaKey) setParam(mediaKey, PG_DEMO_MEDIA_URL);
  };
  const chooseFile = (file) => {
    if (!file) return;
    setMediaSourceMode('upload');
    setSelectedFile(file);
    setUploadInfo(null);
    setUploadPhase('ready');
    setLiveResponse(null);
    setLiveError(null);
    setLiveJob(null);
    pgTrack('playground_media_uploaded', {
      ...pgEpMeta(id),
      media_source_type: 'upload',
      file_extension: safeFileExt(file.name),
      file_size_bytes: file.size,
    });
  };
  const selectWorkflow = (workflow) => {
    const workflowChain = pgWorkflowEndpointIds(workflow);
    if (workflowChain.length) {
      setChain(workflowChain);
      selectEndpoint(workflowChain[0]);
    } else {
      selectEndpoint(workflow.endpoint);
    }
    pgTrack('playground_workflow_selected', { workflow: workflow.name, endpoint_id: workflow.endpoint });
  };
  const addEndpointToChain = () => {
    setChain(current => current.includes(id) ? current : [...current, id]);
    pgTrack('playground_chain_endpoint_added', pgEpMeta(id));
  };
  const removeEndpointFromChain = (endpointId) => {
    setChain(current => {
      const next = current.filter(item => item !== endpointId);
      return next.length ? next : [id];
    });
  };

  const setParam = (k, v) => {
    setParams(p => ({ ...p, [k]: v }));
    const ctl = (ep.playgroundControls || []).find(c => c.key === k);
    if (!ctl) return;
    const base = pgEpMeta(id);
    if (ctl.type === 'url' && k.toLowerCase().indexOf('url') >= 0) {
      // Never the URL itself — only the fact + safe extension.
      pgTrack('playground_media_url_entered', {
        ...base,
        media_url_entered: !!v,
        media_source_type: 'url',
        file_extension:    safeFileExt(v),
      });
    } else if (k === 'start' || k === 'end') {
      const start = k === 'start' ? v : params.start;
      const end   = k === 'end'   ? v : params.end;
      pgTrack('playground_time_window_changed', {
        ...base, start, end,
        window_seconds: (Number.isFinite(start) && Number.isFinite(end)) ? Math.max(0, end - start) : undefined,
      });
    } else if (k === 'quality') {
      pgTrack('playground_param_changed', { ...base, param: 'quality', quality: v });
    }
  };

  const requestBody = React.useMemo(() => {
    const out = {};
    (ep.playgroundControls || []).forEach(c => { out[c.key] = params[c.key] ?? c.default; });
    return out;
  }, [ep, params]);
  const displayRequestBody = React.useMemo(
    () => {
      const display = { ...requestBody };
      if (selectedFile && mediaKey) display[mediaKey] = uploadInfo?.media_url || `upload://${selectedFile.name}`;
      return pgDisplayRequest(display, ep.playgroundControls || []);
    },
    [requestBody, ep, selectedFile, uploadInfo, mediaKey],
  );

  const chainDetails = React.useMemo(() => {
    const mediaUrl = selectedFile ? (uploadInfo?.media_url || `upload://${selectedFile.name}`) : (requestBody[mediaKey] || PG_DEMO_MEDIA_URL);
    return chain.map(endpointId => {
      const chainParams = pgParamsForEndpoint(endpointId, id, requestBody, mediaUrl);
      const billableSeconds = pgBillableSecondsFor(endpointId, mediaSeconds, chainParams);
      const estimatedUsd = pgEstimateEndpointUsd(endpointId, mediaSeconds, chainParams);
      return { endpointId, params: chainParams, billableSeconds, estimatedUsd };
    });
  }, [chain, id, requestBody, selectedFile, uploadInfo, mediaKey, mediaSeconds]);
  const cost = chainDetails.reduce((sum, item) => sum + item.estimatedUsd, 0);
  const liveChainEnabled = chain.every(endpointId => window.MomentIQ?.canRunLive?.(endpointId));
  const isFixedPrice = chainDetails.every(item => pricingUnit(item.endpointId) === 'request');
  const outputEndpointId = chain[chain.length - 1] || id;
  const outputEp = ENDPOINTS[outputEndpointId] || ep;
  const Preview = PREVIEWS[outputEp.fakeResponseType] || PreviewSignal;
  const liveEnabled = !!(window.MomentIQ && typeof window.MomentIQ.runJob === 'function' && liveChainEnabled);
  const hasProcessedOutput = !!(liveResponse || liveError || liveJob);
  const previewResponse = liveResponse?.results || liveResponse?.job?.result || liveResponse?.clip || liveResponse || ep.responseExample;

  // playground_price_estimate_viewed — fire when the estimated cost actually
  // changes (debounced through React state, not on every keystroke).
  React.useEffect(() => {
    pgTrack('playground_price_estimate_viewed', {
      ...pgEpMeta(id),
      estimated_price_usd:  +cost.toFixed(4),
      media_length_seconds: mediaSeconds,
      is_fixed_price:       isFixedPrice,
      endpoint_chain:       chain,
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [id, mediaSeconds, chain.join('|'), cost]);

  const copy = (label, text) => {
    try { navigator.clipboard?.writeText(text); } catch (e) {}
    setCopied(label); setTimeout(() => setCopied(''), 1400);
  };

  // Job runner. Endpoints with worker adapters use the hosted API/worker path;
  // endpoints without adapters still show preview data until they are live.
  const runDemo = async () => {
    const base = pgEpMeta(id);
    pgTrack('playground_demo_run_clicked', {
      ...base,
      media_length_seconds: mediaSeconds,
      estimated_price_usd:  +cost.toFixed(4),
      start: params.start, end: params.end,
      quality: params.quality,
      endpoint_chain: chain,
    });
    setRunning(true);
    setLiveJob(null);
    if (liveEnabled) {
      setLiveResponse(null);
      setLiveError(null);
      try {
        const runOptions = {
          intervalMs: 1500,
          timeoutMs: 120000,
          onProgress: ({ phase, upload }) => {
            setUploadPhase(phase);
            if (upload) setUploadInfo(upload);
          },
        };
        let upload = null;
        let mediaUrl = requestBody[mediaKey] || PG_DEMO_MEDIA_URL;
        if (selectedFile && mediaKey) {
          upload = await window.MomentIQ.uploadFile(selectedFile, runOptions);
          setUploadInfo(upload);
          mediaUrl = upload.media_url;
          setParams(p => ({ ...p, [mediaKey]: upload.media_url }));
        }
        const chainResults = [];
        let response = null;
        let previousResult = null;
        for (const endpointId of chain) {
          const endpointParams = pgApplyPreviousResultToParams(
            endpointId,
            pgParamsForEndpoint(endpointId, id, requestBody, mediaUrl),
            previousResult,
          );
          response = await window.MomentIQ.runJob(endpointId, endpointParams, runOptions);
          previousResult = pgResultPayload(response);
          chainResults.push({ endpoint: endpointId, response });
          if (response?.lab_session) setLabSession(response.lab_session);
          setLiveResponse({
            ...response,
            upload,
            chain_results: chainResults,
            results: pgResultPayload(response),
          });
          setLiveJob(response?.job || null);
        }
        const finalResponse = {
          ...(response || {}),
          upload,
          chain_results: chainResults,
          results: pgResultPayload(response),
        };
        setLiveResponse(finalResponse);
        setLiveJob(response?.job || null);
        pgTrack('playground_demo_completed', {
          ...base,
          media_length_seconds: mediaSeconds,
          estimated_price_usd: response?.job?.estimatedPriceUsd ?? response?.estimated_price_usd ?? +cost.toFixed(4),
          success: response?.job?.status !== 'failed',
          job_id: response?.job?.id || response?.job_id,
          job_status: response?.job?.status || response?.status,
          endpoint_chain: chain,
        });
      } catch (err) {
        const payload = err?.payload || { error: { code: 'client_error', message: err?.message || String(err) } };
        setLiveError(payload);
        setLiveJob(payload?.job || null);
        pgTrack('playground_demo_completed', {
          ...base,
          media_length_seconds: mediaSeconds,
          estimated_price_usd: +cost.toFixed(4),
          success: false,
          error_code: payload?.error?.code,
          endpoint_chain: chain,
        });
      } finally {
        setRunning(false);
      }
      return;
    }
    setTimeout(() => {
      setRunning(false);
      setLiveResponse({ mode: 'preview_only', note: 'This endpoint is not connected to a hosted worker yet.', results: ep.responseExample });
      pgTrack('playground_demo_completed', {
        ...base,
        media_length_seconds: mediaSeconds,
        estimated_price_usd:  +cost.toFixed(4),
        success: true,
        endpoint_chain: chain,
      });
    }, 650);
  };

  return (
    <div style={{
      background: PG_DARK.bg, border: `1px solid ${PG_DARK.line}`, borderRadius: 14,
      overflow: 'hidden', color: PG_DARK.ink,
      boxShadow: '0 30px 60px -22px rgba(0,0,0,.55), 0 0 0 1px rgba(255,255,255,.02)',
    }}>
      {/* chrome */}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 10,
        padding: '10px 14px',
        borderBottom: `1px solid ${PG_DARK.line}`, background: PG_DARK.panelAlt,
      }}>
        <span style={{ width: 10, height: 10, borderRadius: 999, background: '#3a3f47' }} />
        <span style={{ width: 10, height: 10, borderRadius: 999, background: '#3a3f47' }} />
        <span style={{ width: 10, height: 10, borderRadius: 999, background: '#3a3f47' }} />
        <span style={{ fontFamily: wf.mono, fontSize: 11.5, color: PG_DARK.ink3, marginLeft: 8 }}>
          Moment Lab
        </span>
        <span style={{ flex: 1 }} />
        <span style={{ fontFamily: wf.mono, fontSize: 11, color: PG_DARK.ink3 }}>
          Try the API free here
        </span>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)' }}>
        {/* LEFT: selector + controls + request JSON */}
        <div style={{ padding: 18, borderRight: `1px solid ${PG_DARK.line}`, minWidth: 0 }}>
          <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1.2, color: PG_DARK.ink3, textTransform: 'uppercase', marginBottom: 8 }}>Choose endpoint</div>
          <GroupedSelect value={id} onChange={selectEndpoint} />

          <div style={{ marginTop: 12 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1.2, color: PG_DARK.ink3, textTransform: 'uppercase', marginBottom: 8 }}>Endpoint chain</div>
            <div style={{
              display: 'flex', flexDirection: 'column', gap: 8,
              padding: 10, borderRadius: 10, background: PG_DARK.panel, border: `1px solid ${PG_DARK.line}`,
            }}>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 7 }}>
                {chain.map((endpointId, index) => {
                  const item = ENDPOINTS[endpointId];
                  const group = GROUPS[item.group];
                  return (
                    <button key={`${endpointId}-${index}`} onClick={() => setId(endpointId)} style={{
                      display: 'inline-flex', alignItems: 'center', gap: 6,
                      padding: '5px 7px', borderRadius: 7, cursor: 'pointer',
                      border: `1px solid ${group.color}`,
                      background: endpointId === id ? 'var(--accent-soft)' : PG_DARK.bgDeep,
                      color: PG_DARK.ink, fontFamily: wf.mono, fontSize: 11,
                    }}>
                      <span style={{ color: group.color }}>{index + 1}</span>
                      <span>{item.path.replace('/v1/', '')}</span>
                      {chain.length > 1 && (
                        <span onClick={(e) => { e.stopPropagation(); removeEndpointFromChain(endpointId); }} style={{ color: PG_DARK.ink3 }}>x</span>
                      )}
                    </button>
                  );
                })}
              </div>
              <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                <button onClick={addEndpointToChain} style={{ ...pgBtn(false), padding: '6px 9px', fontSize: 12 }}>
                  Add selected endpoint
                </button>
                <button onClick={() => setChain([id])} style={{ ...pgBtn(false), padding: '6px 9px', fontSize: 12 }}>
                  Use only this endpoint
                </button>
              </div>
            </div>
          </div>

          <div style={{ marginTop: 12 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1.2, color: PG_DARK.ink3, textTransform: 'uppercase', marginBottom: 8 }}>Workflow chain presets</div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
              {PG_WORKFLOWS.map(w => (
                <button key={w.name} onClick={() => selectWorkflow(w)} style={{
                  ...pgBtn(false), padding: '6px 9px', fontSize: 12,
                  borderColor: ENDPOINTS[w.endpoint] ? GROUPS[ENDPOINTS[w.endpoint].group].color : PG_DARK.line,
                }}>
                  {w.name}
                </button>
              ))}
            </div>
          </div>

          <div style={{
            marginTop: 14,
            display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap',
          }}>
            <Method kind={ep.method} dark />
            <span style={{ fontFamily: wf.mono, fontSize: 13, color: PG_DARK.ink }}>{ep.path}</span>
            <span style={{
              fontFamily: wf.mono, fontSize: 10.5, padding: '1px 6px', borderRadius: 4,
              background: 'var(--accent-soft)', color: 'var(--accent)',
              textTransform: 'uppercase', letterSpacing: 0.5,
            }}>{ep.group}</span>
            <span style={{
              fontFamily: wf.mono, fontSize: 10.5, padding: '1px 6px', borderRadius: 4,
              background: liveEnabled ? 'rgba(92, 184, 92, .14)' : 'rgba(230, 192, 123, .14)',
              color: liveEnabled ? '#9bd17b' : '#e6c07b',
              textTransform: 'uppercase', letterSpacing: 0.5,
            }}>{liveEnabled ? 'live worker' : 'preview'}</span>
          </div>
          <div style={{ fontFamily: wf.sans, fontSize: 13, color: PG_DARK.ink2, lineHeight: 1.55, marginTop: 10 }}>
            {ep.playgroundDescription || ep.short}
          </div>

          {mediaKey && (
            <div style={{
              marginTop: 14, padding: 14, borderRadius: 10,
              border: `1px solid ${PG_DARK.line}`, background: PG_DARK.panel,
            }}>
              <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
                <div>
                  <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1.2, color: PG_DARK.ink3, textTransform: 'uppercase' }}>Source media</div>
                  <div style={{ marginTop: 4, fontFamily: wf.sans, fontSize: 13, lineHeight: 1.4, color: PG_DARK.ink2 }}>
                    Choose the file the selected endpoint chain should process.
                  </div>
                </div>
              </div>
              <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 12 }}>
                <label style={{ ...pgBtn(mediaSourceMode === 'upload'), display: 'inline-flex', alignItems: 'center', padding: '7px 12px' }}>
                  Upload your own
                  <input
                    type="file"
                    accept="video/mp4,video/quicktime,video/webm,video/x-m4v,audio/mpeg,audio/wav,audio/mp4,audio/aac,audio/ogg,.mp4,.mov,.webm,.m4v,.mp3,.wav,.m4a,.aac,.ogg"
                    onChange={e => chooseFile(e.target.files && e.target.files[0])}
                    style={{ display: 'none' }}
                  />
                </label>
                <button onClick={useDemoMedia} style={{ ...pgBtn(mediaSourceMode === 'demo'), padding: '7px 12px' }}>Use demo video</button>
              </div>
              <div style={{
                marginTop: 10, padding: 10, borderRadius: 8,
                background: PG_DARK.bgDeep, border: `1px solid ${PG_DARK.line}`,
              }}>
                <div style={{ fontFamily: wf.sans, fontSize: 13, color: PG_DARK.ink, fontWeight: 650 }}>
                  {selectedFile ? selectedFile.name : 'Demo video loaded'}
                </div>
                <div style={{ marginTop: 4, fontFamily: wf.mono, fontSize: 11, color: PG_DARK.ink3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                  {selectedFile
                    ? `${(selectedFile.size / 1024 / 1024).toFixed(1)} MB ${uploadPhase ? `- ${uploadPhase}` : ''}`
                    : PG_DEMO_MEDIA_URL}
                </div>
                <div style={{ marginTop: 4, fontFamily: wf.mono, fontSize: 11, color: PG_DARK.ink3 }}>
                  Duration: {pgFormatDuration(mediaSeconds)}
                </div>
                {(selectedFile && selectedFile.type && selectedFile.type.startsWith('audio/')) ? (
                  <audio src={sourcePreviewUrl} controls onLoadedMetadata={(e) => {
                    const duration = e.currentTarget.duration;
                    if (Number.isFinite(duration) && duration > 0) setMediaSeconds(duration);
                  }} style={{ width: '100%', marginTop: 10 }} />
                ) : (
                  <video src={sourcePreviewUrl} controls muted={!selectedFile} onLoadedMetadata={(e) => {
                    const duration = e.currentTarget.duration;
                    if (Number.isFinite(duration) && duration > 0) setMediaSeconds(duration);
                  }} style={{
                    width: '100%', maxHeight: 132, marginTop: 10, borderRadius: 8,
                    background: PG_DARK.bgDeep, border: `1px solid ${PG_DARK.line}`,
                  }} />
                )}
                {uploadInfo?.media_url && (
                  <div style={{ marginTop: 6, fontFamily: wf.mono, fontSize: 10.5, color: '#9bd17b' }}>
                    Uploaded to R2 for processing
                  </div>
                )}
              </div>
            </div>
          )}

          {/* Controls */}
          <div style={{ marginTop: 18 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1.2, color: PG_DARK.ink3, textTransform: 'uppercase', marginBottom: 8 }}>Request settings</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
            {(ep.playgroundControls || []).map(ctl => {
              const span = (ctl.type === 'url' || ctl.type === 'json' || ctl.type === 'text') ? '1 / -1' : 'auto';
              return (
                <div key={ctl.key} style={{ gridColumn: span, minWidth: 0 }}>
                  <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 5 }}>
                    <span style={{ fontFamily: wf.mono, fontSize: 11.5, color: PG_DARK.ink2 }}>{pgControlLabel(ctl)}</span>
                    {pgControlHint(ctl) && <span style={{ fontFamily: wf.mono, fontSize: 10.5, color: PG_DARK.ink4 }}>{pgControlHint(ctl)}</span>}
                  </div>
                  <PgInput ctl={ctl} value={params[ctl.key] ?? ctl.default} onChange={v => setParam(ctl.key, v)} />
                </div>
              );
            })}
            </div>
          </div>

          <details style={{ marginTop: 18 }}>
            <summary style={{ fontFamily: wf.mono, fontSize: 11, color: PG_DARK.ink3, cursor: 'pointer', userSelect: 'none' }}>
              API request for developers
            </summary>
            <div style={{ marginTop: 8 }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>
                <span style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1.2, color: PG_DARK.ink3, textTransform: 'uppercase' }}>Request body</span>
                <span style={{ fontFamily: wf.mono, fontSize: 11, color: 'var(--accent)' }}>
                  POST {ep.path}
                </span>
              </div>
              <div style={{ borderRadius: 10, border: `1px solid ${PG_DARK.line}`, overflow: 'hidden' }}>
                <JsonBlock value={displayRequestBody} maxHeight={220} />
              </div>
            </div>
          </details>
        </div>

        {/* RIGHT: response + cost + actions */}
        <div style={{ padding: 18, background: PG_DARK.panelAlt, minWidth: 0 }}>
          {/* Cost row */}
          <div style={{
            display: 'flex', alignItems: 'flex-start', gap: 12,
            padding: '12px 14px',
            borderRadius: 10, background: PG_DARK.panel, border: `1px solid ${PG_DARK.line}`,
            marginBottom: 14,
          }}>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1.2, color: PG_DARK.ink3, textTransform: 'uppercase' }}>Estimated cost</div>
              <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginTop: 4 }}>
                <span style={{ fontFamily: wf.sans, fontSize: 22, fontWeight: 600, color: PG_DARK.ink }}>
                  ${cost.toFixed(2)}
                </span>
                <span style={{ fontFamily: wf.mono, fontSize: 11, color: PG_DARK.ink3 }}>
                  {chain.length} endpoint{chain.length === 1 ? '' : 's'} on {pgFormatDuration(mediaSeconds)}
                </span>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginTop: 8 }}>
                {chainDetails.map(item => (
                  <div key={item.endpointId} style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 8, fontFamily: wf.mono, fontSize: 10.5, color: PG_DARK.ink3 }}>
                    <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ENDPOINTS[item.endpointId].path.replace('/v1/', '')}</span>
                    <span>{pricingUnit(item.endpointId) === 'request' ? priceLabel(item.endpointId) : `$${item.estimatedUsd.toFixed(2)}`}</span>
                  </div>
                ))}
              </div>
              {labSession && (
                <div style={{ marginTop: 6, fontFamily: wf.mono, fontSize: 11, color: PG_DARK.ink3 }}>
                  Moment Lab trial: {labSession.trial_units_remaining} units left
                </div>
              )}
              <div style={{ marginTop: 6, fontFamily: wf.sans, fontSize: 12, color: '#9bd17b' }}>
                $5 free credit before signup. We ask you to create an account when trial credit runs out.
              </div>
            </div>
          </div>

          {/* Response preview */}
          <div style={{
            borderRadius: 10, background: PG_DARK.bgDeep, border: `1px solid ${PG_DARK.line}`,
            padding: 14, marginBottom: 14,
          }}>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1.2, color: PG_DARK.ink3, textTransform: 'uppercase', marginBottom: 10 }}>
              Output after selected endpoints run
            </div>
            {hasProcessedOutput
              ? <Preview ep={outputEp} response={previewResponse} />
              : <PreviewWaiting chain={chain} mediaSeconds={mediaSeconds} />}
          </div>
          {liveError && (
            <div style={{
              borderRadius: 10, background: '#2a1518', border: '1px solid #5f242c',
              padding: 12, marginBottom: 14,
              fontFamily: wf.mono, fontSize: 11.5, color: '#ffd5d9',
            }}>
              {liveError?.error?.code || 'error'}: {liveError?.error?.message || 'MomentIQ request failed'}
            </div>
          )}
          {liveJob && (
            <div style={{
              borderRadius: 10, background: PG_DARK.panel, border: `1px solid ${PG_DARK.line}`,
              padding: 12, marginBottom: 14,
              fontFamily: wf.mono, fontSize: 11.5, color: PG_DARK.ink2,
              display: 'grid', gridTemplateColumns: '1fr auto', gap: 8,
            }}>
              <span>{liveJob.id} · {liveJob.status} · {liveJob.resultMode || 'worker'}</span>
              <span style={{ color: 'var(--accent)' }}>{liveJob.processingMs != null ? `${liveJob.processingMs} ms` : ''}</span>
            </div>
          )}

          {/* Actions */}
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            <button onClick={runDemo} disabled={running} style={pgBtn(true)}>
              {running
                ? (uploadPhase === 'uploading' ? 'Uploading media...' : 'Running selected endpoints...')
                : (liveEnabled ? (selectedFile ? 'Upload and run selected endpoints' : 'Run selected endpoints') : 'Preview selected endpoints')}
            </button>
            <a href={docsHrefFor(id)}
              onClick={() => pgTrack('playground_view_docs_clicked', pgEpMeta(id))}
              style={{ ...pgBtn(false), textDecoration: 'none', display: 'inline-flex' }}>
              View docs →
            </a>
          </div>

          {/* Response JSON (collapsed peek) */}
          <details style={{ marginTop: 14 }}>
            <summary style={{ fontFamily: wf.mono, fontSize: 11, color: PG_DARK.ink3, cursor: 'pointer', userSelect: 'none' }}>
              Show raw response JSON
            </summary>
            <div style={{ marginTop: 8, borderRadius: 10, border: `1px solid ${PG_DARK.line}`, overflow: 'hidden' }}>
              <JsonBlock value={liveError || liveResponse || ep.responseExample} maxHeight={240} />
            </div>
          </details>
        </div>
      </div>
    </div>
  );
};

const pgBtn = (primary) => ({
  fontFamily: wf.sans, fontSize: 13, fontWeight: 500,
  padding: '8px 14px', borderRadius: 8,
  border: `1px solid ${primary ? 'var(--accent)' : PG_DARK.line}`,
  background: primary ? 'var(--accent)' : PG_DARK.panel,
  color: primary ? '#fff' : PG_DARK.ink,
  cursor: 'pointer',
});

window.Playground = Playground;
window.pgEpMeta   = pgEpMeta;
window.pgTrack    = pgTrack;
