/* ============================================================
 * Shared utilities & primitives
 * ============================================================ */
const { useState, useEffect, useRef, useMemo, useLayoutEffect } = React;

/* Where every "Book a demo" (nav-wide) / "Talk to sales" (pricing page) CTA goes.
 *
 * The Fillout form behind this URL books straight into Manel's calendar and
 * fires the same #ventas-web Slack notification and Pipedrive deal that the
 * Hotelinking web form does. It is declared once here — shared.jsx loads
 * before every other script on every page — so the destination can be changed
 * in one place rather than in the ~28 CTAs scattered across the site.
 */
const DEMO_URL = "https://form.hotelinking.com/t/qhxD5RSbBNus";

/* Popup mode, the way hotelinking.com does it.
 *
 * Fillout's popup embed works by placing a <div data-fillout-embed-type="popup">
 * and letting server.fillout.com/embed/v1/ inject its OWN <button> inside it.
 * hotelinking.com then styles that injected button. We can't copy that directly:
 * our CTAs are React anchors with icon children and their own .btn styling, and
 * there are 30+ of them.
 *
 * So we mount ONE off-screen popup host, let Fillout inject its button there,
 * and forward clicks from any DEMO_URL anchor to it. The visitor keeps our
 * buttons; the modal is Fillout's real popup, opened by a real click on its own
 * trigger — no private API.
 *
 * Off-screen rather than display:none, so Fillout still lays the button out.
 *
 * Deliberately progressive: we only preventDefault once the injected button
 * actually exists. If the embed script is blocked, slow, or fails, the anchor
 * stays a plain link to the hosted form — a demo CTA must never be a dead
 * button. Modified clicks (⌘/ctrl/shift, middle) fall through too, so
 * "open in new tab" keeps working.
 */
const FILLOUT_FORM_ID = "qhxD5RSbBNus";
const FILLOUT_DOMAIN = "form.hotelinking.com";

(function initDemoPopup() {
  if (typeof document === "undefined" || window.__gmDemoPopupInit) return;
  window.__gmDemoPopupInit = true;

  const HOST_ID = "gm-demo-popup-host";

  const mount = () => {
    if (document.getElementById(HOST_ID)) return;
    const host = document.createElement("div");
    host.id = HOST_ID;
    host.setAttribute("data-fillout-id", FILLOUT_FORM_ID);
    host.setAttribute("data-fillout-embed-type", "popup");
    host.setAttribute("data-fillout-dynamic-resize", "");
    host.setAttribute("data-fillout-inherit-parameters", "");
    host.setAttribute("data-fillout-domain", FILLOUT_DOMAIN);
    host.setAttribute("data-fillout-popup-size", "medium");
    host.setAttribute("aria-hidden", "true");
    host.style.cssText =
      "position:absolute;left:-9999px;top:0;width:1px;height:1px;overflow:hidden;";
    document.body.appendChild(host);

    const s = document.createElement("script");
    s.src = "https://server.fillout.com/embed/v1/";
    s.async = true;
    document.body.appendChild(s);
  };

  if (document.body) mount();
  else document.addEventListener("DOMContentLoaded", mount);

  document.addEventListener("click", (e) => {
    if (e.defaultPrevented || e.button !== 0) return;
    if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
    const el = e.target;
    if (!el || typeof el.closest !== "function") return;
    const cta = el.closest('a[href="' + DEMO_URL + '"]');
    if (!cta) return;
    const trigger = document.querySelector("#" + HOST_ID + " button");
    if (!trigger) return; // embed not ready — let the link navigate
    e.preventDefault();
    trigger.click();
  });
})();

/* The GuestMaker mark — the REAL vector, traced from public/logo-icon.svg.
 *
 * What was here before was hand-drawn: a chunky M built from straight line
 * segments with a two-lobed valentine heart bezier'd into the V. It was an
 * approximation someone eyeballed, and it read as fabricated because it was —
 * the real mark's accent is not a heart at all, it is a rotated rounded square
 * with a stroked arc over it, and the M's geometry is quite different.
 *
 * The accent stays #ec71fe (the mark's own fuchsia) rather than var(--accent):
 * --accent is a tweakable site variable and the brand mark is not ours to
 * retint. The M inherits currentColor so `color` still controls it.
 */
const GMMark = ({ size = 28, color }) => (
  <svg
    width={size}
    height={size}
    viewBox="0 0 1200 1200"
    xmlns="http://www.w3.org/2000/svg"
    aria-hidden="true"
    style={{ color: color === "white" ? "#FFFFFF" : "var(--ink)" }}
  >
    <path
      d="M901.98,946.76h-120.79v-273.54l-173.69,168.11h-14.11l-173.69-167.23v272.66h-121.67v-455.68h125.2l177.22,173.4,176.34-173.4h125.2v455.68Z"
      fill="currentColor"
    />
    <g>
      <path
        d="M560.53,236.9h0c45.87,0,83.12,37.24,83.12,83.12v194.64h-166.23v-194.64c0-45.87,37.24-83.12,83.12-83.12Z"
        transform="translate(-101.54 506.42) rotate(-45)"
        fill="#ec71fe"
        opacity=".5"
      />
      <path
        d="M717.51,415.21l20.16-20.09c32.46-32.46,32.46-85.09,0-117.55h0c-32.46-32.46-85.09-32.46-117.55,0l-20.12,20.12"
        fill="#ec71fe"
      />
      <rect
        x="516.93"
        y="332.12"
        width="166.18"
        height="166.23"
        transform="translate(-117.87 545.89) rotate(-45)"
        fill="#ec71fe"
        opacity=".6"
      />
    </g>
  </svg>
);

