// Datasets Atlas — a typological catalog of voice data specimens.
// Grid of small waveform thumbnails, each tagged with language + domain.
// Ambient: gentle phase-offset breathing across the grid.
// Interaction: hover a specimen → it lights up signal-coral + telemetry strip
// below populates with that specimen's metadata.

const SPECIMENS = [
  // 24 specimens — language code · domain · seed values
  { id: 'es-mx-conv', lang: 'ES-MX', dom: 'CONV', sr: '4.2', f0: '184', spk: 3, dur: '4:12' },
  { id: 'en-us-exp',  lang: 'EN-US', dom: 'EXPRT', sr: '3.6', f0: '128', spk: 2, dur: '8:04' },
  { id: 'ja-jp-narr', lang: 'JA-JP', dom: 'NARR',  sr: '5.1', f0: '212', spk: 1, dur: '12:30' },
  { id: 'pt-br-cust', lang: 'PT-BR', dom: 'CUST',  sr: '4.0', f0: '156', spk: 2, dur: '5:48' },
  { id: 'fr-fr-emot', lang: 'FR-FR', dom: 'EMOT',  sr: '3.8', f0: '198', spk: 2, dur: '3:21' },
  { id: 'hi-in-task', lang: 'HI-IN', dom: 'TASK',  sr: '4.5', f0: '172', spk: 2, dur: '6:14' },
  { id: 'sw-ke-conv', lang: 'SW-KE', dom: 'CONV',  sr: '4.1', f0: '168', spk: 4, dur: '5:02' },
  { id: 'de-de-bcst', lang: 'DE-DE', dom: 'BCST',  sr: '3.4', f0: '144', spk: 3, dur: '15:22' },
  { id: 'ar-eg-narr', lang: 'AR-EG', dom: 'NARR',  sr: '4.7', f0: '146', spk: 1, dur: '9:18' },
  { id: 'zh-cn-cust', lang: 'ZH-CN', dom: 'CUST',  sr: '5.3', f0: '194', spk: 2, dur: '4:36' },
  { id: 'ko-kr-cs',   lang: 'KO-KR', dom: 'CSWCH', sr: '4.4', f0: '176', spk: 2, dur: '7:01' },
  { id: 'it-it-exp',  lang: 'IT-IT', dom: 'EXPRT', sr: '3.9', f0: '162', spk: 3, dur: '11:44' },
  { id: 'ru-ru-emot', lang: 'RU-RU', dom: 'EMOT',  sr: '3.7', f0: '138', spk: 2, dur: '4:08' },
  { id: 'tr-tr-task', lang: 'TR-TR', dom: 'TASK',  sr: '4.3', f0: '180', spk: 2, dur: '5:55' },
  { id: 'vi-vn-conv', lang: 'VI-VN', dom: 'CONV',  sr: '4.8', f0: '202', spk: 3, dur: '6:42' },
  { id: 'pl-pl-bcst', lang: 'PL-PL', dom: 'BCST',  sr: '3.5', f0: '152', spk: 2, dur: '13:09' },
  { id: 'nl-nl-cust', lang: 'NL-NL', dom: 'CUST',  sr: '4.0', f0: '160', spk: 2, dur: '4:50' },
  { id: 'th-th-narr', lang: 'TH-TH', dom: 'NARR',  sr: '4.6', f0: '208', spk: 1, dur: '10:11' },
  { id: 'es-ar-cs',   lang: 'ES-AR', dom: 'CSWCH', sr: '4.5', f0: '188', spk: 2, dur: '6:28' },
  { id: 'en-gb-exp',  lang: 'EN-GB', dom: 'EXPRT', sr: '3.7', f0: '124', spk: 2, dur: '9:32' },
  { id: 'fa-ir-emot', lang: 'FA-IR', dom: 'EMOT',  sr: '4.0', f0: '174', spk: 2, dur: '3:55' },
  { id: 'id-id-conv', lang: 'ID-ID', dom: 'CONV',  sr: '4.4', f0: '170', spk: 4, dur: '5:36' },
  { id: 'he-il-task', lang: 'HE-IL', dom: 'TASK',  sr: '4.2', f0: '158', spk: 2, dur: '6:03' },
  { id: 'yo-ng-narr', lang: 'YO-NG', dom: 'NARR',  sr: '4.9', f0: '192', spk: 1, dur: '8:47' },
];

