// ui_kits/website/PodcastPlayer.jsx
//
// Native HTML5 audio player components for the podcast page.
//
//   • usePodcastPlayer()   — singleton playback state (one audio element, one
//                            "now playing" episode). Used by both the
//                            featured player card and inline episode rows.
//   • NativeAudioPlayer    — the big player card that replaces the Spotify
//                            embed on the featured episode.
//   • EpisodeProgressBar   — inline scrubber that appears under the
//                            currently-playing episode in the list.
//
// All UI uses the wellness palette (leaf-deep + coral + cream).

// ============================================================
// Singleton audio element + React subscription
// ============================================================
//
// There's exactly one <audio> for the whole page so that starting a new
// episode automatically stops the previous one. The element is created on
// first call to ensurePlayerAudio() and lives until the page unloads.

const _ppState = {
  audio: null,
  episode: null,     // currently loaded episode object
  isPlaying: false,
  currentTime: 0,
  duration: 0,
  rate: 1,
  loading: false,
  listeners: new Set(),
};

function ensurePlayerAudio() {
  if (_ppState.audio) return _ppState.audio;
  const a = new Audio();
  a.preload = 'metadata';

  const emit = () => _ppState.listeners.forEach(fn => fn());

  a.addEventListener('play',       () => { _ppState.isPlaying = true;  _ppState.loading = false; emit(); });
  a.addEventListener('pause',      () => { _ppState.isPlaying = false; emit(); });
  a.addEventListener('ended',      () => { _ppState.isPlaying = false; emit(); });
  a.addEventListener('waiting',    () => { _ppState.loading = true;  emit(); });
  a.addEventListener('canplay',    () => { _ppState.loading = false; emit(); });
  a.addEventListener('timeupdate', () => { _ppState.currentTime = a.currentTime; emit(); });
  a.addEventListener('durationchange', () => { _ppState.duration = a.duration || 0; emit(); });
  a.addEventListener('loadedmetadata',  () => { _ppState.duration = a.duration || 0; emit(); });
  a.addEventListener('ratechange', () => { _ppState.rate = a.playbackRate; emit(); });

  _ppState.audio = a;
  return a;
}

// Public hook — components call this to read state + send commands
function usePodcastPlayer() {
  // tickle a React re-render whenever the singleton emits
  const [, force] = React.useReducer(x => x + 1, 0);
  React.useEffect(() => {
    _ppState.listeners.add(force);
    return () => _ppState.listeners.delete(force);
  }, []);

  const play = React.useCallback((episode) => {
    const a = ensurePlayerAudio();
    // Same episode? Just resume.
    if (_ppState.episode && _ppState.episode.id === episode.id) {
      a.play().catch(err => console.warn('audio play rejected:', err));
      return;
    }
    _ppState.episode = episode;
    _ppState.currentTime = 0;
    _ppState.duration = episode.durationSec || 0;
    _ppState.loading = true;
    a.src = episode.audioUrl;
    a.currentTime = 0;
    a.playbackRate = _ppState.rate;
    a.play().catch(err => console.warn('audio play rejected:', err));
    _ppState.listeners.forEach(fn => fn());
  }, []);

  const pause = React.useCallback(() => { if (_ppState.audio) _ppState.audio.pause(); }, []);
  const toggle = React.useCallback((episode) => {
    if (_ppState.episode && _ppState.episode.id === episode.id) {
      if (_ppState.isPlaying) pause(); else play(episode);
    } else { play(episode); }
  }, [play, pause]);
  const seekTo = React.useCallback((t) => { if (_ppState.audio) _ppState.audio.currentTime = t; }, []);
  const seekBy = React.useCallback((delta) => {
    const a = _ppState.audio; if (!a) return;
    a.currentTime = Math.max(0, Math.min((a.duration || 0), a.currentTime + delta));
  }, []);
  const setRate = React.useCallback((r) => {
    const a = ensurePlayerAudio();
    a.playbackRate = r; _ppState.rate = r;
    _ppState.listeners.forEach(fn => fn());
  }, []);

  return {
    episode:     _ppState.episode,
    isPlaying:   _ppState.isPlaying,
    loading:     _ppState.loading,
    currentTime: _ppState.currentTime,
    duration:    _ppState.duration || (_ppState.episode?.durationSec || 0),
    rate:        _ppState.rate,
    play, pause, toggle, seekTo, seekBy, setRate,
    isActive: (ep) => _ppState.episode && _ppState.episode.id === ep.id,
  };
}

// ============================================================
// Time helpers + small inline icons
// ============================================================

