// ---------- Course carousel ----------
// Pulls categories + courses from the shared WB_* globals so the homepage
// carousel and the All-Courses subpage stay in sync.
//
// Carousel model (rewritten 2024-12): track is a transform-based row, not an
// overflow-scroll container. Pointer drag with setPointerCapture moves the
// row; on release we snap to the nearest card index. This sidesteps the iOS
// Safari quirks around overflow + nested touch handlers that broke swiping.
// Convert "#RRGGBB" to "rgba(r,g,b,a)" so we can build fade gradients in JS.
function hexToRgba(hex, a) {
  const h = String(hex).replace('#', '');
  const n = parseInt(h.length === 3 ? h.split('').map(c => c + c).join('') : h, 16);
  return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${a})`;
}

// Backend-Daten reaktiv halten: das Carousel re-rendert, sobald
// course-data.jsx einen frischen Fetch abgeschlossen hat.
const useCarouselData = () => {
  const [version, setVersion] = React.useState(0);
  React.useEffect(() => {
    const onUpdate = () => setVersion((v) => v + 1);
    window.addEventListener('wb-courses-updated', onUpdate);
    return () => window.removeEventListener('wb-courses-updated', onUpdate);
  }, []);
  return React.useMemo(() => {
    const cats = window.WB_CATEGORIES || [];
    const courses = window.WB_COURSES || [];
    // Flach + kategorie-interleaved für ein bunt durchmischtes Pill-Band.
    const buckets = cats.map((cat) => ({ cat, list: courses.filter((c) => c.cat === cat.id) }));
    const max = buckets.length ? Math.max(...buckets.map((b) => b.list.length)) : 0;
    const directCourses = [];
    for (let i = 0; i < max; i++) {
      buckets.forEach(({ cat, list }) => {
        if (list[i]) directCourses.push({ label: list[i].title, cat, course: list[i] });
      });
    }
    return { cats, courses, directCourses };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [version]);
};

const CourseCarousel = ({ onOpenInquiry }) => {
  const stageRef = React.useRef(null);
  const trackRef = React.useRef(null);
  const pillsRef = React.useRef(null);
  const [activeBubble, setActiveBubble] = React.useState(null);
  const [openCourse, setOpenCourse] = React.useState(null);
  const headRef = useReveal();
  const { cats: courseCategories, directCourses } = useCarouselData();

  // Card geometry — measured at runtime, recomputed on resize.
  const [geom, setGeom] = React.useState({ cardW: 240, gap: 18, stageW: 1200 });

  // Active card index — never wraps, can go negative or > N. We render the row
  // long enough to cover any reasonable index, and modulo into the data array
  // when picking which category a slot shows.
  const N = courseCategories.length;
  const [index, setIndex] = React.useState(0);
  const [drag, setDrag] = React.useState(0); // px, additional translate while dragging

  const dragRef = React.useRef({ active: false, startX: 0, startIndex: 0, moved: false });

  // Measure: card width = clamp(200, 22vw, 260), gap = 18px.
  React.useEffect(() => {
    const measure = () => {
      const stage = stageRef.current;
      if (!stage) return;
      const card = stage.querySelector('.crs-card');
      const cardW = card ? card.offsetWidth : 240;
      setGeom({ cardW, gap: 18, stageW: stage.clientWidth });
    };
    measure();
    window.addEventListener('resize', measure);
    return () => window.removeEventListener('resize', measure);
  }, []);

  // Externe Trigger zum Öffnen einer Kurs-Detail-Ansicht (z.B. aus dem
  // Aktuelles-Karussell): hört auf 'wb-open-course-by-id' und sucht den
  // Kurs in der globalen Liste.
  React.useEffect(() => {
    const onOpenById = (e) => {
      const id = e?.detail?.id;
      if (!id) return;
      const course = (window.WB_COURSES || []).find((c) => c.id === id);
      if (course) setOpenCourse(course);
    };
    window.addEventListener('wb-open-course-by-id', onOpenById);
    return () => window.removeEventListener('wb-open-course-by-id', onOpenById);
  }, []);

  const step = geom.cardW + geom.gap;

  // Bounded carousel: how many full cards fit in the visible stage area.
  // The track sits flush with the container's left and right edges so the
  // first card aligns with the heading and the last card aligns with the
  // "Alle Kurse" button. Arrows float over the card edges.
  const SIDE_GUTTER = 56;
  const visibleW = geom.stageW;
  const visibleCount = Math.max(1, Math.floor((visibleW + geom.gap) / step));
  const maxIndex = Math.max(0, N - visibleCount);
  const clampIndex = (i) => Math.max(0, Math.min(maxIndex, i));

  // Left-align: card 0 sits flush with the stage's inner padding (SIDE_GUTTER).
  const baseOffset = -index * step;

  // Rubber-band drag past the edges so the user feels the stop.
  const RUBBER = 0.35;
  const rubberize = (dx) => {
    // dx > 0 means dragging right (toward earlier cards).
    if (dx > 0 && index <= 0) return dx * RUBBER;
    if (dx < 0 && index >= maxIndex) return dx * RUBBER;
    return dx;
  };

  // Pointer-drag to swipe.
  const onPointerDown = (e) => {
    if (e.button !== undefined && e.button !== 0) return;
    // Don't capture if user clicked the side arrow or any nested button
    const t = e.target;
    if (t && t.closest && t.closest('[data-side-arrow]')) return;
    // NOTE: we deliberately do NOT call setPointerCapture here. Capturing on
    // pointerdown re-targets the synthesized click event to the stage div,
    // which swallows clicks on the <a> cards (the browser never sees them as
    // link activations). We only capture once the user has actually moved
    // past the drag threshold — see onPointerMove below.
    dragRef.current = {
      active: true, startX: e.clientX, startIndex: index, moved: false,
      pointerId: e.pointerId, captured: false,
    };
  };
  const onPointerMove = (e) => {
    if (!dragRef.current.active) return;
    const dx = e.clientX - dragRef.current.startX;
    if (!dragRef.current.moved && Math.abs(dx) > 5) {
      dragRef.current.moved = true;
      // Now that we know it's a drag, capture the pointer so we keep getting
      // move/up events even if the cursor leaves the stage.
      if (!dragRef.current.captured) {
        try {
          e.currentTarget.setPointerCapture(e.pointerId);
          dragRef.current.captured = true;
        } catch (_) {}
      }
    }
    if (dragRef.current.moved) {
      setDrag(rubberize(dx));
    }
  };
  const onPointerUp = (e) => {
    if (!dragRef.current.active) return;
    const dx = e.clientX - dragRef.current.startX;
    dragRef.current.active = false;
    setDrag(0);
    // Snap: shift index by however many "steps" the drag covered (rounded),
    // clamped to [0, maxIndex] so we hit a hard stop at the ends.
    const delta = Math.round(-dx / step);
    if (delta !== 0) setIndex(i => clampIndex(i + delta));
    if (dragRef.current.captured) {
      try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
    }
    // Swallow the click that follows a drag so cards don't open accidentally
    if (dragRef.current.moved) {
      const swallow = (ev) => { ev.stopPropagation(); ev.preventDefault(); };
      e.currentTarget.addEventListener('click', swallow, { capture: true, once: true });
      // Reset moved flag for next interaction
      setTimeout(() => { dragRef.current.moved = false; }, 0);
    }
  };

  // Re-clamp the active index when the viewport changes (resize might
  // increase visibleCount and lower maxIndex below the current index).
  React.useEffect(() => {
    setIndex(i => clampIndex(i));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [maxIndex]);

  // Render slots: only the real categories — no infinite wrap. Bounds give
  // the slider a clear start and end.
  const slots = courseCategories.map((cat, i) => ({ slot: i, cat }));

  const atStart = index <= 0;
  const atEnd = index >= maxIndex;

  const scrollPillsBy = (dir) => {
    const p = pillsRef.current;
    if (!p) return;
    p.scrollBy({ left: dir * Math.max(220, p.clientWidth * 0.6), behavior: 'smooth' });
  };

  // Track pill scroll position so we can disable the arrows at the ends.
  const [pillBounds, setPillBounds] = React.useState({ atStart: true, atEnd: false });
  React.useEffect(() => {
    const p = pillsRef.current;
    if (!p) return;
    const update = () => {
      const max = p.scrollWidth - p.clientWidth;
      setPillBounds({
        atStart: p.scrollLeft <= 1,
        atEnd: p.scrollLeft >= max - 1,
      });
    };
    update();
    p.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update);
    return () => {
      p.removeEventListener('scroll', update);
      window.removeEventListener('resize', update);
    };
  }, []);

  return (
    <section id="angebot" style={{ padding: 'clamp(56px, 8vw, 96px) 0 clamp(40px, 6vw, 64px)' }}>
      <div className="container">
        <div ref={headRef} className="reveal" style={{
          marginBottom: 48, display: 'flex', alignItems: 'flex-end',
          justifyContent: 'space-between', gap: 24, flexWrap: 'wrap',
        }}>
          <div style={{ maxWidth: 720 }}>
            <div className="eyebrow" style={{ marginBottom: 16 }}>{window.wbText('kurse.eyebrow', 'Kurse')}</div>
            <h2 style={{
              fontSize: 'clamp(36px, 5vw, 56px)', lineHeight: 1.1, letterSpacing: '-0.015em',
              marginBottom: 20,
              fontVariationSettings: '"opsz" 96, "SOFT" 50, "WONK" 1',
            }}>
              {window.wbT('kurse.headline', 'Kurse für alle [Lebensphasen].')}
            </h2>
            <p className="lead" style={{ maxWidth: 600 }}>
              {window.wbText('kurse.lead', 'Vom ersten Spüren bis lange nach der Geburt — und durch alles, was Frausein sonst noch ausmacht. Kurse, Einzelbegleitung, Frauenkreise.')}
            </p>
          </div>
          <a href="Alle Kurse.html" className="btn btn-secondary" style={{
            textDecoration: 'none', whiteSpace: 'nowrap',
          }}>
            Alle Kurse <span style={{ fontSize: 17, marginLeft: 2 }}>→</span>
          </a>
        </div>
      </div>

      {/* Subheading: Kategorien */}
      <div className="container" style={{ marginBottom: 18 }}>
        <div style={{
          display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap',
          color: 'var(--ink-70)',
        }}>
          <span className="eyebrow" style={{ color: 'var(--heather-plum)' }}>● Kategorien</span>
          <span style={{ fontSize: 14, color: 'var(--ink-50)' }}>
            Lebensphasen im Überblick — wisch durch oder klick eine Karte
          </span>
        </div>
      </div>

      {/* Empty-State falls keine Kategorien gepflegt sind */}
      {courseCategories.length === 0 ? (
        <div className="container">
          <div style={{
            background: 'var(--offwhite)',
            border: '1px dashed var(--sandstone-border)',
            borderRadius: 14,
            padding: 'clamp(28px, 4vw, 40px)',
            textAlign: 'center',
            color: 'var(--ink-70)',
            fontSize: 15, lineHeight: 1.6,
          }}>
            Aktuell sind keine Kurs-Kategorien angelegt. Sobald wir hier Kurse
            ausschreiben, tauchen sie als Karten auf.
          </div>
        </div>
      ) : (
      <>
      {/* Card carousel — transform-based, pointer-drag swipe */}
      <div className="container" style={{ position: 'relative' }}>
        <CrsArrow dir={-1} onClick={() => setIndex(i => clampIndex(i - 1))} gutter={SIDE_GUTTER} disabled={atStart} />
        <CrsArrow dir={1} onClick={() => setIndex(i => clampIndex(i + 1))} gutter={SIDE_GUTTER} disabled={atEnd} />

        <div
          ref={stageRef}
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={onPointerUp}
          onPointerCancel={onPointerUp}
          style={{
            position: 'relative',
            overflow: 'hidden',
            padding: '4px 0 24px',
            touchAction: 'pan-y',
            cursor: dragRef.current.active ? 'grabbing' : 'grab',
            userSelect: 'none',
            WebkitUserSelect: 'none',
          }}
        >
          <div
            ref={trackRef}
            style={{
              display: 'flex', gap: geom.gap,
              paddingLeft: 0,
              paddingRight: 0,
              transform: `translate3d(${baseOffset + drag}px, 0, 0)`,
              transition: dragRef.current.active ? 'none' : 'transform 480ms cubic-bezier(0.2, 0.8, 0.2, 1)',
              willChange: 'transform',
            }}
          >
            {slots.map(({ slot, cat: c }) => (
              <a key={slot} className="crs-card"
                href={`Alle Kurse.html#cat=${c.id}`}
                style={{
                  flex: '0 0 clamp(200px, 22vw, 260px)',
                  aspectRatio: '3/4',
                  borderRadius: 20,
                  background: c.tone.bg,
                  color: c.tone.text,
                  textDecoration: 'none',
                  position: 'relative',
                  overflow: 'hidden',
                  cursor: 'pointer',
                  boxShadow: '0 4px 14px rgba(42,34,29,0.08)',
                  transition: 'transform 240ms var(--ease)',
                  padding: '22px 22px 26px',
                  WebkitTapHighlightColor: 'transparent',
                }}
                onMouseEnter={(e) => e.currentTarget.style.transform = 'translateY(-4px)'}
                onMouseLeave={(e) => e.currentTarget.style.transform = 'translateY(0)'}
                onClick={(e) => {
                  // Block native navigation only if this was a drag, not a click.
                  if (dragRef.current.moved) {
                    e.preventDefault();
                    e.stopPropagation();
                  }
                }}
                onDragStart={(e) => e.preventDefault()}
              >
                {/* Themed photo backdrop — Bild kommt aus dem Admin (Kategorie-Editor).
                    Wenn kein Bild gesetzt: kein <img>, nur sanfter Gradient — kein Stock-Bild. */}
                <div aria-hidden="true" style={{
                  position: 'absolute', top: 0, left: 0, right: 0, height: '62%',
                  overflow: 'hidden',
                  pointerEvents: 'none',
                }}>
                  {c.imageUrl && (
                    <img
                      src={c.imageUrl}
                      alt=""
                      loading="lazy"
                      draggable={false}
                      style={{
                        width: '100%', height: '100%', objectFit: 'cover',
                        mixBlendMode: 'multiply',
                        opacity: 0.78,
                        filter: 'saturate(0.85)',
                      }}
                    />
                  )}
                  <div style={{
                    position: 'absolute', inset: 0,
                    background: c.tone.solid,
                    mixBlendMode: c.imageUrl ? 'color' : 'normal',
                    opacity: c.imageUrl ? 0.55 : 1,
                  }} />
                  <div style={{
                    position: 'absolute', left: 0, right: 0, bottom: -1, height: '60%',
                    background: `linear-gradient(180deg, ${hexToRgba(c.tone.solid, 0)} 0%, ${c.tone.solid} 92%)`,
                  }} />
                </div>

                {/* Eyebrow oben — absolut, damit der untere Block immer
                    an einer fixen Position startet (top-aligned über alle
                    Karten hinweg, unabhängig von der Beschreibungslänge). */}
                <div style={{
                  position: 'absolute', top: 22, left: 22, right: 22, zIndex: 2,
                  display: 'flex', justifyContent: 'space-between',
                  fontSize: 11, letterSpacing: '0.16em', textTransform: 'uppercase',
                  opacity: 0.85, fontWeight: 600,
                }}>
                  <span>{c.eyebrow}</span>
                  <span>↗</span>
                </div>

                {/* Titel + Beschreibung — Ankern bei 62% von oben (genau am
                    Gradient-Übergang), damit die Headlines aller Karten oben
                    bündig stehen. */}
                <div style={{
                  position: 'absolute', top: '62%', left: 22, right: 22, zIndex: 2,
                }}>
                  <h3 style={{
                    fontFamily: 'Fraunces, serif',
                    fontSize: 'clamp(20px, 2.1vw, 26px)',
                    lineHeight: 1.1, marginBottom: 10,
                    fontVariationSettings: '"opsz" 96, "SOFT" 40',
                    color: c.tone.text,
                  }}>{c.title}</h3>
                  <p style={{
                    fontSize: 13, lineHeight: 1.5,
                    opacity: 0.88, maxWidth: 220,
                    // Sicherheitsnetz fürs Layout: Altbestand mit zu langem
                    // Text wird auf 3 Zeilen geclampt. Admin-Limit (120 Zeichen)
                    // verhindert das eigentlich schon.
                    display: '-webkit-box',
                    WebkitLineClamp: 3,
                    WebkitBoxOrient: 'vertical',
                    overflow: 'hidden',
                  }}>{c.body}</p>
                </div>
              </a>
            ))}
          </div>
        </div>
      </div>

      {/* Subheading: Einzelne Kurse */}
      <div className="container" style={{ marginTop: 40, marginBottom: 14 }}>
        <div style={{
          display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap',
          color: 'var(--ink-70)',
        }}>
          <span className="eyebrow" style={{ color: 'var(--heather-plum)' }}>● Einzelne Kurse</span>
          <span style={{ fontSize: 14, color: 'var(--ink-50)' }}>
            Direkt zum Kurs — tippe einen Kurs für Details
          </span>
        </div>
      </div>

      {/* Empty-State falls keine Kurse gepflegt sind */}
      {directCourses.length === 0 ? (
        <div className="container">
          <div style={{
            background: 'var(--offwhite)',
            border: '1px dashed var(--sandstone-border)',
            borderRadius: 14,
            padding: 'clamp(20px, 3vw, 28px)',
            textAlign: 'center',
            color: 'var(--ink-70)',
            fontSize: 14, lineHeight: 1.6,
          }}>
            Aktuell sind keine Einzel-Kurse ausgeschrieben. Schau bald wieder
            vorbei — oder schreib uns direkt.
          </div>
        </div>
      ) : (
      <>
      {/* Single-row pill carousel — colored by parent category. Stays overflow-scroll;
          pills are tiny and don't have iOS issues. */}
      <div className="container" style={{ position: 'relative' }}>
        <CrsArrow dir={-1} onClick={() => scrollPillsBy(-1)} gutter={SIDE_GUTTER} small disabled={pillBounds.atStart} />
        <CrsArrow dir={1} onClick={() => scrollPillsBy(1)} gutter={SIDE_GUTTER} small disabled={pillBounds.atEnd} />

        <div ref={pillsRef} className="crs-pills" style={{
          display: 'flex', gap: 8,
          overflowX: 'auto', overflowY: 'hidden',
          padding: `8px ${SIDE_GUTTER}px`,
          scrollbarWidth: 'none',
          WebkitOverflowScrolling: 'touch',
          scrollSnapType: 'x proximity',
        }}>
          <style>{`.crs-pills::-webkit-scrollbar{display:none}`}</style>
          {directCourses.map(({ label, cat, course }, i) => {
            const active = activeBubble === label;
            return (
              <button key={`${cat.id}-${label}-${i}`} type="button"
                title={cat.title}
                onClick={() => { setActiveBubble(label); setOpenCourse(course); }}
                style={{
                  fontSize: 12, fontWeight: 500,
                  padding: '7px 14px', borderRadius: 999,
                  background: active ? cat.tone.bg : cat.pill.bg,
                  color: active ? cat.tone.text : cat.pill.text,
                  border: '1px solid ' + (active ? 'transparent' : cat.pill.border),
                  cursor: 'pointer',
                  transition: 'all 160ms var(--ease)',
                  whiteSpace: 'nowrap',
                  flex: '0 0 auto',
                  fontFamily: 'inherit',
                  scrollSnapAlign: 'start',
                  display: 'inline-flex', alignItems: 'center', gap: 6,
                }}
                onMouseEnter={(e) => {
                  if (!active) {
                    e.currentTarget.style.transform = 'translateY(-1px)';
                    e.currentTarget.style.boxShadow = '0 2px 6px rgba(42,34,29,0.08)';
                  }
                }}
                onMouseLeave={(e) => {
                  if (!active) {
                    e.currentTarget.style.transform = 'translateY(0)';
                    e.currentTarget.style.boxShadow = 'none';
                  }
                }}
              >
                <span style={{
                  width: 6, height: 6, borderRadius: 999,
                  background: cat.pill.text,
                  opacity: active ? 0.85 : 0.7,
                  flexShrink: 0,
                }} />
                {label}
              </button>
            );
          })}
        </div>
      </div>
      </>
      )}
      </>
      )}
      {(() => {
        const Detail = window.WBCourseDetail;
        return openCourse && Detail ? (
          <Detail course={openCourse} onClose={() => { setOpenCourse(null); setActiveBubble(null); }} />
        ) : null;
      })()}
    </section>
  );
};

