/* Draggable primitive + the visual cards. */

const { useState, useRef, useEffect, useCallback } = React;

/* ───────────────────── useDraggable ─────────────────────
   Lifts an absolutely-positioned card with mouse + touch.
   Positions are stored as { x, y } in px relative to viewport.
   A short movement threshold means a clean click is NOT swallowed
   by drag — important when the card's children have onClick handlers.
   When `initial` changes (e.g. viewport resize → layout recompute)
   the card snaps back to the freshly-computed default position. */
function useDraggable(initial, onDragChange) {
  const [pos, setPos] = useState(initial);
  const [dragging, setDragging] = useState(false);
  const elRef = useRef(null);
  const s = useRef({
    down: false, startX: 0, startY: 0,
    offsetX: 0, offsetY: 0, pointerId: null,
  });

  // Sync to `initial` whenever the planned position changes.
  // Guarded by !dragging so an in-flight drag isn't snapped away.
  useEffect(() => {
    if (s.current.down) return;
    setPos({ x: initial.x, y: initial.y });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [initial.x, initial.y]);

  const onPointerDown = useCallback((e) => {
    if (!elRef.current) return;
    const rect = elRef.current.getBoundingClientRect();
    s.current = {
      down: true,
      startX: e.clientX, startY: e.clientY,
      offsetX: e.clientX - rect.left, offsetY: e.clientY - rect.top,
      pointerId: e.pointerId,
    };
    // No preventDefault, no pointer-capture yet — wait until the
    // user actually moves past the drag threshold. This lets a real
    // click through to inner handlers (Read button, etc.).
  }, []);

  // Listen on window so events that fly outside the card still work.
  useEffect(() => {
    const onMove = (e) => {
      if (!s.current.down) return;
      const dx = e.clientX - s.current.startX;
      const dy = e.clientY - s.current.startY;
      if (!dragging && Math.hypot(dx, dy) < 5) return;
      if (!dragging) {
        setDragging(true);
        onDragChange?.(true);
        try { elRef.current?.setPointerCapture?.(s.current.pointerId); } catch (_) {}
      }
      const w = window.innerWidth;
      const h = window.innerHeight;
      const cardW = elRef.current?.offsetWidth ?? 0;
      const margin = 32;
      let nx = e.clientX - s.current.offsetX;
      let ny = e.clientY - s.current.offsetY;
      nx = Math.max(-cardW + margin * 2, Math.min(w - margin, nx));
      ny = Math.max(8, Math.min(h - margin, ny));
      setPos({ x: nx, y: ny });
    };
    const onUp = () => {
      const wasDown = s.current.down;
      s.current.down = false;
      if (dragging) {
        setDragging(false);
        onDragChange?.(false);
      }
    };
    window.addEventListener("pointermove", onMove);
    window.addEventListener("pointerup", onUp);
    window.addEventListener("pointercancel", onUp);
    return () => {
      window.removeEventListener("pointermove", onMove);
      window.removeEventListener("pointerup", onUp);
      window.removeEventListener("pointercancel", onUp);
    };
  }, [dragging, onDragChange]);

  return { pos, setPos, dragging, elRef, onPointerDown };
}

/* ───────────────────── Card wrapper ───────────────────── */
function Card({
  initial, rotation = 0, z = 1, children,
  className = "", style = {}, bringToTop, onTopZ,
}) {
  const [topZ, setTopZ] = useState(z);
  // True for ~120ms after drag ends — used by onClickCapture to
  // squelch a stray click that the browser may still synthesize.
  const justDraggedRef = useRef(false);
  const clearTimer = useRef(null);

  const handleDragChange = useCallback((isDragging) => {
    if (isDragging) {
      justDraggedRef.current = true;
      if (clearTimer.current) clearTimeout(clearTimer.current);
      if (bringToTop) {
        const newZ = bringToTop();
        setTopZ(newZ);
      }
    } else {
      // Hold the flag briefly so the synthetic click after pointerup is squelched.
      if (clearTimer.current) clearTimeout(clearTimer.current);
      clearTimer.current = setTimeout(() => { justDraggedRef.current = false; }, 120);
    }
  }, [bringToTop]);

  const { pos, dragging, elRef, onPointerDown } = useDraggable(initial, handleDragChange);

  const lift = dragging ? 1.04 : 1;
  const liftRot = dragging ? rotation * 0.4 : rotation;

  const onClickCapture = useCallback((e) => {
    if (justDraggedRef.current) {
      e.stopPropagation();
      e.preventDefault();
    }
  }, []);

  // Hover any card -> raise it to the top of the stack.
  const onPointerEnter = useCallback(() => {
    if (bringToTop) setTopZ(bringToTop());
  }, [bringToTop]);

  return (
    <div
      ref={elRef}
      className={`card ${dragging ? "dragging" : ""} ${className}`}
      onPointerDown={onPointerDown}
      onPointerEnter={onPointerEnter}
      onClickCapture={onClickCapture}
      style={{
        left: pos.x,
        top: pos.y,
        zIndex: topZ,
        transform: `rotate(${liftRot}deg) scale(${lift})`,
        transition: dragging ? "none" : "transform .25s cubic-bezier(.2,.8,.2,1), box-shadow .2s ease",
        ...style,
        ["--rot"]: `${rotation}deg`,
      }}
    >
      <div className="body">{children}</div>
    </div>
  );
}

/* ───────────────────── Polaroid (author) ───────────────────── */
function Polaroid({ photo, caption, sub, width = 240 }) {
  return (
    <div className="polaroid" style={{ width }}>
      <span className="tape" style={{ top: -10, left: "30%", transform: "rotate(-4deg)" }}></span>
      <span className="tape pink" style={{ top: -8, right: "18%", transform: "rotate(6deg)", width: 60 }}></span>
      <img className="photo" src={photo} alt={caption} draggable="false" />
      <div className="caption">
        {caption}
        <span className="sub">{sub}</span>
      </div>
    </div>
  );
}

/* ───────────────────── Case Card ───────────────────── */
function CaseCard({ n, art, title, meta, tags, tape = "yellow", width = 268, clickable = false, onOpen, readLabel = "Read" }) {
  return (
    <div
      className={`polaroid case-card ${clickable ? "is-clickable" : ""}`}
      style={{ width }}
      onClick={clickable && onOpen ? onOpen : undefined}
      role={clickable ? "button" : undefined}
      tabIndex={clickable ? 0 : undefined}
      onKeyDown={(e) => {
        if (clickable && onOpen && (e.key === "Enter" || e.key === " ")) {
          e.preventDefault();
          onOpen();
        }
      }}>
      <span className={`tape ${tape}`} style={{ top: -10, left: "32%", transform: "rotate(-3deg)" }}></span>
      <div className="photo">
        {art}
        <div className="tags">
          {tags.map((t) => <span key={t} className="tag">{t}</span>)}
        </div>
        {clickable && onOpen && (
          <button
            type="button"
            className="case-read"
            onClick={(e) => { e.stopPropagation(); onOpen(); }}>
            {readLabel}
            <svg viewBox="0 0 24 24" aria-hidden="true">
              <path d="M7 17 L 17 7 M 9 7 L 17 7 L 17 15" stroke="currentColor" strokeWidth="2.4" fill="none" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
          </button>
        )}
      </div>
      <div className="caption">
        {title}
        <span className="sub">{meta}</span>
      </div>
      <div className="pin">{n}</div>
    </div>
  );
}

/* ───────────────────── Contact note ───────────────────── */
function ContactNote({ title, labels, contact }) {
  return (
    <div className="contact-note">
      <span className="tape pink" style={{ top: -8, left: 24, transform: "rotate(-6deg)" }}></span>
      <span className="tape" style={{ top: -10, right: 28, transform: "rotate(4deg)", width: 50 }}></span>
      <h4>{title}</h4>
      <div className="row">
        <span className="label">{labels.mail}</span>
        <a className="val" href={contact.mailHref}>{contact.mail}</a>
      </div>
      <div className="row">
        <span className="label">{labels.tel}</span>
        <a className="val" href={contact.telHref}>{contact.tel}</a>
      </div>
      <div className="row">
        <span className="label">{labels.in}</span>
        <a className="val" href={contact.linkedinHref} target="_blank" rel="noreferrer">{contact.linkedin}</a>
      </div>
    </div>
  );
}

/* ───────────────────── Sticky note (legacy compact form, kept for any caller) ───────────────────── */
function Sticky({ quote, who, color = "yellow" }) {
  return (
    <div className={`sticky ${color}`}>
      “{quote}”
      <div className="who">— {who}</div>
    </div>
  );
}

/* ───────────────────── Testimonial card (large, polaroid-y) ─────────────────────
   Designed to be displayed both in the home stack AND inside the modal.
   `compact` => home stack: shows a 20-word excerpt + "Read" affordance on hover.
   `compact=false` => modal: shows the full quote, wider layout. */
function excerpt(text, n = 20) {
  if (!text) return "";
  const words = text.replace(/\s+/g, " ").trim().split(" ");
  if (words.length <= n) return text;
  return words.slice(0, n).join(" ") + "…";
}

function TestimonialCard({
  testimonial, color = "pink", compact = true, readLabel = "Read",
  onRead, width,
}) {
  const { quote, name, position, company, photo } = testimonial;
  const display = compact ? `"${excerpt(quote, 20)}"` : `"${quote}"`;
  const subtitle = company ? `${position}${company ? ` · ${company}` : ""}` : position;

  return (
    <div
      className={`tcard tcard--${color} ${compact ? "tcard--compact" : "tcard--full"}`}
      style={width ? { width } : undefined}
      onClick={compact && onRead ? onRead : undefined}
      role={compact ? "button" : undefined}
      tabIndex={compact ? 0 : undefined}
      onKeyDown={(e) => {
        if (compact && onRead && (e.key === "Enter" || e.key === " ")) {
          e.preventDefault();
          onRead();
        }
      }}>
      <svg className="tcard__quotemark" viewBox="0 0 24 24" aria-hidden="true">
        <path d="M3 9 C 3 6, 5 4, 8 4 L 9 6 C 7 6, 6 7.5, 6 9 L 9 9 L 9 14 L 3 14 Z M 13 9 C 13 6, 15 4, 18 4 L 19 6 C 17 6, 16 7.5, 16 9 L 19 9 L 19 14 L 13 14 Z" fill="currentColor"/>
      </svg>

      <p className="tcard__quote" style={compact ? undefined : { whiteSpace: "pre-line" }}>
        {display}
      </p>

      <div className="tcard__signature">
        <div className="tcard__sigtext">
          <span className="tcard__name">— {name}</span>
          <span className="tcard__role">{subtitle}</span>
        </div>
        <div className="tcard__avatar-wrap">
          <img className="tcard__avatar" src={photo} alt={name} draggable="false" />
        </div>
      </div>

      {compact && onRead && (
        <button
          type="button"
          className="tcard__read"
          onClick={(e) => { e.stopPropagation(); onRead(); }}
          aria-label={readLabel}>
          {readLabel}
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <path d="M7 17 L 17 7 M 9 7 L 17 7 L 17 15" stroke="currentColor" strokeWidth="2.4" fill="none" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </button>
      )}
    </div>
  );
}

/* ───────────────────── Testimonial stack (home view) ─────────────────────
   3 fanned cards, top one is on top. Hover any card => it animates to the
   top of the stack (mowaotun.com behavior). Click => open modal. */
function TestimonialStack({ testimonials, readLabel, onOpen }) {
  // Track which card is currently "on top" (hovered). null = default order.
  const [topIdx, setTopIdx] = useState(testimonials.length - 1);
  // Pre-computed transform per slot — slot index, not card index.
  const slots = [
    { x: 0,   y: 24,  rot: -8 },
    { x: 36,  y: 12,  rot: 5  },
    { x: 78,  y: 0,   rot: -2 },
  ];

  // Build order: every non-top card keeps its base slot; top card gets the
  // last (front) slot. We swap by moving topIdx to the LAST slot position
  // and shifting the others up.
  const order = testimonials.map((_, i) => i).filter((i) => i !== topIdx);
  order.push(topIdx);
  // slotForCard[cardIdx] = slot index
  const slotForCard = [];
  order.forEach((cardIdx, slotIdx) => { slotForCard[cardIdx] = slotIdx; });

  return (
    <div className="tstack" style={{ width: 360, height: 260 }}>
      {testimonials.map((t, i) => {
        const slotIdx = slotForCard[i];
        const slot = slots[slotIdx];
        const isTop = slotIdx === slots.length - 1;
        return (
          <div
            key={i}
            className={`tstack__slot ${isTop ? "is-top" : ""}`}
            style={{
              transform: `translate(${slot.x}px, ${slot.y}px) rotate(${slot.rot}deg)`,
              zIndex: 10 + slotIdx,
            }}
            onMouseEnter={() => setTopIdx(i)}
            onFocus={() => setTopIdx(i)}>
            <TestimonialCard
              testimonial={t}
              color={t.color}
              compact={true}
              readLabel={readLabel}
              onRead={() => onOpen(i)} />
          </div>
        );
      })}
    </div>
  );
}

/* ───────────────────── Testimonial modal ─────────────────────
   Centered overlay; X close, prev/next arrows, ESC + arrow keys.
   Loops infinitely. */
function TestimonialModal({
  testimonials, openIdx, onClose, onPrev, onNext,
  prevLabel, nextLabel, closeLabel,
}) {
  // Lock scroll while open + keyboard handlers.
  useEffect(() => {
    if (openIdx == null) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      else if (e.key === "ArrowLeft") onPrev();
      else if (e.key === "ArrowRight") onNext();
    };
    window.addEventListener("keydown", onKey);
    return () => {
      document.body.style.overflow = prev;
      window.removeEventListener("keydown", onKey);
    };
  }, [openIdx, onClose, onPrev, onNext]);

  if (openIdx == null) return null;
  const t = testimonials[openIdx];

  return (
    <div className="tmodal" role="dialog" aria-modal="true" onClick={onClose}>
      <div className="tmodal__shell" onClick={(e) => e.stopPropagation()}>
        <button
          type="button"
          className="tmodal__nav tmodal__nav--prev"
          aria-label={prevLabel}
          onClick={onPrev}>
          <svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true">
            <path d="M15 5 L 8 12 L 15 19" stroke="currentColor" strokeWidth="2.4" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </button>

        <div className="tmodal__card-wrap">
          <TestimonialCard
            testimonial={t}
            color={t.color}
            compact={false}
            width="100%" />

          <button
            type="button"
            className="tmodal__close"
            aria-label={closeLabel}
            onClick={onClose}>
            <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
              <path d="M6 6 L 18 18 M 18 6 L 6 18" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round"/>
            </svg>
          </button>

          <div className="tmodal__dots" aria-hidden="true">
            {testimonials.map((_, i) => (
              <span key={i} className={`tmodal__dot ${i === openIdx ? "is-on" : ""}`} />
            ))}
          </div>
        </div>

        <button
          type="button"
          className="tmodal__nav tmodal__nav--next"
          aria-label={nextLabel}
          onClick={onNext}>
          <svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true">
            <path d="M9 5 L 16 12 L 9 19" stroke="currentColor" strokeWidth="2.4" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </button>
      </div>
    </div>
  );
}