function fmtTime(s) {
  if (!s || !isFinite(s)) return '0:00';
  const m = Math.floor(s / 60);
  const sec = Math.floor(s % 60);
  return `${m}:${sec.toString().padStart(2, '0')}`;
}

const Rewind15 = ({ size = 22 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <path d="M12.5 4 8 8l4.5 4"/>
    <path d="M8 8h6.5a5.5 5.5 0 1 1 0 11H8"/>
    <text x="6.6" y="17.6" fontFamily="system-ui, sans-serif" fontSize="7.5" fontWeight="700" fill="currentColor" stroke="none">15</text>
  </svg>
);
const Forward15 = ({ size = 22 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <path d="M11.5 4 16 8l-4.5 4"/>
    <path d="M16 8H9.5a5.5 5.5 0 1 0 0 11H16"/>
    <text x="8.6" y="17.6" fontFamily="system-ui, sans-serif" fontSize="7.5" fontWeight="700" fill="currentColor" stroke="none">15</text>
  </svg>
);

// ============================================================
// Scrubber (used by both the big player and inline episode rows)
// ============================================================

function Scrubber({ value, max, onChange, accent = 'var(--brand-coral)', track = 'rgba(0,0,0,0.10)', height = 6 }) {
  const ref = React.useRef(null);

  const handle = (e) => {
    const el = ref.current; if (!el || !max) return;
    const rect = el.getBoundingClientRect();
    const x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left;
    const ratio = Math.max(0, Math.min(1, x / rect.width));
    onChange(ratio * max);
  };

  const pct = max > 0 ? (value / max) * 100 : 0;

  return (
    <div
      ref={ref}
      onClick={handle}
      onMouseDown={(e) => {
        handle(e);
        const move = (ev) => handle(ev);
        const up = () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); };
        window.addEventListener('mousemove', move);
        window.addEventListener('mouseup', up);
      }}
      style={{
        position: 'relative', height: height + 14, padding: `7px 0`,
        cursor: 'pointer', userSelect: 'none',
      }}
    >
      <div style={{ position: 'absolute', left: 0, right: 0, top: '50%', transform: 'translateY(-50%)', height, borderRadius: 999, background: track }}/>
      <div style={{ position: 'absolute', left: 0, top: '50%', transform: 'translateY(-50%)', height, width: `${pct}%`, borderRadius: 999, background: accent, transition: 'width 80ms linear' }}/>
      <div style={{
        position: 'absolute', left: `${pct}%`, top: '50%',
        transform: 'translate(-50%, -50%)',
        width: 14, height: 14, borderRadius: '50%',
        background: '#fff', border: `2px solid ${accent}`,
        boxShadow: '0 2px 4px rgba(0,0,0,0.18)',
        transition: 'left 80ms linear',
      }}/>
    </div>
  );
}

// ============================================================
// NativeAudioPlayer — replaces the Spotify embed on the featured card
// ============================================================

const playerStyles = {
  card: {
    background: '#fff',
    borderRadius: 24,
    boxShadow: '0 18px 40px rgba(31,79,64,0.10)',
    overflow: 'hidden',
    display: 'grid', gridTemplateColumns: '320px 1fr',
    border: '1px solid var(--color-divider)',
  },
  art: {
    position: 'relative',
    background: 'var(--brand-leaf-deep)',
  },
  artImg: { width: '100%', height: '100%', objectFit: 'cover', display: 'block' },
  artBadge: {
    position: 'absolute', top: 16, left: 16,
    background: 'rgba(0,0,0,0.55)',
    color: '#fff',
    fontFamily: 'var(--font-sans)',
    fontSize: 10, fontWeight: 700,
    letterSpacing: '0.18em', textTransform: 'uppercase',
    padding: '5px 10px', borderRadius: 999,
    backdropFilter: 'blur(8px)',
  },
  body: { padding: '32px 36px 28px', display: 'flex', flexDirection: 'column' },
  meta: {
    fontFamily: 'var(--font-mono)',
    fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase',
    color: 'var(--color-fg-subtle)',
    marginBottom: 12,
  },
  title: {
    fontFamily: 'var(--font-serif)', fontWeight: 700,
    fontSize: 26, lineHeight: 1.2, letterSpacing: '-0.01em',
    color: 'var(--color-fg-strong)', margin: 0,
    textWrap: 'balance',
  },
  excerpt: {
    fontFamily: 'var(--font-sans)', fontSize: 14.5, lineHeight: 1.65,
    color: 'var(--color-fg)', margin: '12px 0 0',
    display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden',
  },
  controls: {
    marginTop: 22, paddingTop: 18,
    borderTop: '1px solid var(--color-divider)',
    display: 'flex', flexDirection: 'column', gap: 12,
  },
  controlsRow: { display: 'flex', alignItems: 'center', gap: 16 },
  bigPlay: {
    width: 56, height: 56, borderRadius: '50%',
    background: 'var(--brand-coral)', color: '#fff',
    border: 'none', cursor: 'pointer',
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    boxShadow: '0 10px 24px rgba(194,106,74,0.40)',
    flexShrink: 0,
    transition: 'transform 160ms var(--ease-out)',
  },
  skip: {
    width: 40, height: 40, borderRadius: '50%',
    background: 'transparent',
    color: 'var(--brand-leaf-deep)',
    border: '1.5px solid var(--color-divider)',
    cursor: 'pointer',
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    flexShrink: 0,
  },
  times: {
    fontFamily: 'var(--font-mono)',
    fontSize: 12,
    color: 'var(--color-fg-subtle)',
    fontVariantNumeric: 'tabular-nums',
    display: 'flex', gap: 12, marginLeft: 'auto',
  },
  rateGroup: { display: 'flex', alignItems: 'center', gap: 6 },
  rateBtn: (active) => ({
    fontFamily: 'var(--font-mono)',
    fontSize: 11, fontWeight: 600,
    background: active ? 'var(--brand-leaf-deep)' : 'transparent',
    color: active ? '#fff' : 'var(--color-fg-muted)',
    border: '1px solid ' + (active ? 'transparent' : 'var(--color-divider)'),
    padding: '4px 8px', borderRadius: 6,
    cursor: 'pointer',
  }),
};

