// Client Feedback — draggable Tinder-style card stack.
//
// Ported (not copy-pasted) from a shadcn/Tailwind/TypeScript + framer-motion
// reference component the user supplied. This project has none of that
// stack — no package.json, no bundler, no Tailwind, no TS; React/Babel/GSAP
// are plain <script> tags and Motion (window.Motion, loaded via CDN in
// index.html) is the vanilla build, which has no built-in drag/gesture
// recognizer. So the VISUAL DESIGN and INTERACTION are reproduced exactly
// (drag to dismiss, spring back under the threshold, stacked cards behind),
// but built with plain pointer events + this site's existing CSS tokens
// instead of Tailwind classes and framer-motion's <motion.div drag>.
//
// Drag itself is driven by direct style writes (1:1 cursor tracking, no
// spring — a spring DURING active drag is what made the magnetic buttons
// feel disconnected earlier). Motion's spring only kicks in once, at
// release, using the numeric "drive a value" pattern (safe: no ambiguity
// about what the "current" value is, since the true current position is
// tracked in a plain JS variable throughout the drag).
function Star({ filled }) {
  return (
    <svg width="15" height="15" viewBox="0 0 24 24" fill={filled ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="1.5">
      <path d="M12 2.5l2.9 6.6 7.1.7-5.4 4.8 1.6 7-6.2-3.7-6.2 3.7 1.6-7-5.4-4.8 7.1-.7L12 2.5z" strokeLinejoin="round" />
    </svg>
  );
}

function StarRow({ rating = 5 }) {
  return (
    <div className="testi-stars" role="img" aria-label={rating + ' out of 5 stars'}>
      {[1, 2, 3, 4, 5].map((n) => <Star key={n} filled={n <= rating} />)}
    </div>
  );
}

// Resting vs. "rising" (mid-drag) look for the card peeking out behind the
// top one — interpolated by hand below since these are plain numbers, not
// something a CSS transition can be handed mid-gesture.
const BACK_REST = { scale: 0.96, y: 10, opacity: 0.85 };
const BACK_RISEN = { scale: 0.99, y: 3, opacity: 1 };

// The draggable top card. Remounts (fresh instance, fresh listeners) each
// time the current testimonial changes, via the `key` the parent gives it.
// `swipeRef` lets the PARENT trigger the exact same fly-off-and-advance
// sequence a manual drag produces — used for autoplay, so the timer-driven
// advance looks like a swipe instead of the testimonial just snapping to
// the next one.
function TopCard({ t, onSettled, stageRef, swipeRef }) {
  const ref = React.useRef(null);

  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const M = window.Motion;
    const THRESHOLD = 100;
    let dragging = false;
    let startX = 0;
    let curX = 0;
    let pointerId = null;
    let backCard = null;

    const paint = (x) => {
      el.style.transform = 'translate3d(' + x.toFixed(1) + 'px,0,0) rotate(' + (x / 20).toFixed(2) + 'deg)';
    };
    // The card directly behind rises to meet the top card as you drag,
    // proportional to how far past the release threshold you've gone.
    const paintBack = (x) => {
      if (!backCard) backCard = stageRef.current && stageRef.current.querySelector('[data-depth="1"]');
      if (!backCard) return;
      const p = Math.min(1, Math.abs(x) / THRESHOLD);
      const scale = BACK_REST.scale + (BACK_RISEN.scale - BACK_REST.scale) * p;
      const y = BACK_REST.y + (BACK_RISEN.y - BACK_REST.y) * p;
      const opacity = BACK_REST.opacity + (BACK_RISEN.opacity - BACK_REST.opacity) * p;
      backCard.style.transform = 'translateY(' + y.toFixed(2) + 'px) scale(' + scale.toFixed(3) + ')';
      backCard.style.opacity = opacity.toFixed(3);
    };
    const resetBack = () => {
      if (!backCard) return;
      backCard.style.transition = 'transform .3s ease, opacity .3s ease';
      backCard.style.transform = '';
      backCard.style.opacity = '';
    };

    // Shared by both a real drag-release past the threshold AND a
    // programmatic autoplay swipe — same fly-off, same fade, same
    // settle-then-advance timing either way.
    //
    // This is a TWEEN, not a spring. A spring is built to decelerate and
    // settle INTO its target — right for "spring back to center" below,
    // but for "fly off and vanish" it reads as the card gently braking
    // near the edge instead of accelerating away. ease-in keeps building
    // speed the whole way, which looks like a natural flick.
    //
    // Position, rotation, and opacity all ride the SAME animate() call
    // (one timeline) instead of position being driven by JS while opacity
    // fades on a separate CSS transition — two independently-timed
    // animations can drift out of sync and create a flash where the old
    // card lingers a beat after the new one is already mounted.
    const EXIT_DURATION = 420; // ms — also how long we wait before unmounting
    const flyOff = (dir, fromX) => {
      const flyTo = dir * ((typeof window !== 'undefined' ? window.innerWidth : 1200) * 0.9);
      resetBack();
      if (M) {
        M.animate(el, { x: flyTo, rotate: dir * 24, opacity: 0 }, { type: 'tween', ease: 'easeIn', duration: EXIT_DURATION / 1000 });
      } else {
        el.style.transition = 'transform ' + EXIT_DURATION + 'ms ease-in, opacity ' + EXIT_DURATION + 'ms ease-in';
        paint(flyTo);
        el.style.opacity = '0';
      }
      setTimeout(() => onSettled(true), EXIT_DURATION);
    };

    const onDown = (e) => {
      dragging = true;
      pointerId = e.pointerId;
      try { el.setPointerCapture(pointerId); } catch (err) {}
      startX = e.clientX;
      el.style.transition = 'none';
      el.classList.add('is-dragging');
      backCard = stageRef.current && stageRef.current.querySelector('[data-depth="1"]');
      if (backCard) backCard.style.transition = 'none';
    };
    const onMove = (e) => {
      if (!dragging) return;
      curX = e.clientX - startX;
      paint(curX);
      paintBack(curX);
    };
    const onUp = () => {
      if (!dragging) return;
      dragging = false;
      el.classList.remove('is-dragging');
      const exceeded = Math.abs(curX) > THRESHOLD;
      const dir = curX >= 0 ? 1 : -1;
      const from = curX;

      if (exceeded) {
        flyOff(dir, from);
      } else {
        if (backCard) backCard.style.transition = 'transform .3s ease, opacity .3s ease';
        if (M) {
          M.animate(from, 0, { type: 'spring', stiffness: 300, damping: 22, onUpdate: (v) => paint(v) });
        } else {
          el.style.transition = 'transform .3s cubic-bezier(.2,.8,.2,1)';
          paint(0);
        }
        onSettled(false);
        resetBack();
      }
      curX = 0;
    };

    el.addEventListener('pointerdown', onDown);
    el.addEventListener('pointermove', onMove);
    el.addEventListener('pointerup', onUp);
    el.addEventListener('pointercancel', onUp);
    if (swipeRef) swipeRef.current = (dir) => { if (!dragging) flyOff(dir, 0); };
    return () => {
      el.removeEventListener('pointerdown', onDown);
      el.removeEventListener('pointermove', onMove);
      el.removeEventListener('pointerup', onUp);
      el.removeEventListener('pointercancel', onUp);
      if (swipeRef && swipeRef.current) swipeRef.current = null;
    };
  }, [onSettled, stageRef, swipeRef]);

  return (
    <div className="testi-card testi-card-top" ref={ref} data-depth="0">
      <StarRow rating={t.rating} />
      <div className="testi-quote">&ldquo;{t.quote}&rdquo;</div>
      <div className="testi-person">
        <span className="testi-avatar">{t.name.split(' ').map((w) => w[0]).slice(0, 2).join('')}</span>
        <span>
          <span className="testi-name">{t.name}</span>
          <span className="testi-role">{t.role}</span>
        </span>
      </div>
      <span className="testi-drag-hint">drag &#8596;</span>
    </div>
  );
}

