/* global React */
const { useEffect, useRef, useState } = React;

/* ----------------------------------------------------------------- */
/*  Defensive scroll-lock reset — runs on every page load, including  */
/*  bfcache (back/forward) restores. Prevents pages from getting      */
/*  stuck non-scrollable when an earlier overlay's cleanup didn't run */
/*  (e.g. user navigated away while a panel was open and the browser  */
/*  restored a cached DOM with overflow:hidden still set on html).    */
/* ----------------------------------------------------------------- */
(function ensureScrollIsAlwaysAvailable() {
  // Selectors that uniquely identify an open overlay. As long as ANY of these
  // is in the DOM with aria-hidden="false", we treat the lock as legitimate.
  // Otherwise we clear the lock immediately.
  const OVERLAY_SELECTORS = [
    'aside[aria-label="ASPACE AI creative concierge"][aria-hidden="false"]',
    '#contact-panel[aria-hidden="false"]',
    '[role="dialog"][aria-modal="true"]',
    '[aria-label="Cookie consent"]',
  ];

  function anyOverlayOpen() {
    for (const sel of OVERLAY_SELECTORS) {
      if (document.querySelector(sel)) return true;
    }
    // Site menu / search overlay also lock scroll — detect via inline style
    // (they use aria-hidden too). Generic catch:
    const dialogs = document.querySelectorAll('[aria-modal="true"], [role="dialog"]');
    for (const d of dialogs) {
      if (d.getAttribute('aria-hidden') === 'false') return true;
      const cs = getComputedStyle(d);
      if (cs.opacity !== '0' && cs.visibility !== 'hidden' && cs.pointerEvents !== 'none') return true;
    }
    return false;
  }

  function unlock() {
    document.documentElement.style.overflow = '';
    document.body.style.overflow = '';
    if (window.lenis && typeof window.lenis.start === 'function') {
      try { window.lenis.start(); } catch (_) {}
    }
  }

  function maybeUnlock() {
    const html = document.documentElement.style.overflow;
    const body = document.body.style.overflow;
    if ((html === 'hidden' || body === 'hidden') && !anyOverlayOpen()) {
      unlock();
    } else if (window.lenis?.isStopped && !anyOverlayOpen()) {
      try { window.lenis.start(); } catch (_) {}
    }
  }

  // Run immediately so the page never paints in a stuck state.
  unlock();

  // Hook every event that suggests we're back to interacting with this page.
  window.addEventListener('pageshow', unlock);
  window.addEventListener('focus', unlock);
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'visible') unlock();
  });

  // Watch html/body inline-style mutations. If overflow:hidden gets set without
  // a real open overlay, undo it on the next tick. This catches anything that
  // bypassed React cleanup (bfcache, navigation mid-overlay, browser quirks).
  if (typeof MutationObserver !== 'undefined') {
    const obs = new MutationObserver(() => {
      // Defer to let any legitimate overlay-mount finish first.
      setTimeout(maybeUnlock, 50);
    });
    obs.observe(document.documentElement, { attributes: true, attributeFilter: ['style'] });
    obs.observe(document.body,            { attributes: true, attributeFilter: ['style'] });
  }

  // Safety net: poll once a second. If overflow is locked and no overlay is
  // visibly open, unlock. Cheap (5ms-ish work).
  setInterval(maybeUnlock, 1000);

  // First-load races: rerun a few times during the first 2 seconds in case
  // Lenis or an overlay's init briefly toggled state.
  [200, 600, 1500].forEach(t => setTimeout(maybeUnlock, t));
})();