/* ───────────────────── Squiggle / decoration ───────────────────── */
function Squiggle() {
  return (
    <svg className="squiggle" viewBox="0 0 120 40" fill="none">
      <path d="M2 24 C 18 4, 30 38, 48 18 S 80 8, 102 22 L 118 16 M 110 10 L 118 16 L 112 24"
        stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
    </svg>
  );
}

/* ───────────────────── "selected work ↑" arrow ───────────────────── */
function SelectedWorkArrow({ label, labelX, labelY, tipX, tipY }) {
  // Geometry
  const LABEL_W = 240;
  const LABEL_H = 30;

  // Arrow starts just under the label center; ends at the tip target.
  const sx = labelX;
  const sy = labelY + LABEL_H + 4;
  const ex = tipX;
  const ey = tipY;

  // SVG bounding box covers both points + padding for stroke & arrowhead.
  const PAD = 28;
  const minX = Math.min(sx, ex) - PAD;
  const minY = Math.min(sy, ey) - PAD;
  const maxX = Math.max(sx, ex) + PAD;
  const maxY = Math.max(sy, ey) + PAD;
  const svgW = maxX - minX;
  const svgH = maxY - minY;

  // Local coords inside the SVG
  const lsx = sx - minX, lsy = sy - minY;
  const lex = ex - minX, ley = ey - minY;

  // Build a graceful arc from the label DOWN-LEFT to the tip. The curve
  // descends gently — its lowest point stays roughly at tip-Y so it does
  // not dip into the giant PRODUCT / DESIGNER centerpiece below.
  const dx = lex - lsx;   // negative — tip is left of start
  const dy = ley - lsy;   // small positive — tip is slightly lower than start
  const cp1x = lsx - 30;             // pull start tangent gently LEFT
  const cp1y = lsy + 32;             // …and DOWN (so the arrow leaves the label heading toward the tip)
  const cp2x = lex + Math.max(80, Math.abs(dx) * 0.35);  // come in from the upper-right of the tip
  const cp2y = ley - 30;             // …above the tip so the arrowhead points DOWN-LEFT

  const path = `M ${lsx} ${lsy} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${lex} ${ley}`;

  // Arrowhead: oriented along the tangent at the end of the cubic
  // (from cp2 → end). Two short ticks rotated ±28° back from the tip.
  const tdx = lex - cp2x;
  const tdy = ley - cp2y;
  const tlen = Math.hypot(tdx, tdy) || 1;
  const tnx = tdx / tlen;
  const tny = tdy / tlen;
  const AH = 16;          // arrowhead length
  const ANG = (28 * Math.PI) / 180;
  const cos = Math.cos(ANG), sin = Math.sin(ANG);
  // Two rotated unit vectors
  const ax1 = lex - AH * (tnx * cos - tny * sin);
  const ay1 = ley - AH * (tnx * sin + tny * cos);
  const ax2 = lex - AH * (tnx * cos + tny * sin);
  const ay2 = ley - AH * (-tnx * sin + tny * cos);
  const headPath = `M ${ax1} ${ay1} L ${lex} ${ley} L ${ax2} ${ay2}`;

  // Unique filter id so multiple instances don't collide
  const fid = "pencil-" + Math.abs((labelX * 31 + tipX) | 0);

  return (
    <React.Fragment>
      {/* The label */}
      <div
        className="selected-work-label"
        style={{
          position: "absolute",
          left: labelX - LABEL_W / 2,
          top: labelY,
          width: LABEL_W,
          textAlign: "center",
          fontFamily: "'Caveat', cursive",
          fontSize: 26,
          fontWeight: 600,
          color: "var(--accent)",
          transform: "rotate(-2deg)",
          zIndex: 6,
          pointerEvents: "none",
          lineHeight: 1.1
        }}
      >
        {label}
      </div>

      {/* The hand-drawn arrow */}
      <svg
        width={svgW}
        height={svgH}
        viewBox={`0 0 ${svgW} ${svgH}`}
        style={{
          position: "absolute",
          left: minX,
          top: minY,
          zIndex: 5,
          pointerEvents: "none",
          overflow: "visible"
        }}
      >
        <defs>
          <filter id={fid} x="-10%" y="-10%" width="120%" height="120%">
            {/* gentle wobble to simulate a marker pen */}
            <feTurbulence type="fractalNoise" baseFrequency="0.022" numOctaves="2" seed="7" result="noise" />
            <feDisplacementMap in="SourceGraphic" in2="noise" scale="3.4" />
          </filter>
        </defs>

        {/* Soft "double-stroke" pencil shadow under the main line */}
        <g filter={`url(#${fid})`} style={{ opacity: 0.35 }}>
          <path d={path} stroke="var(--accent)" strokeWidth="5" strokeLinecap="round" strokeLinejoin="round" fill="none" />
        </g>
        {/* Main stroke */}
        <g filter={`url(#${fid})`}>
          <path d={path} stroke="var(--accent)" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round" fill="none" />
          <path d={headPath} stroke="var(--accent)" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round" fill="none" />
        </g>
      </svg>
    </React.Fragment>
  );
}