function Testimonials() {
  const items = window.SITE_DATA.testimonials;
  const n = items.length;
  const [index, setIndex] = React.useState(0);
  const [paused, setPaused] = React.useState(false);
  const stageRef = React.useRef(null);
  const swipeRef = React.useRef(null);
  const autoDirRef = React.useRef(1); // alternates each tick: left, right, left, right…

  React.useEffect(() => {
    if (paused) return;
    // Trigger the SAME fly-off-and-settle animation a manual drag produces
    // (via the top card's exposed swipeRef) instead of snapping the index
    // straight to the next testimonial — the timer should look like a
    // swipe happened, not an instant cut. Direction alternates left/right
    // each tick, purely for visual variety — the advance is always forward.
    const id = setInterval(() => {
      autoDirRef.current = -autoDirRef.current;
      if (swipeRef.current) swipeRef.current(autoDirRef.current);
      else setIndex((i) => (i + 1) % n); // swipeRef not mounted yet — fall back to a direct advance
    }, 5000);
    return () => clearInterval(id);
  }, [paused, n]);

  const advance = React.useCallback((didExit) => {
    if (didExit) setIndex((i) => (i + 1) % n);
  }, [n]);

  const cur = items[index];
  const next1 = items[(index + 1) % n];
  const next2 = items[(index + 2) % n];

  return (
    <section className="section container" data-screen-label="Testimonials">
      <Reveal><div className="eyebrow"><b>/</b> TESTIMONIALS</div></Reveal>
      <Reveal><h2 className="h2"><window.AnimatedText parts={[{t:'Client '},{t:'Feedback',accent:true},{t:'.'}]} repel /></h2></Reveal>
      <Reveal>
        <div
          className="testi-stage"
          ref={stageRef}
          onMouseEnter={() => setPaused(true)}
          onMouseLeave={() => setPaused(false)}
        >
          {n > 2 && (
            <div className="testi-card testi-card-back" data-depth="2" key={'d2-' + (index + 2) % n}>
              <StarRow rating={next2.rating} />
              <div className="testi-quote">&ldquo;{next2.quote}&rdquo;</div>
              <div className="testi-person">
                <span className="testi-avatar">{next2.name.split(' ').map((w) => w[0]).slice(0, 2).join('')}</span>
                <span>
                  <span className="testi-name">{next2.name}</span>
                  <span className="testi-role">{next2.role}</span>
                </span>
              </div>
            </div>
          )}
          {n > 1 && (
            <div className="testi-card testi-card-back" data-depth="1" key={'d1-' + (index + 1) % n}>
              <StarRow rating={next1.rating} />
              <div className="testi-quote">&ldquo;{next1.quote}&rdquo;</div>
              <div className="testi-person">
                <span className="testi-avatar">{next1.name.split(' ').map((w) => w[0]).slice(0, 2).join('')}</span>
                <span>
                  <span className="testi-name">{next1.name}</span>
                  <span className="testi-role">{next1.role}</span>
                </span>
              </div>
            </div>
          )}
          <TopCard key={'top-' + index} t={cur} onSettled={advance} stageRef={stageRef} swipeRef={swipeRef} />
        </div>
      </Reveal>
      <div className="testi-controls">
        <button className="fan-arrow" onClick={() => setIndex((i) => (i - 1 + n) % n)} aria-label="Previous testimonial">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
        </button>
        <div className="testi-dots">
          {items.map((_, i) => (
            <span key={i} className={`testi-dot ${i === index ? 'active' : ''}`} onClick={() => setIndex(i)}></span>
          ))}
        </div>
        <button className="fan-arrow" onClick={() => setIndex((i) => (i + 1) % n)} aria-label="Next testimonial">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
        </button>
      </div>
    </section>
  );
}

window.Testimonials = Testimonials;
