// Isac Lacerda — editorial primitives
// shared components used across sections

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

/* ─── useInView (intersection observer) ───────────────── */
function useInView(opts = { threshold: 0.18 }) {
  const ref = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    // Safety: if element is already in viewport on mount (above-fold), force.
    const rect = el.getBoundingClientRect();
    if (rect.top < window.innerHeight * 0.9 && rect.bottom > 0) {
      setInView(true);
      return;
    }
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setInView(true); io.disconnect(); }
    }, opts);
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, inView];
}

/* ─── EditorialDivider ─────────────────────────────────── */
function EditorialDivider({ num, label }) {
  const [ref, inView] = useInView();
  return (
    <div ref={ref} className="is-divider" style={{ opacity: inView ? 1 : 0, transition: "opacity 1s" }}>
      <span className="num">{num}</span>
      <span className="lbl">/ {label}</span>
      <span className="line" style={{ transform: inView ? "scaleX(1)" : "scaleX(0)", transformOrigin: "left", transition: "transform 1.2s cubic-bezier(.7,0,.2,1) .15s" }} />
    </div>
  );
}

/* ─── ItalicSplitHeadline ──────────────────────────────── */
// Two-line headline: roman on line 1, italic on line 2 (signature move)
function ItalicSplit({ a, b, className = "h-display-2", as = "h2" }) {
  const Tag = as;
  return (
    <Tag className={`is-display ${className} txt-balance`} style={{ margin: 0 }}>
      {a}
      <br />
      <em>{b}</em>
    </Tag>
  );
}

/* ─── BlurReveal text ──────────────────────────────────── */
function BlurReveal({ children, delay = 0, className = "" }) {
  const [ref, inView] = useInView();
  return (
    <div
      ref={ref}
      className={className}
      style={{
        opacity: inView ? 1 : 0,
        filter: inView ? "blur(0)" : "blur(12px)",
        transform: inView ? "translateY(0)" : "translateY(20px)",
        transition: `opacity 1.1s cubic-bezier(.2,.7,.2,1) ${delay}ms, filter 1.1s cubic-bezier(.2,.7,.2,1) ${delay}ms, transform 1.1s cubic-bezier(.2,.7,.2,1) ${delay}ms`,
      }}
    >
      {children}
    </div>
  );
}

/* ─── CurtainReveal ──────────────────────────────────────
   Note: cannot put clip-path: inset(100%) on the observed
   element — fully-clipped elements report isIntersecting=false
   so the IO would never fire. Observe outer wrapper; clip
   on inner element. */
function CurtainReveal({ children, delay = 0, className = "" }) {
  const [ref, inView] = useInView();
  return (
    <div ref={ref} className={className} style={{ overflow: "hidden" }}>
      <div
        style={{
          clipPath: inView ? "inset(0 0 0 0)" : "inset(0 0 100% 0)",
          opacity: inView ? 1 : 0,
          transform: inView ? "translateY(0)" : "translateY(16px)",
          transition: `clip-path 1.2s cubic-bezier(.65,.05,.36,1) ${delay}ms, opacity .9s ease-out ${delay}ms, transform 1.1s cubic-bezier(.2,.7,.2,1) ${delay}ms`,
          willChange: "clip-path, opacity, transform",
        }}
      >
        {children}
      </div>
    </div>
  );
}

