// ui.jsx — shared building blocks
const { useRef, useEffect, useState } = React;

// ── Animation viability probe ───────────────────────────────────────────────
// Offscreen/throttled iframes pause CSS transitions+animations, which would
// leave reveal-on-scroll content stuck hidden. We measure rAF cadence once; if
// frames are slow (not being rendered) or reduced-motion is set, we mark
// animations non-viable and Reveal renders content statically visible.
let ANIM_VIABLE = null;            // null = unknown, true/false once probed
const ANIM_WAITERS = [];
function finalizeAnim(v) {
  ANIM_VIABLE = v;
  document.documentElement.classList.toggle("anim-off", !v);
  ANIM_WAITERS.splice(0).forEach((fn) => fn());
}
(function probeAnim() {
  if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
    finalizeAnim(false); return;
  }
  const t0 = performance.now();
  requestAnimationFrame(() => requestAnimationFrame(() => {
    finalizeAnim((performance.now() - t0) < 200);   // ~2 frames should be < ~60ms when rendered
  }));
  // hard fallback in case rAF never fires
  setTimeout(() => { if (ANIM_VIABLE === null) finalizeAnim(false); }, 1200);
})();
function onAnimDecided(fn) { if (ANIM_VIABLE !== null) fn(); else ANIM_WAITERS.push(fn); }

// Safety net: no matter what (paused/throttled/offscreen contexts that freeze
// CSS animations at their hidden keyframe), force every animated element to its
// visible end-state shortly after load. Timers fire even when rAF is throttled,
// so this guarantees content is never stuck invisible. Live browsers complete
// their fades well before this and see no change.
setTimeout(() => { document.documentElement.classList.add("force-shown"); }, 1100);

// Scroll-reveal wrapper. Falls back to statically-visible when animations are
// not viable (throttled/offscreen/reduced-motion).
function Reveal({ children, as = "div", delay = 0, className = "", style = {} }) {
  const ref = useRef(null);
  // mode: 'static' (visible, no transition) | 'pre' (hidden, armed) | 'in' (revealed)
  const [mode, setMode] = useState("static");
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let cleanup = () => {};
    onAnimDecided(() => {
      if (!ref.current) return;
      if (!ANIM_VIABLE) { setMode("static"); return; }
      const vh = window.innerHeight || document.documentElement.clientHeight;
      const inView = () => {
        const r = el.getBoundingClientRect();
        return r.top < vh * 0.85 && r.bottom > 0;
      };
      if (inView()) {
        // gentle entrance for above-fold content
        setMode("pre");
        requestAnimationFrame(() => requestAnimationFrame(() => ref.current && setMode("in")));
        return;
      }
      // below the fold: arm hidden, reveal on scroll
      setMode("pre");
      let done = false, raf = 0;
      const check = () => {
        if (done) return;
        if (inView()) { done = true; setMode("in"); detach(); }
      };
      const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(check); };
      const detach = () => {
        window.removeEventListener("scroll", onScroll);
        window.removeEventListener("resize", onScroll);
        cancelAnimationFrame(raf);
      };
      window.addEventListener("scroll", onScroll, { passive: true });
      window.addEventListener("resize", onScroll);
      cleanup = detach;
    });
    return () => cleanup();
  }, []);

  const dur = "0.85s";
  const tx = `opacity ${dur} ease ${delay}ms, transform ${dur} cubic-bezier(.2,.7,.2,1) ${delay}ms`;
  const modeStyle =
    mode === "pre" ? { opacity: 0, transform: "translateY(22px)", transition: tx } :
    mode === "in"  ? { opacity: 1, transform: "none", transition: tx } :
                     { opacity: 1, transform: "none" }; // static
  const Tag = as;
  return (
    <Tag ref={ref}
      className={`reveal ${mode === "in" || mode === "static" ? "is-in" : ""} ${className}`}
      style={{ ...modeStyle, ...style }}>
      {children}
    </Tag>
  );
}

function Eyebrow({ children, align = "left", style = {} }) {
  return <div className="eyebrow" style={{ textAlign: align, ...style }}>
    <span className="eyebrow-tick" />{children}
  </div>;
}

function GoldButton({ children, onClick, full = false, size = "md", type = "solid", disabled = false }) {
  return (
    <button className={`btn btn-gold ${type === "outline" ? "btn-outline" : ""} btn-${size}`}
      style={full ? { width: "100%", ...(disabled ? { opacity: .6, cursor: "default" } : null) } : (disabled ? { opacity: .6, cursor: "default" } : null)} onClick={onClick} disabled={disabled}>
      <span>{children}</span>
    </button>
  );
}

function OutlineButton({ children, onClick, full = false, size = "md" }) {
  return (
    <button className={`btn btn-ghost btn-${size}`}
      style={full ? { width: "100%" } : null} onClick={onClick}>
      <span>{children}</span>
    </button>
  );
}

// Circular brand medallion (the processed badge) with a thin gold ring.
function Medallion({ size = 92 }) {
  return (
    <span className="medallion" style={{ width: size, height: size }}>
      <img src="assets/crest-badge.png" alt="Chairman's Reserve" draggable="false" />
    </span>
  );
}

// Gold serif wordmark for the nav / footer.
function Wordmark({ small = false, onClick }) {
  return (
    <button className={`wordmark ${small ? "wordmark-sm" : ""}`} onClick={onClick}>
      <span className="wm-main">Chairman’s Reserve</span>
      <span className="wm-sub">Luxury Experiences</span>
    </button>
  );
}