// Floating side arrow — uses the same gutter so card arrows + pill arrows line up vertically.
const CrsArrow = ({ dir, onClick, gutter = 56, small, disabled }) => {
  const size = small ? 32 : 40;
  const offset = Math.max(8, (gutter - size) / 2);
  return (
    <button type="button" onClick={disabled ? undefined : onClick}
      data-side-arrow="true"
      disabled={disabled}
      aria-label={dir < 0 ? 'Vorherige' : 'Nächste'}
      aria-disabled={disabled || undefined}
      style={{
        position: 'absolute',
        top: '50%',
        [dir < 0 ? 'left' : 'right']: offset,
        transform: 'translateY(-50%)',
        zIndex: 5,
        width: size, height: size, borderRadius: 999,
        background: 'rgba(250, 246, 241, 0.9)',
        backdropFilter: 'blur(8px)',
        WebkitBackdropFilter: 'blur(8px)',
        border: '1px solid rgba(196, 169, 143, 0.5)',
        color: 'var(--ink)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        cursor: disabled ? 'not-allowed' : 'pointer',
        fontSize: small ? 13 : 15,
        boxShadow: disabled ? 'none' : '0 4px 12px rgba(42,34,29,0.10)',
        opacity: disabled ? 0.35 : 1,
        transition: 'all 160ms var(--ease)',
      }}
      onMouseEnter={(e) => {
        if (disabled) return;
        e.currentTarget.style.background = 'var(--heather-plum)';
        e.currentTarget.style.color = 'var(--offwhite)';
        e.currentTarget.style.borderColor = 'var(--heather-plum)';
      }}
      onMouseLeave={(e) => {
        if (disabled) return;
        e.currentTarget.style.background = 'rgba(250, 246, 241, 0.9)';
        e.currentTarget.style.color = 'var(--ink)';
        e.currentTarget.style.borderColor = 'rgba(196, 169, 143, 0.5)';
      }}
    >{dir < 0 ? '←' : '→'}</button>
  );
};

window.CourseCarousel = CourseCarousel;