/* ───────────────────── Static card (mobile, no drag) ─────────────────────
   Renders the same visual envelope as <Card> but without pointer-drag
   wiring — used in the mobile vertical layout where dragging would
   conflict with page scrolling. */
function StaticCard({ rotation = 0, className = "", children, style = {} }) {
  return (
    <div
      className={`card static ${className}`}
      style={{
        position: "relative",
        transform: `rotate(${rotation}deg)`,
        ["--rot"]: `${rotation}deg`,
        ...style,
      }}>
      <div className="body">{children}</div>
    </div>
  );
}

/* ───────────────────── Section divider (mobile) ─────────────────────
   Small-caps label on the left, hand-drawn horizontal line trailing off
   to the right — used to separate sections on mobile / tablet. */
function SectionDivider({ label }) {
  return (
    <div className="section-divider">
      <span className="section-divider__label">{label}</span>
      <svg className="section-divider__line" viewBox="0 0 400 8" preserveAspectRatio="none" aria-hidden="true">
        <path d="M2 4 C 80 2, 160 6, 240 4 S 380 6, 398 4"
          stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" fill="none" />
      </svg>
    </div>
  );
}

Object.assign(window, {
  Card, Polaroid, CaseCard, ContactNote, Sticky, Squiggle, SelectedWorkArrow, useDraggable,
  StaticCard, SectionDivider,
  TestimonialCard, TestimonialStack, TestimonialModal,
});