function NativeAudioPlayer({ episode, fallbackArt }) {
  const player = usePodcastPlayer();
  const active = player.isActive(episode);
  const tr = (typeof useT === 'function') ? useT() : (k) => k;
  const lang = (typeof LangContext !== 'undefined') ? React.useContext(LangContext) : 'es';

  if (!episode) return null;

  const isPlaying = active && player.isPlaying;
  const cur  = active ? player.currentTime : 0;
  const dur  = active && player.duration ? player.duration : (episode.durationSec || episode.minutes * 60 || 0);
  const rate = active ? player.rate : 1;

  return (
    <div style={playerStyles.card}>
      <div style={playerStyles.art}>
        <img src={episode.artworkUrl || fallbackArt} alt="" style={playerStyles.artImg}
             onError={(e) => { e.currentTarget.style.display = 'none'; }}/>
        <span style={playerStyles.artBadge}>
          <span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: '#FF4D4D', marginRight: 6, verticalAlign: 'middle' }}/>
          {lang === 'en' ? 'NEW THIS WEEK' : 'NUEVO ESTA SEMANA'}
        </span>
      </div>

      <div style={playerStyles.body}>
        <div style={playerStyles.meta}>
          {lang === 'en' ? 'Episode' : 'Episodio'} {episode.n} · {episode.dateLong} · {episode.minutes} min
        </div>
        <h2 style={playerStyles.title}>{episode.title}</h2>
        {episode.excerpt && <p style={playerStyles.excerpt}>{episode.excerpt}</p>}

        <div style={playerStyles.controls}>
          <Scrubber
            value={cur} max={dur || 1}
            onChange={(t) => { if (!active) player.play(episode); player.seekTo(t); }}
            accent="var(--brand-coral)"
            track="rgba(31,79,64,0.10)"
          />
          <div style={playerStyles.controlsRow}>
            <button style={playerStyles.skip} onClick={() => player.seekBy(-15)} aria-label="Back 15 seconds"
                    title="Back 15s">
              <Rewind15 size={20}/>
            </button>
            <button
              style={playerStyles.bigPlay}
              onClick={() => player.toggle(episode)}
              aria-label={isPlaying ? 'Pause' : 'Play'}
            >
              {player.loading && active
                ? <span style={{ width: 22, height: 22, borderRadius: '50%', border: '2.5px solid rgba(255,255,255,0.35)', borderTopColor: '#fff', animation: 'ppspin 0.8s linear infinite' }}/>
                : (isPlaying ? <PauseIcon size={24}/> : <PlayIcon size={24}/>)
              }
            </button>
            <button style={playerStyles.skip} onClick={() => player.seekBy(15)} aria-label="Forward 15 seconds"
                    title="Forward 15s">
              <Forward15 size={20}/>
            </button>

            <div style={playerStyles.rateGroup}>
              {[1, 1.25, 1.5, 2].map(r => (
                <button key={r} style={playerStyles.rateBtn(rate === r)} onClick={() => player.setRate(r)}>
                  {r}x
                </button>
              ))}
            </div>

            <div style={playerStyles.times}>
              <span>{fmtTime(cur)}</span>
              <span style={{ opacity: 0.4 }}>/</span>
              <span>{fmtTime(dur)}</span>
            </div>
          </div>
        </div>
      </div>

      <style>{`@keyframes ppspin { to { transform: rotate(360deg); } }`}</style>
    </div>
  );
}

