/* global React, ReactDOM */
const { useState: cUseState, useEffect: cUseEffect, useRef: cUseRef } = React;

/* ---------- inline icons ---------- */
function IconSound({ on }) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
      {on ? (
        <>
          <path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path>
          <path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path>
        </>
      ) : (
        <>
          <line x1="22" y1="9" x2="16" y2="15"></line>
          <line x1="16" y1="9" x2="22" y2="15"></line>
        </>
      )}
    </svg>
  );
}
function IconClose() {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <line x1="18" y1="6" x2="6" y2="18"></line>
      <line x1="6" y1="6" x2="18" y2="18"></line>
    </svg>
  );
}

/* ---------- SVG loader (inlines svg so we can tint via fill: currentColor / CSS) ---------- */
const svgCache = {};
function InlineSVG({ src, className }) {
  const [markup, setMarkup] = cUseState(svgCache[src] || null);
  cUseEffect(() => {
    let alive = true;
    if (svgCache[src]) { setMarkup(svgCache[src]); return; }
    fetch(src).then(r => r.text()).then(t => {
      // Strip XML declaration so it inlines cleanly
      const cleaned = t.replace(/<\?xml[^>]*\?>/, '').trim();
      svgCache[src] = cleaned;
      if (alive) setMarkup(cleaned);
    }).catch(() => {});
    return () => { alive = false; };
  }, [src]);
  if (!markup) return <div className={className} />;
  return <div className={className} dangerouslySetInnerHTML={{ __html: markup }} />;
}

/* ---------- Intro gate: its tap is the gesture that lets audio start ---------- */
function Intro({ headline, isMobile, onEnter }) {
  const [leaving, setLeaving] = cUseState(false);
  function enter() {
    if (leaving) return;
    window.AR_AUDIO.unlock();
    setLeaving(true);
    setTimeout(onEnter, 500);
  }
  return (
    <button type="button" className={`intro ${leaving ? 'out' : ''}`} onClick={enter}>
      <span className="intro__title">{headline || 'Angel Rocket'}</span>
      <span className="intro__cta">{isMobile ? 'Tap' : 'Click'} to enter · sound on</span>
    </button>
  );
}

/* ---------- Mobile nudge / clippy ---------- */
function MobileNudge({ messages, onDismiss }) {
  const [idx, setIdx] = cUseState(0);
  const [open, setOpen] = cUseState(false); // start collapsed; user can tap to read
  cUseEffect(() => {
    const id = setInterval(() => setIdx(i => (i + 1) % messages.length), 7500);
    return () => clearInterval(id);
  }, [messages.length]);
  if (!open) {
    return (
      <div className="nudge nudge--mini">
        <button className="nudge__avatar" onClick={() => setOpen(true)} aria-label="Tips">?</button>
      </div>
    );
  }
  return (
    <div className="nudge" role="status">
      <div className="nudge__bubble">
        <button className="nudge__close" onClick={() => setOpen(false)} aria-label="Close tip">×</button>
        {messages[idx]}
      </div>
      <div className="nudge__avatar" aria-hidden="true">?</div>
    </div>
  );
}