// Elegant dark placeholder panel standing in for vehicle photography.
function PhotoPlaceholder({ label = "", ratio = "16 / 10", tone = "emerald", round = 0, children }) {
  return (
    <div className={`photo-ph tone-${tone}`} style={{ aspectRatio: ratio, borderRadius: round }}>
      <div className="photo-ph-sheen" />
      <div className="photo-ph-grain" />
      <div className="photo-ph-mark">
        <svg viewBox="0 0 48 48" width="32" height="32" aria-hidden="true">
          <circle cx="24" cy="24" r="14" fill="none" stroke="currentColor" strokeWidth="1.1" />
          <circle cx="24" cy="24" r="5.4" fill="none" stroke="currentColor" strokeWidth="1.1" />
          <path d="M24 10v4M24 34v4M10 24h4M34 24h4" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" />
        </svg>
        {label ? <span className="photo-ph-label">{label}</span> : null}
        <span className="photo-ph-sub">Photography to come</span>
      </div>
      {children}
    </div>
  );
}

// Refined line icons for the value pillars.
function PillarIcon({ name }) {
  const common = { width: 38, height: 38, viewBox: "0 0 40 40", fill: "none",
    stroke: "currentColor", strokeWidth: 1.25, strokeLinecap: "round", strokeLinejoin: "round" };
  const paths = {
    curated: <g><path d="M20 5l5 7-5 23-5-23 5-7z" /><path d="M11 12h18" /><path d="M15 12l5-7 5 7" /></g>,
    glove:   <g><path d="M9 23a11 11 0 0 0 22 0" /><path d="M20 23V9a3 3 0 0 1 6 0v6" /><path d="M14 23v-4a3 3 0 0 1 6 0" /><path d="M9 27h22" /></g>,
    reservation: <g><circle cx="14" cy="16" r="6" /><path d="M18.5 19.5L31 32" /><path d="M27 28l3-3M24 25l2.5-2.5" /></g>,
    wrench:  <g><path d="M27 12a6 6 0 0 1-8 7l-9 9 3 3 9-9a6 6 0 0 0 7-8l-3.5 3.5-3-3L27 12z" /></g>,
    income:  <g><circle cx="20" cy="20" r="13" /><path d="M20 12v16" /><path d="M24 16.5c-.8-1.6-2.3-2.5-4-2.5-2.2 0-4 1.3-4 3.2 0 4.3 8 2.3 8 6.4 0 1.9-1.8 3.4-4 3.4-1.9 0-3.5-1-4.2-2.6" /></g>,
    shield:  <g><path d="M20 5l11 4v8c0 7-4.7 12.4-11 14-6.3-1.6-11-7-11-14V9l11-4z" /><path d="M15 19.5l3.5 3.5 7-7.5" /></g>,
    vetted:  <g><circle cx="16" cy="15" r="5" /><path d="M7 31c0-5 4-8 9-8 2 0 3.8.5 5.2 1.4" /><path d="M23 27l3 3 6-6.5" /></g>,
    calendar:<g><rect x="7" y="9" width="26" height="24" rx="3" /><path d="M7 15h26M14 5v6M26 5v6" /><path d="M13 22h4M19 22h4M13 27h4" /></g>,
    split:   <g><path d="M20 6v9" /><circle cx="20" cy="6" r="2.5" /><path d="M20 15c0 5-9 4-9 11" /><path d="M20 15c0 5 9 4 9 11" /><circle cx="11" cy="29" r="2.5" /><circle cx="29" cy="29" r="2.5" /></g>,
  };
  return <svg {...common}>{paths[name]}</svg>;
}

// ============ EMAIL FORM PLUMBING (FormSubmit) ============
// Posts the real <form> (with file inputs) into a hidden iframe so attachments
// are delivered and the page never navigates away. onLoad of the sink = success.
function useEmailForm() {
  const [sending, setSending] = React.useState(false);
  const [sent, setSent] = React.useState(false);
  const submittedRef = React.useRef(false);
  const sinkName = React.useRef("crsink_" + Math.random().toString(36).slice(2)).current;
  const begin = () => { submittedRef.current = true; setSending(true); };
  const onSinkLoad = () => {
    if (submittedRef.current) { submittedRef.current = false; setSending(false); setSent(true); }
  };
  const reset = () => { setSent(false); setSending(false); submittedRef.current = false; };
  return { sending, sent, sinkName, begin, onSinkLoad, reset };
}

// Hidden config inputs FormSubmit reads (subject line, table email, no captcha, honeypot).
function FormSubmitMeta({ subject }) {
  return (
    <React.Fragment>
      <input type="hidden" name="_subject" value={subject} />
      <input type="hidden" name="_template" value="table" />
      <input type="hidden" name="_captcha" value="false" />
      <input type="text" name="_honey" tabIndex={-1} autoComplete="off" style={{ position: "absolute", left: "-9999px", width: 1, height: 1, opacity: 0 }} aria-hidden="true" />
    </React.Fragment>
  );
}

Object.assign(window, { Reveal, Eyebrow, GoldButton, OutlineButton, Medallion, Wordmark, PhotoPlaceholder, PillarIcon, useEmailForm, FormSubmitMeta });