// ============================================================
// EpisodeProgressBar — inline scrubber under a row in the list
// ============================================================

function EpisodeProgressBar({ episode }) {
  const player = usePodcastPlayer();
  if (!player.isActive(episode)) return null;
  const cur = player.currentTime;
  const dur = player.duration || (episode.durationSec || episode.minutes * 60 || 0);
  return (
    <div style={{ gridColumn: '1 / -1', padding: '14px 6px 0' }}>
      <Scrubber
        value={cur} max={dur || 1}
        onChange={(t) => player.seekTo(t)}
        accent="var(--brand-coral)"
        track="rgba(31,79,64,0.10)"
        height={4}
      />
      <div style={{
        display: 'flex', justifyContent: 'space-between',
        fontFamily: 'var(--font-mono)', fontSize: 11,
        color: 'var(--color-fg-subtle)',
        marginTop: 4,
        fontVariantNumeric: 'tabular-nums',
      }}>
        <span>{fmtTime(cur)}</span>
        <span>-{fmtTime(Math.max(0, dur - cur))}</span>
      </div>
    </div>
  );
}

// ============================================================
// MiniPlayer — sticky bar pinned to the bottom of the viewport
// ============================================================
//
// Mounts once at the app root and stays visible whenever any episode is
// loaded. Slides up the first time playback starts; can be dismissed
// with the × button, which also stops playback.

const miniStyles = {
  wrap: (visible) => ({
    position: 'fixed', left: 0, right: 0, bottom: 0,
    zIndex: 90,
    transform: visible ? 'translateY(0)' : 'translateY(110%)',
    transition: 'transform 320ms var(--ease-out)',
    pointerEvents: visible ? 'auto' : 'none',
    padding: '0 20px 20px',
    display: 'flex', justifyContent: 'center',
  }),
  bar: {
    width: '100%', maxWidth: 1100,
    background: 'rgba(20, 38, 31, 0.96)',
    backdropFilter: 'blur(16px)',
    WebkitBackdropFilter: 'blur(16px)',
    borderRadius: 18,
    boxShadow: '0 18px 48px rgba(15, 31, 24, 0.40), 0 0 0 1px rgba(255,255,255,0.08) inset',
    color: '#fff',
    display: 'grid',
    // art | text + scrubber | controls
    gridTemplateColumns: '56px 1fr auto',
    gap: 20,
    alignItems: 'center',
    padding: '12px 18px 12px 12px',
  },
  art: {
    width: 56, height: 56, borderRadius: 10,
    overflow: 'hidden', flexShrink: 0,
    background: 'var(--brand-leaf)',
  },
  artImg: { width: '100%', height: '100%', objectFit: 'cover', display: 'block' },
  textCol: { minWidth: 0, display: 'flex', flexDirection: 'column', gap: 6, paddingTop: 2 },
  title: {
    fontFamily: 'var(--font-serif)',
    fontWeight: 600,
    fontSize: 14,
    lineHeight: 1.25,
    color: '#fff',
    margin: 0,
    overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
  },
  timeRow: { display: 'flex', alignItems: 'center', gap: 10 },
  timeLabel: {
    fontFamily: 'var(--font-mono)',
    fontSize: 10.5,
    color: 'rgba(255,255,255,0.55)',
    fontVariantNumeric: 'tabular-nums',
    flexShrink: 0,
    minWidth: 32,
  },
  scrubberWrap: { flex: 1 },
  controls: { display: 'flex', alignItems: 'center', gap: 6 },
  ctrlBtn: {
    width: 38, height: 38, borderRadius: '50%',
    background: 'transparent',
    color: 'rgba(255,255,255,0.85)',
    border: 'none',
    cursor: 'pointer',
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    flexShrink: 0,
  },
  playBtn: {
    width: 46, height: 46, borderRadius: '50%',
    background: 'var(--brand-coral)',
    color: '#fff',
    border: 'none',
    cursor: 'pointer',
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    flexShrink: 0,
    boxShadow: '0 6px 18px rgba(194,106,74,0.40)',
  },
  rateBtn: {
    fontFamily: 'var(--font-mono)',
    fontSize: 11, fontWeight: 700,
    background: 'rgba(255,255,255,0.10)',
    color: 'rgba(255,255,255,0.90)',
    border: 'none',
    padding: '7px 9px', borderRadius: 8,
    cursor: 'pointer',
    minWidth: 38,
    flexShrink: 0,
  },
  closeBtn: {
    width: 30, height: 30, borderRadius: '50%',
    background: 'rgba(255,255,255,0.08)',
    color: 'rgba(255,255,255,0.70)',
    border: 'none',
    cursor: 'pointer',
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    flexShrink: 0,
    marginLeft: 4,
  },
};

