// Header + shared bits for both directions.

// Read the CSRF token set by the server as a cookie.
function getCsrfToken() {
  const m = document.cookie.match(/(?:^|;\s*)XSRF-TOKEN=([^;]+)/);
  return m ? decodeURIComponent(m[1]) : '';
}

// ─── Annotation parsing (same as /live page) ───────────────────
function parseAnnotations(photo) {
  if (!photo || !photo.Annotations) return [];
  try {
    var parsed = typeof photo.Annotations === 'string'
      ? JSON.parse(photo.Annotations)
      : photo.Annotations;
    if (Array.isArray(parsed)) return parsed;
    if (parsed && Array.isArray(parsed.annotations)) return parsed.annotations;
    return [];
  } catch (err) {
    return [];
  }
}

function extractBox(annotation, naturalWidth, naturalHeight, displayWidth, displayHeight) {
  var hasBox = annotation && annotation.box && annotation.box.width !== undefined;
  var raw = hasBox ? annotation.box : annotation;
  var left = raw.left !== undefined ? raw.left : raw.x;
  var top  = raw.top  !== undefined ? raw.top  : raw.y;
  var width  = raw.width;
  var height = raw.height;

  if ([left, top, width, height].some(function(v) { return v === undefined || v === null || isNaN(v); })) {
    return null;
  }

  if (annotation.annotationDisplayWidth && annotation.annotationDisplayHeight) {
    var scaleX = displayWidth  / annotation.annotationDisplayWidth;
    var scaleY = displayHeight / annotation.annotationDisplayHeight;
    return { left: left*scaleX, top: top*scaleY, width: width*scaleX, height: height*scaleY };
  }

  var maxVal = Math.max(left, top, width, height);
  var baseLeft = left, baseTop = top, baseWidth = width, baseHeight = height;
  if (maxVal <= 1) {
    baseLeft  = left   * naturalWidth;
    baseTop   = top    * naturalHeight;
    baseWidth = width  * naturalWidth;
    baseHeight= height * naturalHeight;
  } else if (maxVal <= 100) {
    baseLeft  = (left   / 100) * naturalWidth;
    baseTop   = (top    / 100) * naturalHeight;
    baseWidth = (width  / 100) * naturalWidth;
    baseHeight= (height / 100) * naturalHeight;
  }
  var sX = displayWidth  / naturalWidth;
  var sY = displayHeight / naturalHeight;
  return { left: baseLeft*sX, top: baseTop*sY, width: baseWidth*sX, height: baseHeight*sY };
}