// Pre-generate deterministic waveform samples for each specimen so they
// don't reflow on re-render. Each waveform has a unique character based on its
// domain (e.g. CONV is choppy, NARR is flowing, BCST is regular).
const SAMPLES = 64;
const waveSeed = (specimen, idx) => {
  // simple deterministic pseudorandom from specimen.id + idx
  let h = 0;
  const s = specimen.id + ':' + idx;
  for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0;
  return ((h >>> 0) % 1000) / 1000;
};
const SPECIMEN_WAVES = SPECIMENS.map(sp => {
  const arr = [];
  // domain-specific shape character
  const character = {
    CONV:  (i, n) => Math.sin(i * 0.4) * 0.4 + (n - 0.5) * 0.6,        // choppy
    EXPRT: (i, n) => Math.sin(i * 0.18) * 0.5 + (n - 0.5) * 0.25,      // smooth
    NARR:  (i, n) => Math.sin(i * 0.12) * 0.6 + Math.sin(i * 0.5) * 0.15 + (n - 0.5) * 0.15, // flowing
    CUST:  (i, n) => Math.sin(i * 0.3) * 0.4 + (n - 0.5) * 0.4,        // moderate
    EMOT:  (i, n) => Math.sin(i * 0.25) * 0.7 + (n - 0.5) * 0.5,       // dramatic
    TASK:  (i, n) => (Math.sin(i * 0.22) * 0.35 + (n - 0.5) * 0.3) * (i % 8 < 6 ? 1 : 0.2), // bursty
    BCST:  (i, n) => Math.sin(i * 0.16) * 0.45 + (n - 0.5) * 0.18,     // regular
    CSWCH: (i, n) => Math.sin(i * (i < SAMPLES/2 ? 0.2 : 0.35)) * 0.5 + (n - 0.5) * 0.3, // shifts mid-way
  }[sp.dom] || ((i, n) => (n - 0.5) * 0.6);
  for (let i = 0; i < SAMPLES; i++) {
    const n = waveSeed(sp, i);
    arr.push(character(i, n));
  }
  return arr;
});