/* ---------- Splash 4-panel home ---------- */
function Splash({ onEnter, glow, isMobile, headline, layout, showBrand, showLabels, activeKey }) {
  const lore = window.AR_LORE;
  // panel grid order in the screen (TL, TR, BL, BR) per test.png
  // TL teal = north, TR green = west, BL brown = east, BR yellow = south
  const order = ['north', 'west', 'east', 'south'];

  const [holdKey, setHoldKey] = cUseState(null);
  const holdTimer = cUseRef(null);
  const tappedRef = cUseRef(false);
  const panelRefs = cUseRef({});
  const lastPreviewKey = cUseRef(null);

  function handleEnter(key, ev) {
    const el = panelRefs.current[key];
    if (!el) return;
    const rect = el.getBoundingClientRect();
    const logoEl = el.querySelector('.panel__logo');
    const l = logoEl ? logoEl.getBoundingClientRect() : null;
    const logoBox = l && {
      left: (l.left - rect.left) / rect.width,
      top: (l.top - rect.top) / rect.height,
      width: l.width / rect.width,
      height: l.height / rect.height
    };
    onEnter(key, rect, logoBox);
  }

  // Desktop hover
  function onMouseEnter(key) {
    if (isMobile) return;
    lastPreviewKey.current = key;
    window.AR_AUDIO.preview(key, 500);
  }
  function onMouseLeave(key) {
    if (isMobile) return;
    lastPreviewKey.current = null;
    window.AR_AUDIO.stopPreview(key, 400);
  }

  // Mobile press behaviour: hold = preview audio + glow; quick tap = enter
  function onTouchStart(key) {
    tappedRef.current = false;
    holdTimer.current = setTimeout(() => {
      tappedRef.current = true; // becoming a hold (not a tap)
      setHoldKey(key);
      lastPreviewKey.current = key;
      window.AR_AUDIO.preview(key, 400);
    }, 220);
  }
  function endHold(key) {
    // Always stop any preview that was started on this gesture, regardless of which key
    setHoldKey(null);
    if (lastPreviewKey.current) {
      window.AR_AUDIO.stopPreview(lastPreviewKey.current, 400);
      lastPreviewKey.current = null;
    } else {
      window.AR_AUDIO.stopPreview(key, 400);
    }
  }
  function onTouchEnd(key, ev) {
    clearTimeout(holdTimer.current);
    if (tappedRef.current) {
      // was a hold — release: stop preview
      endHold(key);
    } else {
      // quick tap — navigate
      handleEnter(key, ev);
    }
  }
  function onTouchCancel(key) {
    clearTimeout(holdTimer.current);
    endHold(key);
  }

  // Safety net: stop any active preview if the touch leaves the document entirely
  cUseEffect(() => {
    if (!isMobile) return;
    function globalEnd() {
      if (holdKey) endHold(holdKey);
    }
    window.addEventListener('touchcancel', globalEnd);
    window.addEventListener('contextmenu', globalEnd);
    return () => {
      window.removeEventListener('touchcancel', globalEnd);
      window.removeEventListener('contextmenu', globalEnd);
    };
  }, [holdKey, isMobile]);

  return (
    <div className={`splash splash--${layout || 'centered'} ${showLabels ? '' : 'splash--no-labels'}`} role="navigation" aria-label="Angel Rocket — choose a province">
      {showBrand && <div className="brand">{headline || 'Angel Rocket'}</div>}
      {order.map(key => {
        const data = lore[key];
        const isLeaving = activeKey === key;
        return (
          <div
            key={key}
            ref={el => (panelRefs.current[key] = el)}
            className={`panel ${data.panelClass} ${glow ? 'glow' : ''} ${holdKey === key ? 'is-held' : ''} ${isLeaving ? 'is-leaving' : ''}`}
            onMouseEnter={() => onMouseEnter(key)}
            onMouseLeave={() => onMouseLeave(key)}
            // The intro can close with the pointer already over a panel, so no mouseenter ever fires.
            onMouseMove={() => { if (lastPreviewKey.current !== key) onMouseEnter(key); }}
            onClick={(e) => { if (!isMobile) handleEnter(key, e); }}
            onTouchStart={() => isMobile && onTouchStart(key)}
            onTouchEnd={(e) => isMobile && onTouchEnd(key, e)}
            onTouchCancel={() => isMobile && onTouchCancel(key)}
            tabIndex={0}
            onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleEnter(key, e); }}
            role="button"
            aria-label={`${data.province} — ${data.title}`}
          >
            <InlineSVG src={data.logoSrc} className="panel__logo" />
            {showLabels && <span className="panel__label">{data.province}</span>}
          </div>
        );
      })}
    </div>
  );
}

/* ---------- Expanding clone overlay (FLIP — animates one transform) ---------- */
function Expander({ rect, logoBox, paneClass, logoSrc, onDone }) {
  const [phase, setPhase] = cUseState('start');
  cUseEffect(() => {
    // double-rAF so initial transform paints before we swap
    requestAnimationFrame(() => {
      requestAnimationFrame(() => setPhase('expanded'));
    });
    const t = setTimeout(onDone, 1180);
    return () => { clearTimeout(t); };
  }, []);
  const vw = window.innerWidth;
  const vh = window.innerHeight;
  // Place a viewport-sized box, then transform it down to the panel's rect.
  // When phase === 'expanded' we clear the transform → it animates to fill viewport.
  const sx = rect.width / vw;
  const sy = rect.height / vh;
  const startTransform = `translate(${rect.left}px, ${rect.top}px) scale(${sx}, ${sy})`;
  const style = {
    top: 0,
    left: 0,
    width: '100vw',
    height: '100vh',
    transform: phase === 'expanded' ? 'translate(0,0) scale(1,1)' : startTransform
  };
  return (
    <div
      className={`expander ${paneClass} ${phase === 'expanded' ? 'expanded' : ''}`}
      style={style}
    >
      {logoBox && (
        <div
          className="expander__logo"
          style={{
            left: `${logoBox.left * 100}%`,
            top: `${logoBox.top * 100}%`,
            width: `${logoBox.width * 100}%`,
            height: `${logoBox.height * 100}%`
          }}
        >
          <InlineSVG src={logoSrc} />
        </div>
      )}
    </div>
  );
}