const RATE_OPTIONS = [1, 1.25, 1.5, 2];
function nextRate(r) {
  const i = RATE_OPTIONS.indexOf(r);
  return RATE_OPTIONS[(i + 1) % RATE_OPTIONS.length] || 1;
}

function MiniPlayer() {
  const player = usePodcastPlayer();
  const [dismissed, setDismissed] = React.useState(false);
  const [compact, setCompact] = React.useState(typeof window !== 'undefined' && window.innerWidth < 720);

  React.useEffect(() => {
    const onResize = () => setCompact(window.innerWidth < 720);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  // Reset dismissed state when a new episode loads
  React.useEffect(() => {
    if (player.episode) setDismissed(false);
  }, [player.episode?.id]);

  const ep = player.episode;
  const visible = !!ep && !dismissed;

  const handleClose = () => {
    player.pause();
    setDismissed(true);
  };

  const cur = player.currentTime || 0;
  const dur = player.duration || (ep?.durationSec || 0);

  return (
    <div style={miniStyles.wrap(visible)} aria-hidden={!visible}>
      <div style={{
        ...miniStyles.bar,
        gridTemplateColumns: compact ? '44px 1fr auto' : '56px 1fr auto',
        gap: compact ? 12 : 20,
        padding: compact ? '10px 12px 10px 10px' : '12px 18px 12px 12px',
      }}>
        {/* Cover art */}
        <div style={{ ...miniStyles.art, width: compact ? 44 : 56, height: compact ? 44 : 56 }}>
          {ep && (
            <img
              src={ep.artworkUrl || 'podcast-cover.jpg'}
              alt=""
              style={miniStyles.artImg}
              onError={(e) => { e.currentTarget.style.display = 'none'; }}
            />
          )}
        </div>

        {/* Title + scrubber */}
        <div style={miniStyles.textCol}>
          <h4 style={miniStyles.title}>{ep?.title || ''}</h4>
          <div style={miniStyles.timeRow}>
            {!compact && <span style={miniStyles.timeLabel}>{fmtTime(cur)}</span>}
            <div style={miniStyles.scrubberWrap}>
              <Scrubber
                value={cur}
                max={dur || 1}
                onChange={(t) => player.seekTo(t)}
                accent="var(--brand-coral)"
                track="rgba(255,255,255,0.18)"
                height={4}
              />
            </div>
            {!compact && <span style={miniStyles.timeLabel}>{fmtTime(dur)}</span>}
          </div>
        </div>

        {/* Controls */}
        <div style={miniStyles.controls}>
          {!compact && (
            <button style={miniStyles.ctrlBtn} onClick={() => player.seekBy(-15)} aria-label="Back 15 seconds" title="Back 15s">
              <Rewind15 size={20}/>
            </button>
          )}
          <button
            style={miniStyles.playBtn}
            onClick={() => ep && player.toggle(ep)}
            aria-label={player.isPlaying ? 'Pause' : 'Play'}
          >
            {player.loading
              ? <span style={{ width: 18, height: 18, borderRadius: '50%', border: '2.5px solid rgba(255,255,255,0.35)', borderTopColor: '#fff', animation: 'ppspin 0.8s linear infinite' }}/>
              : (player.isPlaying ? <PauseIcon size={20}/> : <PlayIcon size={20}/>)}
          </button>
          {!compact && (
            <button style={miniStyles.ctrlBtn} onClick={() => player.seekBy(15)} aria-label="Forward 15 seconds" title="Forward 15s">
              <Forward15 size={20}/>
            </button>
          )}
          {!compact && (
            <button
              style={miniStyles.rateBtn}
              onClick={() => player.setRate(nextRate(player.rate))}
              title="Playback speed"
              aria-label="Playback speed"
            >
              {player.rate}x
            </button>
          )}
          <button style={miniStyles.closeBtn} onClick={handleClose} aria-label="Close player" title="Close">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round">
              <path d="M6 6l12 12M18 6L6 18"/>
            </svg>
          </button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, {
  usePodcastPlayer,
  NativeAudioPlayer,
  EpisodeProgressBar,
  MiniPlayer,
  fmtTime,
});