const DatasetsAtlas = ({ scrollT }) => {
  const [hoverIdx, setHoverIdx] = React.useState(null);
  const ref = React.useRef(null);
  // Use externally-supplied scroll progress when wrapped in PinnedScrub,
  // otherwise fall back to the section-based hook.
  const internal = useScrollProgress(ref, { startOffset: 0.95, endOffset: 0.1 });
  const scroll = scrollT !== undefined ? scrollT : internal;
  const clock = scroll * Math.PI * 4;

  // Scroll-driven sweep: a coral highlight moves across specimens one-by-one.
  // Position is a continuous index 0..N. Each specimen has a "lit" amount that
  // peaks when the head is on it and trails behind for an afterglow.
  const N = SPECIMENS.length;
  // Use slightly more than N so the last cell fully reaches peak before the
  // dwell zone ends.
  // Continuous sweep: a soft head-and-tail gradient rolls across the grid
  // perfectly synced with scroll. Many cells are partially lit at any moment,
  // so the wave reads as one continuous sweep rather than discrete pops.
  // Head extends slightly past N so the last cell fully reaches peak.
  const sweepHead = scroll * (N + 2.5) - 1.5;
  const RAMP = 2.2;   // cells ahead of the head that already start glowing
  const TAIL = 4.5;   // cells behind the head that fade out
  const RESIDUAL = 0.22;
  const sweepLit = (i) => {
    const d = sweepHead - i; // positive = behind head (visited), negative = ahead
    if (d >= 0) {
      // behind the head: peak at d=0, smoothly fade across TAIL cells, then residual
      if (d <= TAIL) {
        const k = d / TAIL;             // 0 at head, 1 at tail end
        const fade = 1 - k * k;          // quadratic ease-out
        return RESIDUAL + (1 - RESIDUAL) * fade;
      }
      return RESIDUAL;
    } else {
      // ahead of the head: gentle pre-glow ramping in
      const a = -d; // distance ahead
      if (a >= RAMP) return 0;
      const k = 1 - a / RAMP;            // 0 far ahead, 1 at head
      return k * k;                      // quadratic ease-in
    }
  };

  const active = hoverIdx !== null ? SPECIMENS[hoverIdx] : null;

  return (
    <div ref={ref} style={{
      background: 'var(--ex-cream)',
      border: '1px solid var(--ex-rule)',
      borderRadius: 6,
      overflow: 'hidden',
    }}>
      {/* header strip */}
      <div style={{
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        padding: '14px 24px',
        borderBottom: '1px solid var(--ex-rule)',
        background: 'var(--ex-cream-2)',
        fontFamily: 'var(--ex-mono)', fontSize: 10,
        letterSpacing: '0.08em', textTransform: 'uppercase',
        color: 'var(--ex-mute)',
      }}>
        <span>ATLAS · 24 OF 1,847 SPECIMENS SHOWN</span>
        <span style={{ display: 'flex', gap: 24 }}>
          <span>● 60+ LANGUAGES</span>
          <span>● 8 DOMAINS</span>
          <span>● 50+ ANNOTATIONS</span>
        </span>
        <span>{active ? '▸ ' + active.id.toUpperCase() : '▸ HOVER A SPECIMEN'}</span>
      </div>

      {/* the grid */}
      <div className="ex-atlas-grid" style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(8, 1fr)',
        gap: 0,
      }}>
        {SPECIMENS.map((sp, i) => {
          const isActive = hoverIdx === i;
          const lit = sweepLit(i);
          // gentle row-based breathing (0..1)
          const row = Math.floor(i / 8);
          const col = i % 8;
          const breath = Math.sin(clock * 0.6 - row * 0.5 - col * 0.18) * 0.5 + 0.5;
          const dim = hoverIdx !== null && !isActive ? 0.35 : 1;
          // hover wins; otherwise sweep drives the lit state
          const showLit = isActive ? 1 : lit;

          return (
            <div
              key={sp.id}
              onMouseEnter={() => setHoverIdx(i)}
              onMouseLeave={() => setHoverIdx(null)}
              style={{
                position: 'relative',
                padding: '14px 12px 16px',
                borderRight: (col + 1) % 8 === 0 ? 'none' : '1px solid var(--ex-rule)',
                borderBottom: row < 2 ? '1px solid var(--ex-rule)' : 'none',
                background: isActive
                  ? 'var(--ex-signal-soft)'
                  : showLit > 0
                    ? `oklch(0.68 0.22 30 / ${0.04 + showLit * 0.18})`
                    : 'transparent',
                cursor: 'pointer',
                transition: 'background 0.08s linear, opacity 0.2s',
                opacity: dim,
              }}>
              {/* tiny labels above */}
              <div style={{
                display: 'flex', justifyContent: 'space-between',
                fontFamily: 'var(--ex-mono)', fontSize: 9,
                letterSpacing: '0.06em',
                color: showLit > 0.15 ? `color-mix(in oklch, var(--ex-mute), oklch(0.5 0.22 30) ${Math.min(1, showLit) * 100}%)` : 'var(--ex-mute)',
                marginBottom: 6,
                transition: 'color 0.1s linear',
              }}>
                <span style={{ fontWeight: 500 }}>{sp.lang}</span>
                <span style={{ opacity: 0.7 }}>{sp.dom}</span>
              </div>

              {/* waveform */}
              <svg viewBox="0 0 64 28" preserveAspectRatio="none"
                style={{ width: '100%', height: 28, display: 'block' }}>
                {SPECIMEN_WAVES[i].map((v, j) => {
                  // additional ambient modulation
                  const phase = Math.sin(clock * 1.2 + i * 0.4 + j * 0.3) * 0.06;
                  const mag = Math.abs(v + phase);
                  const h = Math.max(1, mag * 26);
                  const litMix = Math.min(1, showLit);
                  const stroke = litMix > 0.05
                    ? `color-mix(in oklch, var(--ex-ink), oklch(0.55 0.22 30) ${litMix * 100}%)`
                    : 'var(--ex-ink)';
                  return (
                    <line key={j}
                      x1={j + 0.5} y1={14 - h/2}
                      x2={j + 0.5} y2={14 + h/2}
                      stroke={stroke}
                      strokeWidth={0.7 + litMix * 0.15}
                      opacity={0.45 + litMix * 0.4 + breath * 0.15 * (1 - litMix)}
                      strokeLinecap="round"/>
                  );
                })}
              </svg>

              {/* duration footer */}
              <div style={{
                marginTop: 6,
                fontFamily: 'var(--ex-mono)', fontSize: 9,
                color: showLit > 0.15 ? `color-mix(in oklch, var(--ex-mute), oklch(0.5 0.22 30) ${Math.min(1, showLit) * 100}%)` : 'var(--ex-mute)',
                fontVariantNumeric: 'tabular-nums',
                display: 'flex', justifyContent: 'space-between',
                transition: 'color 0.1s linear',
              }}>
                <span>{sp.dur}</span>
                <span>{sp.spk}sp</span>
              </div>

              {/* corner tick when active */}
              {isActive && (
                <>
                  <span style={{
                    position: 'absolute', top: 0, left: 0,
                    width: 6, height: 6,
                    borderTop: '1.5px solid oklch(0.55 0.22 30)',
                    borderLeft: '1.5px solid oklch(0.55 0.22 30)',
                  }}/>
                  <span style={{
                    position: 'absolute', bottom: 0, right: 0,
                    width: 6, height: 6,
                    borderBottom: '1.5px solid oklch(0.55 0.22 30)',
                    borderRight: '1.5px solid oklch(0.55 0.22 30)',
                  }}/>
                </>
              )}
            </div>
          );
        })}
      </div>

      {/* telemetry strip — populated on hover */}
      <div style={{
        borderTop: '1px solid var(--ex-rule)',
        background: 'var(--ex-cream-2)',
        padding: '14px 24px',
        display: 'grid',
        gridTemplateColumns: 'repeat(7, 1fr)',
        gap: 16,
        fontFamily: 'var(--ex-mono)', fontSize: 11,
        color: active ? 'var(--ex-ink)' : 'var(--ex-mute)',
        transition: 'color 0.2s',
        minHeight: 56,
        alignItems: 'center',
      }}>
        <TStat label="SPECIMEN"  value={active ? active.id.toUpperCase() : '— select to inspect'} wide/>
        <TStat label="LANG"      value={active ? active.lang : '—'}/>
        <TStat label="DOMAIN"    value={active ? active.dom : '—'}/>
        <TStat label="DURATION"  value={active ? active.dur : '—'}/>
        <TStat label="F0 MEAN"   value={active ? active.f0 + ' Hz' : '—'}/>
        <TStat label="RATE"      value={active ? active.sr + ' syl/s' : '—'}/>
        <TStat label="SPEAKERS"  value={active ? active.spk : '—'}/>
      </div>
    </div>
  );
};

const TStat = ({ label, value, wide }) => (
  <div style={{ gridColumn: wide ? 'span 1' : 'auto', minWidth: 0 }}>
    <div style={{
      fontSize: 9, letterSpacing: '0.08em', textTransform: 'uppercase',
      opacity: 0.55, marginBottom: 4,
    }}>{label}</div>
    <div style={{
      fontVariantNumeric: 'tabular-nums',
      fontWeight: 500,
      whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
    }}>{value}</div>
  </div>
);

window.DatasetsAtlas = DatasetsAtlas;
