// AccordionGallery — ported from React Bits (reactbits.dev) to plain JSX. Needs GSAP,
// which index.html loads as a UMD script (window.gsap) because this site has no bundler.
//
// Deviations from upstream, all deliberate:
//   * `items` also accept `srcSet`/`sizes`, so the panels use this site's responsive
//     derivatives instead of one fixed URL.
//   * The root's height comes from a `--ag-height` custom property rather than an inline
//     style, so a media query can still override it. Inline heights cannot be overridden.
//   * If GSAP is absent the panels still render and still respond to hover/click; they
//     just snap instead of animating. Upstream would throw.
//   * The dimming of collapsed panels is a `dim` prop rather than a hardcoded 0.35. At
//     upstream's value, against this site's warm cream, the row read as a near-black slab.
const AccordionGallery = ({
  items = [], defaultIndex = 2, accentColor = "#ffffff", overlayColor = "#060010",
  textColor = "#ffffff", height = 460, gap = 10, radius = 16, expandRatio = 0.52,
  orientation = "horizontal", duration = 0.6, ease = "power3.out", parallax = 0.5,
  tilt = 8, stagger = 0.06, trigger = "hover", showLabels = true, grayscale = true,
  dim = 0.35, className = "",
}) => {
  const { useRef, useEffect, useState, useCallback } = React;

  const rootRef = useRef(null);
  const panelRefs = useRef([]);
  const mediaRefs = useRef([]);
  const barRefs = useRef([]);
  const textRefs = useRef([]);
  const tlRef = useRef(null);
  const firstRunRef = useRef(true);
  const mediaSizeRef = useRef(320);

  const vertical = orientation === "vertical";
  // The CSS stacks the panels into a column below 560px. The maths has to follow the layout it
  // is actually in, not the prop: run in row mode against a column, it sized the media from the
  // container's width (leaving empty bands above and below the open photograph) and applied the
  // row's sideways parallax shift, which exposed a gap at the edge of every collapsed panel
  // (owner's report, 2026-09-05). `measure` reads the computed flex-direction into this ref
  // before every layout pass — a ref, not state, so the first pass already runs the right
  // maths instead of laying out as a row and then tweening into the column.
  const columnRef = useRef(false);
  const count = items.length;
  const [active, setActive] = useState(Math.min(Math.max(defaultIndex, 0), count - 1));

  const prefersReduced = typeof window !== "undefined" && window.matchMedia
    ? window.matchMedia("(prefers-reduced-motion: reduce)").matches
    : false;

  const applyLayout = useCallback((animate) => {
    const panels = panelRefs.current;
    const gsap = window.gsap;
    if (!panels.length || !gsap) return;

    const r = Math.min(Math.max(expandRatio, 0.2), 0.9);
    const grow = count > 1 ? (r * (count - 1)) / (1 - r) : 1;
    const mediaSize = mediaSizeRef.current;
    const isVertical = vertical || columnRef.current;

    if (tlRef.current) tlRef.current.kill();
    const dur = animate && !prefersReduced ? duration : 0;
    const tl = gsap.timeline();

    panels.forEach((panel, i) => {
      if (!panel) return;
      const isActive = i === active;
      const media = mediaRefs.current[i];
      const bar = barRefs.current[i];
      const text = textRefs.current[i];

      const rot = isActive ? 0 : i < active ? tilt : -tilt;
      const rotProp = isVertical ? { rotateX: -rot } : { rotateY: rot };

      tl.to(panel, Object.assign({ flexGrow: isActive ? grow : 1, duration: dur, ease }, rotProp), 0);

      if (media) {
        const drift = Math.max(-1.5, Math.min(1.5, active - i));
        const shift = drift * parallax * mediaSize * 0.06;
        tl.to(media, {
          xPercent: -50, yPercent: -50,
          x: isVertical ? 0 : isActive ? 0 : shift,
          y: isVertical ? (isActive ? 0 : shift) : 0,
          "--ag-gray": grayscale ? (isActive ? 0 : 1) : 0,
          "--ag-dim": isActive ? 0 : dim,
          duration: dur, ease,
        }, 0);
      }

      if (showLabels && bar && text) {
        if (isActive) {
          tl.to([bar, text], { opacity: 1, x: 0, duration: dur, ease, stagger: prefersReduced ? 0 : stagger }, 0);
        } else {
          tl.to([bar, text], { opacity: 0, x: -14, duration: dur * 0.6, ease }, 0);
        }
      }
    });

    tlRef.current = tl;
  }, [active, count, expandRatio, duration, ease, vertical, tilt, parallax,
      grayscale, dim, showLabels, stagger, prefersReduced]);

  useEffect(() => {
    const el = rootRef.current;
    if (!el) return;
    const measure = () => {
      const rect = el.getBoundingClientRect();
      const column = !vertical && getComputedStyle(el).flexDirection === "column";
      columnRef.current = column;
      if (column) {
        // Stacked: the open panel is exactly the photographs' 16:9 and shows the whole frame —
        // the media is the panel's own height, with no parallax headroom — and the gallery's
        // height is what that panel, the collapsed strips at their CSS min-height and the gaps
        // add up to. flex-grow then lands the open panel on that height by itself, because the
        // strips clamp at their floor and it takes the rest.
        const first = panelRefs.current[0];
        const strip = (first && parseFloat(getComputedStyle(first).minHeight)) || 46;
        const open = Math.round(rect.width * 9 / 16);
        mediaSizeRef.current = open;
        el.style.setProperty("--ag-media-size", `${open}px`);
        el.style.setProperty("--ag-height", `${open + (count - 1) * strip + gap * (count - 1)}px`);
      } else {
        const total = vertical ? rect.height : rect.width;
        const usable = Math.max(total - gap * (count - 1), 120);
        const size = Math.max(140, usable * Math.min(Math.max(expandRatio, 0.2), 0.9) * 1.22);
        mediaSizeRef.current = size;
        el.style.setProperty("--ag-media-size", `${size}px`);
        el.style.setProperty("--ag-height", vertical ? `${Math.round(height * 1.6)}px` : `${height}px`);
      }
      applyLayout(!firstRunRef.current);
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(el);
    return () => ro.disconnect();
  }, [applyLayout, gap, count, expandRatio, vertical, height]);

  useEffect(() => {
    applyLayout(!firstRunRef.current);
    firstRunRef.current = false;
  }, [applyLayout]);

  useEffect(() => () => { if (tlRef.current) tlRef.current.kill(); }, []);

  const handleEnter = (i) => { if (trigger === "hover") setActive(i); };
  const handleClick = (i, e) => { if (i !== active) { e.preventDefault(); setActive(i); } };
  const handleKeyDown = (i, e) => {
    if (e.key === "ArrowRight" || e.key === "ArrowDown") { e.preventDefault(); setActive((i + 1) % count); }
    else if (e.key === "ArrowLeft" || e.key === "ArrowUp") { e.preventDefault(); setActive((i - 1 + count) % count); }
  };

  const rootStyle = {
    "--ag-accent": accentColor,
    "--ag-overlay": overlayColor,
    "--ag-text": textColor,
    "--ag-gap": `${gap}px`,
    "--ag-radius": `${radius}px`,
    "--ag-height": vertical ? `${Math.round(height * 1.6)}px` : `${height}px`,
  };

  return (
    <div ref={rootRef}
         className={`accordion-gallery${vertical ? " accordion-gallery--vertical" : ""}${className ? ` ${className}` : ""}`}
         style={rootStyle} role="list" aria-label="Image accordion gallery">
      {items.map((item, i) => {
        const isActive = i === active;
        const Tag = item.link ? "a" : "div";
        return (
          <Tag key={i}
               ref={(el) => { panelRefs.current[i] = el; }}
               className={`ag-panel${isActive ? " ag-panel--active" : ""}`}
               style={{ borderRadius: `${radius}px` }}
               href={item.link || undefined}
               onClick={(e) => handleClick(i, e)}
               onMouseEnter={() => handleEnter(i)}
               onFocus={() => setActive(i)}
               onKeyDown={(e) => handleKeyDown(i, e)}
               role="listitem" tabIndex={0}
               aria-current={isActive ? "true" : undefined}
               aria-label={item.label}>
            <span className="ag-panel__frame">
              <span className="ag-panel__media" ref={(el) => { mediaRefs.current[i] = el; }}>
                <img src={item.image} srcSet={item.srcSet} sizes={item.sizes}
                     alt={item.alt || item.label || ""} draggable={false}
                     loading="lazy" decoding="async" />
              </span>
              <span className="ag-panel__overlay" aria-hidden="true" />
            </span>
            {showLabels && (
              <span className="ag-panel__label" aria-hidden="true">
                <span className="ag-panel__bar" ref={(el) => { barRefs.current[i] = el; }} />
                <span className="ag-panel__text" ref={(el) => { textRefs.current[i] = el; }}>
                  {item.label}
                </span>
              </span>
            )}
          </Tag>
        );
      })}
    </div>
  );
};
window.AccordionGallery = AccordionGallery;