/* Reveal-on-scroll wrapper */
const Reveal = ({ children, delay = 0, as = "div", className = "", style = {}, ...rest }) => {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const io = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setTimeout(() => setShown(true), delay);
          io.disconnect();
        }
      },
      { threshold: 0.12, rootMargin: "0px 0px -40px 0px" }
    );
    io.observe(ref.current);
    return () => io.disconnect();
  }, [delay]);
  const Tag = as;
  return (
    <Tag
      ref={ref}
      className={`reveal ${shown ? "in" : ""} ${className}`}
      style={style}
      {...rest}
    >
      {children}
    </Tag>
  );
};

/* AI badge used everywhere */
const AIBadge = ({ small }) => (
  <span style={{
    display: "inline-flex",
    alignItems: "center",
    gap: 4,
    padding: small ? "1px 6px" : "2px 8px",
    fontSize: small ? 10 : 11,
    fontWeight: 600,
    letterSpacing: "0.04em",
    fontFamily: "var(--f-sans)",
    background: "linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 75%, #7B2A4D))",
    color: "white",
    borderRadius: 4,
    textTransform: "uppercase",
    boxShadow: "0 0 12px var(--accent-glow)",
  }}>AI</span>
);

/* Channel chip with icon */
const ChannelChip = ({ kind, status = "primary", small }) => {
  const Ic = channelIcon[kind];
  const cls = `chip ${status}`;
  const fontSize = small ? 11 : 12;
  return (
    <span className={cls} style={{ fontSize, gap: 6 }}>
      <span style={{ display: "inline-flex", color: status === "primary" ? channelHue[kind] : "var(--ink-3)" }}>
        <Ic size={small ? 12 : 13} stroke={2} />
      </span>
      {channelLabel[kind]}
    </span>
  );
};

/* Section header pattern */
const SectionHead = ({ eyebrow, title, body, align = "left", maxBody = 640 }) => (
  <div className="flex col gap-5" style={{
    alignItems: align === "center" ? "center" : "flex-start",
    textAlign: align,
  }}>
    {eyebrow && <Reveal><div className="eyebrow">{eyebrow}</div></Reveal>}
    {title && <Reveal delay={80}><h2 className="h-2" style={{ maxWidth: 880 }}>{title}</h2></Reveal>}
    {body && <Reveal delay={160}><p className="lead" style={{ maxWidth: maxBody, margin: 0 }}>{body}</p></Reveal>}
  </div>
);

/* Shrinks a fixed-canvas product mockup to fit any container width, keeping
   its exact desktop composition instead of restacking it into a mobile-native
   column. Restacking a product screenshot yields a different, worse-looking UI
   that no longer reads as "the real product"; scaling keeps it recognisable,
   just smaller — the technique memory's hero diagram has always used.

   The wrapper is sized to the SCALED height because transform does not affect
   layout: without it the element still reserves its full native height and
   leaves a large gap below. */
const ScaleFrame = ({ nativeWidth, children, style }) => {
  const wrapRef = React.useRef(null);
  const innerRef = React.useRef(null);
  const [scale, setScale] = React.useState(1);
  const [nativeHeight, setNativeHeight] = React.useState(0);

  React.useEffect(() => {
    const wrap = wrapRef.current;
    if (!wrap) return;
    const ro = new ResizeObserver((entries) => {
      const w = entries[0].contentRect.width;
      if (w > 0) setScale(Math.min(1, w / nativeWidth));
    });
    ro.observe(wrap);
    return () => ro.disconnect();
  }, [nativeWidth]);

  React.useEffect(() => {
    const inner = innerRef.current;
    if (!inner) return;
    const ro = new ResizeObserver((entries) => {
      const h = entries[0].contentRect.height;
      if (h > 0) setNativeHeight(h);
    });
    ro.observe(inner);
    return () => ro.disconnect();
  }, []);

  return (
    <div ref={wrapRef} style={{ width: "100%", height: nativeHeight ? nativeHeight * scale : undefined, overflow: "hidden", ...style }}>
      <div ref={innerRef} style={{ width: nativeWidth, transform: `scale(${scale})`, transformOrigin: "top left" }}>
        {children}
      </div>
    </div>
  );
};

Object.assign(window, { GMMark, Reveal, AIBadge, ChannelChip, SectionHead, ScaleFrame });