/* ─── CountUp metric ───────────────────────────────────── */
function CountUp({ to, sup = "", duration = 1800 }) {
  const [ref, inView] = useInView({ threshold: 0.5 });
  const [val, setVal] = useState(0);
  useEffect(() => {
    if (!inView) return;
    const start = performance.now();
    let raf;
    const tick = (now) => {
      const t = Math.min(1, (now - start) / duration);
      const eased = 1 - Math.pow(1 - t, 3);
      setVal(Math.round(eased * Number(to)));
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [inView, to, duration]);
  return (
    <span ref={ref} className="num">
      {val}{sup && <em>{sup}</em>}
    </span>
  );
}

/* ─── PaperFrame (editorial photo frame) ───────────────── */
function PaperFrame({ label, ratio = "3/4", caption, tone = "warm", src, alt, objectPosition = "center" }) {
  // tone: warm | cool | deep | bronze | paper — only used when no `src`
  const grad = {
    warm:   "linear-gradient(140deg, #C9A876 0%, #7D5C40 60%, #3D2A1E 100%)",
    cool:   "linear-gradient(150deg, #6B5F54 0%, #3A322C 100%)",
    deep:   "linear-gradient(150deg, #5C1A2B 0%, #2A0B14 100%)",
    bronze: "linear-gradient(140deg, #B89070 0%, #6A4A36 50%, #2E1F18 100%)",
    paper:  "linear-gradient(140deg, #EDE6D8 0%, #C9A876 70%, #7D5C40 100%)",
  }[tone] || "linear-gradient(140deg, #C9A876, #3D2A1E)";

  return (
    <figure className="is-photo-frame" style={{ aspectRatio: ratio, margin: 0, position: "relative", overflow: "hidden", background: "#1F1A17" }}>
      {src ? (
        <>
          <img
            src={src}
            alt={alt || caption || ""}
            loading="lazy"
            decoding="async"
            style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", objectPosition }}
          />
          {/* subtle bordeaux multiply tint for editorial consistency */}
          <div style={{ position: "absolute", inset: 0, background: "linear-gradient(180deg, rgba(31,26,23,0) 55%, rgba(31,26,23,.55) 100%)", pointerEvents: "none" }} />
        </>
      ) : (
        <>
          <div style={{ position: "absolute", inset: 0, background: grad }} />
          <svg viewBox="0 0 100 130" preserveAspectRatio="xMidYMid slice" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", opacity: .22, mixBlendMode: "multiply" }} aria-hidden>
            <defs>
              <radialGradient id="ph-g" cx="50%" cy="40%" r="55%">
                <stop offset="0%" stopColor="#1F1A17" stopOpacity=".0"/>
                <stop offset="80%" stopColor="#1F1A17" stopOpacity=".55"/>
              </radialGradient>
            </defs>
            <rect width="100" height="130" fill="url(#ph-g)" />
          </svg>
        </>
      )}
      {/* fine top hairline */}
      <div style={{ position: "absolute", top: 12, left: 14, right: 14, height: 1, background: "rgba(244,239,230,.35)", zIndex: 2 }} />
      <div style={{ position: "absolute", top: 16, left: 14, fontFamily: "JetBrains Mono, monospace", fontSize: 10, letterSpacing: ".2em", textTransform: "uppercase", color: "rgba(244,239,230,.85)", zIndex: 2, textShadow: src ? "0 1px 2px rgba(0,0,0,.4)" : "none" }}>
        {label}
      </div>
      {caption && (
        <figcaption className="caption" style={{ position: "relative", zIndex: 2 }}>{caption}</figcaption>
      )}
    </figure>
  );
}

/* ─── PullQuote ────────────────────────────────────────── */
function PullQuote({ children, cite }) {
  return (
    <blockquote className="is-pullquote">
      "{children}"
      {cite && <cite>— {cite}</cite>}
    </blockquote>
  );
}

/* ─── FashionTicker ────────────────────────────────────── */
function FashionTicker({ items }) {
  const set = (
    <span>
      {items.map((it, i) => (
        <React.Fragment key={i}>
          <em style={{ fontFamily: "Fraunces, serif", fontStyle: "italic", fontWeight: 360, textTransform: "none", letterSpacing: "-.01em", fontSize: "1.18em" }}>{it}</em>
          <span className="dot" />
        </React.Fragment>
      ))}
    </span>
  );
  return (
    <div className="is-ticker">
      <div className="is-ticker-track">
        {set}
        {set}
        {set}
      </div>
    </div>
  );
}

/* ─── BordeauxAurora ───────────────────────────────────── */
function Aurora() {
  return <div className="is-aurora" aria-hidden />;
}

/* expose ALL to window */
Object.assign(window, {
  useInView,
  EditorialDivider,
  ItalicSplit,
  BlurReveal,
  CurtainReveal,
  CountUp,
  PaperFrame,
  PullQuote,
  FashionTicker,
  Aurora,
});