// ─── Photo chain hook ──────────────────────────────────────────
// Fetches a linked chain of real photos from the API using the same
// pattern as the /live page. ?preview=true bypasses the odyssey
// rate limiter so the landing page slideshow is exempt.
function usePhotoChain({ seedPerson, autoPathfind } = {}) {
  const chainRef   = React.useRef([]);  // [{photo, inboundName, outboundName}]
  const fetchingRef = React.useRef(false);
  // When autoPathfind is set, block extend() until the path has been seeded.
  const pathfindPendingRef = React.useRef(!!autoPathfind);
  const [chainLen, setChainLen] = React.useState(0);

  // Fetch the guided path on mount and pre-seed the chain.
  React.useEffect(() => {
    if (!autoPathfind) return;
    const { from, to } = autoPathfind;
    fetch(`/api/odyssey/shortest-path/${encodeURIComponent(from)}/${encodeURIComponent(to)}`)
      .then(r => r.ok ? r.json() : null)
      .then(data => {
        if (data && data.steps && data.steps.length > 0) {
          chainRef.current = data.steps.map(s => ({
            photo: s.photo,
            inboundName: s.inboundPerson,
            outboundName: s.outboundPerson,
          }));
          setChainLen(chainRef.current.length);
        }
        pathfindPendingRef.current = false;
      })
      .catch(e => {
        console.error('[CHAIN] pathfind error:', e);
        pathfindPendingRef.current = false;
      });
  }, []);

  const extend = React.useCallback(async () => {
    // Wait until the guided path (if any) has been seeded before extending.
    if (pathfindPendingRef.current) return;
    if (fetchingRef.current) return;
    fetchingRef.current = true;
    const chain = chainRef.current;

    try {
      if (chain.length === 0) {
        // Bootstrap: use seed person's photo if specified, otherwise random
        const bootstrapUrl = seedPerson
          ? `/api/odyssey/random-photo/${encodeURIComponent(seedPerson)}?preview=true`
          : '/api/odyssey/random-photo?preview=true';
        const resp = await fetch(bootstrapUrl);
        if (!resp.ok) return;
        const data = await resp.json();
        const photo = data && data.photo;
        if (!photo) return;

        const people = photo.People || [];
        const connectable = photo.ConnectablePeople || [];
        const pool = connectable.length > 0 ? connectable : people;
        const outboundPerson = pool.length > 0
          ? pool[Math.floor(Math.random() * pool.length)] : null;

        chainRef.current = [{
          photo,
          inboundName: null,
          outboundName: outboundPerson ? outboundPerson.Name : null,
        }];
        setChainLen(1);
      } else {
        // Extend from the last entry
        const last = chain[chain.length - 1];
        const excludeIds = chain.slice(-15).map(e => e.photo.PhotoID);

        const params = new URLSearchParams({ preview: 'true' });
        if (last.outboundName) params.append('expected', last.outboundName);
        params.append('excludePhotos', JSON.stringify(excludeIds));

        const url = `/api/odyssey/linked-photos/${encodeURIComponent(last.photo.PhotoID)}?${params}`;
        const resp = await fetch(url);
        if (!resp.ok) return;
        const data = await resp.json();
        const nextPhoto = data && data.photo;
        if (!nextPhoto) {
          // Dead end: restart from a fresh random photo
          chainRef.current = [];
          setChainLen(0);
          return;
        }

        // Use the actual shared person the server found
        const actualInbound = (nextPhoto.SharedPeople && nextPhoto.SharedPeople.length > 0)
          ? nextPhoto.SharedPeople[0].Name
          : last.outboundName;

        // Pick an outbound connector for the next step
        const people = nextPhoto.People || [];
        const connectable = nextPhoto.ConnectablePeople || [];
        const pool = connectable.length > 0 ? connectable : people;
        const candidates = pool.filter(p => p.Name !== actualInbound);
        const outboundPerson = candidates.length > 0
          ? candidates[Math.floor(Math.random() * candidates.length)]
          : (pool.length > 0 ? pool[0] : null);

        chainRef.current = [...chain, {
          photo: nextPhoto,
          inboundName: actualInbound,
          outboundName: outboundPerson ? outboundPerson.Name : null,
        }];
        setChainLen(chainRef.current.length);
      }
    } catch (e) {
      console.error('[CHAIN] Fetch error:', e);
    } finally {
      fetchingRef.current = false;
    }
  }, []);

  return { chainRef, chainLen, extend };
}

// ─── Modal opener ─────────────────────────────────────────────
// Updates the URL without a page load, then signals the target modal to open.
function openModal(name, url) {
  history.pushState({ notModal: name }, '', url);
  window.dispatchEvent(new CustomEvent('not:modal', { detail: name }));
}