/* ---------- Sidebar player: province tracklist driving a Bandcamp embed ---------- */
function bandcampEmbedSrc(track, theme) {
  return `https://bandcamp.com/EmbeddedPlayer/track=${track.bandcampId}/size=large/bgcol=${theme.bgcol}/linkcol=${theme.linkcol}/tracklist=false/artwork=small/transparent=true/`;
}

function Player({ data, onListen }) {
  const [active, setActive] = cUseState(0);
  const frameRef = cUseRef(null);
  const track = data.tracks[active];

  // Clicks inside a cross-origin iframe never reach this page; focus moving into it
  // (window blur with the iframe active) is the only signal that playback may start.
  cUseEffect(() => {
    function onBlur() {
      if (document.activeElement === frameRef.current) onListen();
    }
    window.addEventListener('blur', onBlur);
    return () => window.removeEventListener('blur', onBlur);
  }, [onListen]);

  return (
    <div className="player">
      <div className="player__head">
        <span>Selected works</span>
        <span>{data.tracks.length} tracks</span>
      </div>
      <h3 className="player__title">{data.title}</h3>
      <div className="tracklist">
        {data.tracks.map((t, i) => (
          <button
            key={t.bandcampId}
            type="button"
            className={`track ${i === active ? 'is-active' : ''}`}
            onClick={() => setActive(i)}
            aria-pressed={i === active}
          >
            <span className="track__idx">{String(i + 1).padStart(2, '0')}</span>
            <span className="track__name">{t.name}</span>
            <span className="track__time">{t.time}</span>
          </button>
        ))}
      </div>
      <iframe
        ref={frameRef}
        key={track.bandcampId}
        className="player__embed"
        title={`${track.name} on Bandcamp`}
        src={bandcampEmbedSrc(track, data.bandcampTheme)}
        seamless
      />
      <a className="player__link" href={track.url} target="_blank" rel="noopener">Listen on Bandcamp ↗</a>
    </div>
  );
}

/* ---------- Section page ---------- */
// Longest run of characters the title can't break inside (splits at spaces and soft hyphens).
function longestRun(title) {
  return Math.max(...title.split(/[\s\u00AD]+/).map(w => w.length + (/\u00AD/.test(title) ? 1 : 0)));
}

function Section({ data, onClose, soundOn, onToggleSound, onListen, homeButtonStyle }) {
  const [enter, setEnter] = cUseState(false);
  cUseEffect(() => {
    const t = setTimeout(() => setEnter(true), 30);
    return () => clearTimeout(t);
  }, []);

  const homeBtn = (() => {
    switch (homeButtonStyle) {
      case 'text':
        return (
          <button className="iconbtn iconbtn--text" onClick={onClose} aria-label="Back to home">
            <span aria-hidden="true">←</span>
            <span>Home</span>
          </button>
        );
      case 'close':
        return (
          <button className="iconbtn" onClick={onClose} aria-label="Back to home">
            <IconClose />
          </button>
        );
      case 'grid':
        return (
          <button className="iconbtn" onClick={onClose} aria-label="Back to home">
            <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><rect x="3" y="3" width="8" height="8"/><rect x="13" y="3" width="8" height="8"/><rect x="3" y="13" width="8" height="8"/><rect x="13" y="13" width="8" height="8"/></svg>
          </button>
        );
      case 'arrow':
      default:
        return (
          <button className="iconbtn" onClick={onClose} aria-label="Back to home">
            <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
          </button>
        );
    }
  })();

  return (
    <section className={`section ${data.sectionClass} ${enter ? 'in' : ''}`}>
      <header className="section__nav">
        {homeBtn}
        <button
          className="iconbtn soundbtn"
          data-on={soundOn ? 'true' : 'false'}
          onClick={onToggleSound}
          aria-label={soundOn ? 'Mute ambient' : 'Unmute ambient'}
          title={soundOn ? 'Sound on' : 'Sound off'}
        >
          <IconSound on={soundOn} />
        </button>
      </header>

      <div className="section__hero">
        <div className="section__heroText">
          <p className="section__province">{data.province} ❍ Angel Rocket</p>
          <h1 className="section__title" style={{ '--chars': longestRun(data.title) }}>{data.title}</h1>
          <p className="section__lede">{data.lede}</p>
        </div>
        <div className="section__heroLogo">
          <InlineSVG src={data.logoSrc} />
        </div>
      </div>

      <div className="section__body">
        <div className="lore">
          {data.chapters.map((c, i) => (
            <article key={i}>
              <h3>{c.title}</h3>
              {c.body.map((p, j) => <p key={j}>{p}</p>)}
            </article>
          ))}
        </div>
        <aside className="sidebar">
          <Player data={data} onListen={onListen} />
        </aside>
      </div>

      <footer className="section__footer">
        <span>Angel Rocket / {data.province}</span>
      </footer>
    </section>
  );
}

window.ARComponents = { Intro, Splash, Expander, Section, MobileNudge };