// CursorGlow — soft electric blue ray that follows the cursor.
function CursorGlow({ enabled = true }) {
  const dotRef = useRef(null);
  const glowRef = useRef(null);
  const target = useRef({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
  const pos = useRef({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
  const glowPos = useRef({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
  const [hover, setHover] = useState(false);

  useEffect(() => {
    if (!enabled) return;
    const onMove = (e) => { target.current.x = e.clientX; target.current.y = e.clientY; };
    const onOver = (e) => {
      const t = e.target;
      if (t.closest && t.closest('a, button, [data-cursor="lg"]')) setHover(true);
      else setHover(false);
    };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseover', onOver);
    let raf;
    const tick = () => {
      pos.current.x += (target.current.x - pos.current.x) * 0.5;
      pos.current.y += (target.current.y - pos.current.y) * 0.5;
      glowPos.current.x += (target.current.x - glowPos.current.x) * 0.12;
      glowPos.current.y += (target.current.y - glowPos.current.y) * 0.12;
      if (dotRef.current) {
        dotRef.current.style.transform = `translate(${pos.current.x}px, ${pos.current.y}px) translate(-50%, -50%)`;
      }
      if (glowRef.current) {
        glowRef.current.style.transform = `translate(${glowPos.current.x}px, ${glowPos.current.y}px) translate(-50%, -50%)`;
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseover', onOver);
    };
  }, [enabled]);

  if (!enabled) return null;
  return (
    <React.Fragment>
      <div ref={glowRef} className="aspace-glow" />
      <div ref={dotRef} className={"aspace-cursor" + (hover ? " lg" : "")} />
    </React.Fragment>
  );
}

// ScrollStoryLine — a single SVG path covering the full document height.
// We draw a sweeping organic curve and reveal it via stroke-dashoffset
// based on scroll progress.
function ScrollStoryLine({ visible = true }) {
  const svgRef = useRef(null);
  const pathRef = useRef(null);
  const [height, setHeight] = useState(typeof document !== 'undefined' ? Math.max(document.documentElement.clientHeight, 4000) : 4000);

  useEffect(() => {
    const update = () => {
      // Measure content excluding the storyline itself to avoid feedback
      const root = document.getElementById('root');
      let contentH = window.innerHeight;
      if (root) {
        let max = 0;
        root.querySelectorAll(':scope > div > *').forEach(el => {
          if (el.classList.contains('aspace-storyline')) return;
          const b = el.getBoundingClientRect().bottom + window.scrollY;
          if (b > max) max = b;
        });
        contentH = Math.max(max, window.innerHeight);
      }
      setHeight(contentH);
    };
    update();
    const t = setTimeout(update, 500);
    const t2 = setTimeout(update, 1500);
    window.addEventListener('resize', update);
    window.addEventListener('load', update);
    return () => { clearTimeout(t); clearTimeout(t2); window.removeEventListener('resize', update); window.removeEventListener('load', update); };
  }, []);

  // First-mount reveal flag.  We only want the cinematic draw-in once;
  // on subsequent height re-measures the line should already be fully
  // drawn (no re-animation flicker).
  const drawnRef = useRef(false);

  useEffect(() => {
    if (!pathRef.current) return;
    const path = pathRef.current;
    const len  = path.getTotalLength();
    const glow = path.parentNode.querySelector('path.glow');

    path.style.strokeDasharray = String(len);
    if (glow) glow.style.strokeDasharray = String(len);

    if (!drawnRef.current) {
      // ── Cinematic reveal on first paint.  Start fully hidden, then
      //    on the next frame transition the dashoffset to 0 with a
      //    long cubic ease so the curve "writes itself" across the
      //    page.  After this, the line stays visible permanently —
      //    no scroll-tie.  This matches the original design intent:
      //    a single continuous gesture present in the composition.
      path.style.strokeDashoffset = String(len);
      if (glow) glow.style.strokeDashoffset = String(len);
      const easing = 'cubic-bezier(0.16, 1, 0.3, 1)';
      requestAnimationFrame(() => {
        path.style.transition = `stroke-dashoffset 2400ms ${easing}`;
        path.style.strokeDashoffset = '0';
        if (glow) {
          glow.style.transition = `stroke-dashoffset 2400ms ${easing}`;
          glow.style.strokeDashoffset = '0';
        }
      });
      drawnRef.current = true;
    } else {
      // Height re-measured (content layout settled).  Keep the line
      // fully drawn without re-animating.
      path.style.strokeDashoffset = '0';
      if (glow) glow.style.strokeDashoffset = '0';
    }
  }, [height]);

  // ──────────────────────────────────────────────────────────────────
  //  STORYLINE — single continuous gesture, Lusion choreography.
  //
  //  The earlier version stitched 7+ zigzag anchors plus a circular
  //  loop. That read as "decorative SVG dropped on top". This version
  //  is a single S-curve composed of THREE wide horizontal arcs that
  //  always live in vertical whitespace columns:
  //
  //     · Hero / manifesto:  enter offscreen-left → sweep right to ~85%
  //     · Studio section:    sweep back left → ~12%
  //     · Booking / contact: sweep right → exit offscreen at ~110%
  //
  //  Hero arc gets the most amplitude (cinematic entry); subsequent
  //  arcs taper. No loops, no twitching, no algorithmic alternation.
  //  Curves are large cubic splines so the eye never feels a corner.
  // ──────────────────────────────────────────────────────────────────
  const w  = (typeof window !== 'undefined' && window.innerWidth)  || 1440;
  const vh = (typeof window !== 'undefined' && window.innerHeight) || 800;

  // Three deliberate "rest stops" — y-positions where the curve crosses
  // the page horizontally. Spread proportionally to the document so the
  // arcs feel distributed regardless of how many sections exist.
  const yEntry =  vh * 0.78;            // enters partway down hero, BELOW headline
  const yA     =  height * 0.25;        // first crest — right side, after hero
  const yB     =  height * 0.55;        // second crest — left side, mid studios
  const yC     =  height * 0.82;        // third crest — right side, near contact
  const yExit  =  height + vh * 0.02;   // off-canvas, gentle fade out at footer

  // X anchors — alternating sides BUT in deep whitespace columns so the
  // line never crosses headlines. (Hero text is left-aligned, so the
  // first crest is on the right; manifesto blocks center, second crest
  // pulls left; contact aligns right again.)
  const xEntry =  -w * 0.06;
  const xA     =   w * 0.86;
  const xB     =   w * 0.12;
  const xC     =   w * 0.84;
  const xExit  =   w * 1.10;

  // Build the path. Each segment uses generous control points placed
  // ~40% along the segment height — that's what makes the arc read as
  // long, lazy, cinematic rather than tight or computational.
  let d = `M ${xEntry} ${yEntry}`;
  // Hero entry → first crest (largest amplitude — the "cinematic" arc)
  d += ` C ${w * 0.18} ${yEntry + (yA - yEntry) * 0.05},`
    + ` ${w * 0.62} ${yA - (yA - yEntry) * 0.45},`
    + ` ${xA} ${yA}`;
  // First → second crest (sweep left through whitespace)
  d += ` C ${w * 0.95} ${yA + (yB - yA) * 0.30},`
    + ` ${w * 0.30} ${yB - (yB - yA) * 0.30},`
    + ` ${xB} ${yB}`;
  // Second → third crest (sweep right)
  d += ` C ${w * -0.05} ${yB + (yC - yB) * 0.32},`
    + ` ${w * 0.55} ${yC - (yC - yB) * 0.32},`
    + ` ${xC} ${yC}`;
  // Third crest → exit (gentle off-canvas fade)
  d += ` C ${w * 0.96} ${yC + (yExit - yC) * 0.45},`
    + ` ${w * 1.05} ${yExit - 60},`
    + ` ${xExit} ${yExit}`;

  if (!visible) return null;
  return (
    <svg ref={svgRef}
      className="aspace-storyline"
      style={{ height: height + 'px', width: '100%', position: 'absolute', top: 0, left: 0, pointerEvents: 'none' }}
      viewBox={`0 0 ${w} ${height}`}
      preserveAspectRatio="none"
      aria-hidden="true">
      <defs>
        {/* Stroke gradient — atmospheric cyan-blue with transparency at
            the entry and exit so the line *enters* and *leaves* the
            composition rather than abruptly starting/ending. The middle
            third holds the most colour. */}
        <linearGradient id="aspace-storyline-grad" gradientUnits="userSpaceOnUse"
          x1="0" y1={yEntry} x2="0" y2={yExit}>
          <stop offset="0%"   stopColor="rgba(0,194,255,0)"/>
          <stop offset="8%"   stopColor="rgba(120,200,255,0.55)"/>
          <stop offset="32%"  stopColor="rgba(0,194,255,0.95)"/>
          <stop offset="62%"  stopColor="rgba(80,170,255,0.85)"/>
          <stop offset="86%"  stopColor="rgba(140,200,255,0.55)"/>
          <stop offset="100%" stopColor="rgba(0,194,255,0)"/>
        </linearGradient>
        {/* Wider gradient for the soft bloom layer — very low alpha */}
        <linearGradient id="aspace-storyline-glow" gradientUnits="userSpaceOnUse"
          x1="0" y1={yEntry} x2="0" y2={yExit}>
          <stop offset="0%"   stopColor="rgba(0,194,255,0)"/>
          <stop offset="20%"  stopColor="rgba(0,194,255,0.18)"/>
          <stop offset="55%"  stopColor="rgba(0,194,255,0.22)"/>
          <stop offset="85%"  stopColor="rgba(0,194,255,0.12)"/>
          <stop offset="100%" stopColor="rgba(0,194,255,0)"/>
        </linearGradient>
      </defs>
      {/* Soft atmospheric bloom — wide blurred halo (drawn first, behind) */}
      <path className="glow" d={d} strokeDasharray="99999" strokeDashoffset="99999"
        stroke="url(#aspace-storyline-glow)"/>
      {/* Crisp ribbon on top — thinner, gradient stroke, masked draw-on by scroll */}
      <path ref={pathRef} d={d} strokeDasharray="99999" strokeDashoffset="99999"
        stroke="url(#aspace-storyline-grad)"/>
    </svg>
  );
}

window.CursorGlow = CursorGlow;
window.ScrollStoryLine = ScrollStoryLine;