// ─── Header ───────────────────────────────────────────────────
function Header({ showNav = true, variant = 'projection' }) {
  const user = window.CURRENT_USER || null;
  const [menuOpen, setMenuOpen] = React.useState(false);
  const closeMenu = () => setMenuOpen(false);
  return (
    <header className={'not-header not-header--' + variant}>
      <div className="not-header-left">
        <a href="/" style={{ textDecoration: 'none', color: 'inherit' }}>
          <div className="not-wordmark">
            <span className="not-word">NETWORK</span>
            <span className="not-word-of">of</span>
            <span className="not-word">TIME</span>
          </div>
          <div className="not-subword mono">an interactive archive · est. 2024</div>
        </a>
        <img className="not-logo-img" src="/images/NOTlogosquare.png" alt="Network of Time" />
      </div>
      {showNav && (
        <>
          <button
            className={'not-menu-button mono' + (menuOpen ? ' not-menu-button--open' : '')}
            type="button"
            aria-label={menuOpen ? 'Close navigation menu' : 'Open navigation menu'}
            aria-expanded={menuOpen}
            onClick={() => setMenuOpen(open => !open)}
          >
            <span className="not-menu-line" />
            <span className="not-menu-line" />
            <span className="not-menu-line" />
          </button>
          <nav className={'not-nav mono' + (menuOpen ? ' not-nav--open' : '')}>
            <a href="/odyssey" onClick={closeMenu}>Odyssey</a>
            <a href="/about.html" onClick={e => { e.preventDefault(); closeMenu(); openModal('about', '/about.html'); }}>About</a>
            <a href="/submit"     onClick={e => { e.preventDefault(); closeMenu(); openModal('submit', '/submit'); }}>Submit</a>
            <a href="/press"      onClick={e => { e.preventDefault(); closeMenu(); openModal('press', '/press'); }}>Press</a>
            <a href="/contact"    onClick={e => { e.preventDefault(); closeMenu(); openModal('contact', '/contact'); }}>Contact</a>
            <span className="not-nav-divider" />
            {user
              ? <a href="/profile.html" className="not-nav-cta" onClick={e => { e.preventDefault(); closeMenu(); openModal('profile', '/profile.html'); }}>{user.username || user.email || 'Profile'} ↗</a>
              : <a href="/login.html" className="not-nav-cta" onClick={e => { e.preventDefault(); closeMenu(); openModal('login', '/login.html'); }}>Sign in ↗</a>
            }
          </nav>
        </>
      )}
    </header>
  );
}

// ─── Tweaks panel ─────────────────────────────────────────────
function TweaksPanel({ state, setState, visible }) {
  if (!visible) return null;
  const field = (label, key, opts) => (
    <div className="tw-field">
      <div className="tw-label mono">{label}</div>
      <div className="tw-opts">
        {opts.map(o => (
          <button
            key={o.v}
            className={'tw-chip' + (state[key] === o.v ? ' tw-chip--on' : '')}
            onClick={() => setState(s => ({ ...s, [key]: o.v }))}
          >{o.label}</button>
        ))}
      </div>
    </div>
  );
  return (
    <div className="tw-panel">
      <div className="tw-head">
        <span className="tw-title mono">TWEAKS</span>
        <span className="tw-dot" />
      </div>
      {field('DIRECTION', 'direction', [
        { v: 'projection', label: 'A · Projection' },
        { v: 'terminal', label: 'B · Terminal' },
      ])}
      {field('CHAIN SPEED', 'speed', [
        { v: 0.5, label: 'Slow' },
        { v: 1, label: 'Normal' },
        { v: 2, label: 'Fast' },
      ])}
      {field('PHOTO TREATMENT', 'mode', [
        { v: 'original', label: 'Original' },
        { v: 'bw', label: 'Desaturated' },
      ])}
      {field('COUNTER', 'showCounter', [
        { v: true, label: 'On' },
        { v: false, label: 'Off' },
      ])}
      {field('NAV CHROME', 'showNav', [
        { v: true, label: 'Visible' },
        { v: false, label: 'Hidden' },
      ])}
    </div>
  );
}

// ─── Footer bar ───────────────────────────────────────────────
function FooterBar() {
  const year = new Date().getFullYear();
  return (
    <footer className="not-footer">
      <a href="/terms"        className="not-footer-link mono" onClick={e => { e.preventDefault(); openModal('terms', '/terms'); }}>Terms</a>
      <span className="not-footer-sep">·</span>
      <a href="/privacy.html" className="not-footer-link mono" onClick={e => { e.preventDefault(); openModal('privacy', '/privacy.html'); }}>Privacy</a>
      <span className="not-footer-sep">·</span>
      <span className="not-footer-copy mono">© {year} Network of Time</span>
    </footer>
  );
}

Object.assign(window, { getCsrfToken, parseAnnotations, extractBox, usePhotoChain, openModal, Header, TweaksPanel, FooterBar });
