// Top navigation — Home / History / About + BARK.
// /sits/[slug] is reached from the feed, never from the nav.
// The bark button is the one bit of levity.

const { useState: useNavState, useRef: useNavRef, useEffect: useNavEffect, useMemo: useNavMemo } = React;

// Magnetic hover — the cursor is "caught" by the CTA within a small
// capture radius and tugs the button a few pixels toward it. Serves the
// site's single conversion: reward approach-intent on the Book-a-sit
// pill without shouting. Skips on touch and prefers-reduced-motion.
function useMagneticHover(ref, { radius = 120, pull = 0.18, max = 6 } = {}) {
  useNavEffect(() => {
    const el = ref.current;
    if (!el) return;
    const mqReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)');
    const mqFine = window.matchMedia && window.matchMedia('(hover: hover) and (pointer: fine)');
    if ((mqReduced && mqReduced.matches) || (mqFine && !mqFine.matches)) return;

    let raf = 0, targetX = 0, targetY = 0, curX = 0, curY = 0;
    const step = () => {
      curX += (targetX - curX) * 0.22;
      curY += (targetY - curY) * 0.22;
      el.style.setProperty('--mag-x', curX.toFixed(2) + 'px');
      el.style.setProperty('--mag-y', curY.toFixed(2) + 'px');
      if (Math.abs(targetX - curX) > 0.05 || Math.abs(targetY - curY) > 0.05) {
        raf = requestAnimationFrame(step);
      } else {
        raf = 0;
      }
    };
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      const cx = r.left + r.width / 2;
      const cy = r.top + r.height / 2;
      const dx = e.clientX - cx;
      const dy = e.clientY - cy;
      const dist = Math.hypot(dx, dy);
      if (dist < radius && dist > 0) {
        const k = Math.min(max, dist * pull) / dist;
        targetX = dx * k;
        targetY = dy * k;
      } else {
        targetX = 0; targetY = 0;
      }
      if (!raf) raf = requestAnimationFrame(step);
    };
    window.addEventListener('mousemove', onMove, { passive: true });
    return () => {
      window.removeEventListener('mousemove', onMove);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [ref]);
}

const Nav = ({ current, onNav }) => {
  // Wordmark is the home button (clicking "Dylan Agema" routes to feed).
  // History folds into Home as a scroll section — no separate nav entry.
  // Travels remains a direct-link route for deep links and the archive
  // "classic view" anchor in the footer.
  const links = [
    ['info',    'About me'],
    ['labs',    'Labs'],
  ];

  const mailto = 'mailto:dylan.agema@gmail.com'
    + '?subject=' + encodeURIComponent('Housesit request')
    + '&body=' + encodeURIComponent(
      'Hi Dylan —\n\n'
      + 'I\'d like to check your availability for a sit.\n\n'
      + 'Dates:\n'
      + 'Location:\n'
      + 'Pet(s):\n'
      + 'A bit about your home:\n\n'
      + 'Thanks,\n'
    );

  // Current location — derived from the static SITS file (completed
  // history). Doesn't depend on the network; renders immediately.
  const baseStatus = useNavMemo(() => {
    const sits = window.SITS || [];
    const today = new Date().toISOString().slice(0, 10);
    const completed = sits
      .filter(s => s.status !== 'available')
      .slice()
      .sort((a, b) => b.end_date.localeCompare(a.end_date));
    const inRange = completed.find(s => s.start_date <= today && s.end_date >= today);
    const location = (inRange || completed[0])?.location_short || 'Boulder, Colorado';
    return { location };
  }, []);

  // Open window — fetched from the `open-sits` edge function, which
  // reads the private `prospective-sits` Supabase table server-side with
  // the service-role key and returns only {start, end} of the next open
  // gap within a 3-month horizon. The SUPABASE_URL is public (just a
  // project URL); no API keys ship to the browser. CORS + rate-limit
  // are enforced by the function. On network failure we fall back to
  // a safe default — "available on request" — so the UI never blocks
  // on the fetch.
  const [openLabel, setOpenLabel] = useNavState('available on request');
  useNavEffect(() => {
    const base = window.SUPABASE_URL;
    if (!base) return;
    const ac = new AbortController();
    const timer = setTimeout(() => ac.abort(), 4500);
    (async () => {
      try {
        const r = await fetch(`${base}/functions/v1/open-sits`, { signal: ac.signal });
        if (!r.ok) return;
        const j = await r.json().catch(() => null);
        if (!j?.open) return;
        const d1 = new Date(j.open.start + 'T00:00:00Z');
        const d2 = new Date(j.open.end + 'T00:00:00Z');
        const m = (d) => d.toLocaleString('en', { month: 'short', timeZone: 'UTC' }).toLowerCase();
        const sameMonth = d1.getUTCMonth() === d2.getUTCMonth() && d1.getUTCFullYear() === d2.getUTCFullYear();
        setOpenLabel(sameMonth
          ? `open ${m(d1)} ${d1.getUTCDate()} – ${d2.getUTCDate()}`
          : `open ${m(d1)} ${d1.getUTCDate()} – ${m(d2)} ${d2.getUTCDate()}`);
      } catch { /* network error / abort — leave fallback */ }
      finally { clearTimeout(timer); }
    })();
    return () => { ac.abort(); clearTimeout(timer); };
  }, []);

  const status = { location: baseStatus.location, openLabel };

  return (
    <>
      {/* Corner-pinned chrome — aristide layout. Four fixed-position
          elements stay in view as the page scrolls underneath, so the
          user can always see who this is, where they are, and how to
          reach out. No sticky navbar band — each corner is its own
          light-weight anchor. */}
      {/* Top-center live counter — only on the home feed. Sums every
          completed sit's duration (inclusive of both endpoints) plus
          the in-progress portion of any sit whose range contains today.
          Recomputed hourly so the number ticks up over multi-day sits. */}
      {current === 'feed' && <DaysSatCounter />}

      <nav className="site-nav site-nav--corners" aria-label="primary">
        {/* intro-fade is applied to each corner (boxed, position:fixed)
            instead of the parent nav (which has display:contents and
            therefore can't carry opacity). Each corner fades in
            independently when the cascade phase starts. */}
        {/* Top-left: wordmark / home */}
        <div className="site-nav__corner site-nav__corner--tl intro-fade">
          <button className="wm" onClick={() => onNav('feed')}>
            Dylan Agema
          </button>
        </div>

        {/* Top-right: About / Labs as browse links + Book-a-sit
            promoted to a button-styled CTA in the same eye-track. */}
        <div className="site-nav__corner site-nav__corner--tr intro-fade">
          {links.map(([k, label]) => {
            const isActive = current === k;
            return (
              <a
                key={k}
                href={`#${k}`}
                className={isActive ? 'active' : ''}
                aria-current={isActive ? 'page' : undefined}
                onClick={(e) => { e.preventDefault(); onNav(k); }}
              >
                {label}
              </a>
            );
          })}
          <a className="site-nav__book-cta mono-caps" href={mailto}>Book a sit</a>
        </div>

        {/* Bottom-left: status — derived from live data. Location + next
            open window. */}
        <div className="site-nav__corner site-nav__corner--bl mono-caps intro-fade">
          <span>{status.location}</span>
          <span>{status.openLabel}</span>
        </div>

        {/* Bottom-right: outbound links — TrustedHousesitters (sitting
            profile) + LinkedIn (professional profile for the ML/engineering
            side). Book-a-sit moved to TR (email-based booking sits in the
            same group as the About/Labs nav now). Instagram + Email dropped
            — fewer affordances, more signal. */}
        <div className="site-nav__corner site-nav__corner--br intro-fade">
          <div className="site-nav__contact mono-caps">
            <a href="https://www.trustedhousesitters.com/house-and-pet-sitters/united-states/colorado/boulder/l/2277339/" target="_blank" rel="noopener noreferrer">TrustedHousesitters ↗</a>
            <a href="https://www.linkedin.com/in/dylan-agema/" target="_blank" rel="noopener noreferrer">LinkedIn ↗</a>
          </div>
          <BarkButton />
        </div>
      </nav>
    </>
  );
};

// BarkButton — spammable. Each click plays a real dog sound sample
// and spawns a drifting WOOF/BARK/GROWL word. Samples are short MP3s
// from Mixkit (free license, no attribution required). Preloaded as
// Audio elements so playback is instant on click.
let _barkId = 0;
const WORDS_YIP = ['YIP', 'YAP', 'YIP!', 'YAP!', 'RUFF'];
const WORDS_BARK = ['WOOF', 'WOOF!', 'BARK', 'BORK', 'WOOF', 'BARK!', 'WOOFS'];
const WORDS_HOWL = ['AROOOO', 'BAROOO', 'AWOOOO'];
const WORDS_GROWL = ['GRRR', 'GRRRR!', 'RRRR'];
const WORDS_WHIMPER = ['WHINE', 'WHIMPER', '...'];

const DOG_SOUNDS = [
  { src: 'sounds/bark-small.mp3',  mode: 'bark',    weight: 3 },
  { src: 'sounds/bark-medium.mp3', mode: 'bark',    weight: 3 },
  { src: 'sounds/bark-big.mp3',    mode: 'howl',    weight: 2 },
  { src: 'sounds/growl.mp3',       mode: 'growl',   weight: 1 },
  { src: 'sounds/whimper.mp3',     mode: 'whimper', weight: 1 },
  { src: 'sounds/hellhound.mp3',   mode: 'howl',    weight: 1 },
];

const BarkButton = () => {
  const [pops, setPops] = useNavState([]);
  const btnRef = useNavRef(null);
  const samplesRef = useNavRef(null);

  const lastAudioRef = useNavRef(null);

  useNavEffect(() => {
    const pool = DOG_SOUNDS.map(s => {
      const a = new Audio(s.src);
      a.preload = 'auto';
      a.volume = 0.7;
      return { ...s, el: a };
    });
    samplesRef.current = pool;
  }, []);

  const playBark = () => {
    const pool = samplesRef.current;
    if (!pool || !pool.length) return [{ mode: 'bark', dur: 0.5 }];
    if (lastAudioRef.current) {
      lastAudioRef.current.pause();
      lastAudioRef.current.currentTime = 0;
    }
    const totalWeight = pool.reduce((s, p) => s + p.weight, 0);
    let r = Math.random() * totalWeight;
    let chosen = pool[0];
    for (const p of pool) {
      r -= p.weight;
      if (r <= 0) { chosen = p; break; }
    }
    const audio = chosen.el;
    audio.currentTime = 0;
    audio.volume = 0.5 + Math.random() * 0.3;
    audio.playbackRate = 0.9 + Math.random() * 0.2;
    audio.play().catch(() => {});
    lastAudioRef.current = audio;
    return [{ mode: chosen.mode, dur: 1.0 }];
  };

  const pickWord = (mode) => {
    const set = mode === 'yip' ? WORDS_YIP
             : mode === 'howl' ? WORDS_HOWL
             : mode === 'growl' ? WORDS_GROWL
             : mode === 'whimper' ? WORDS_WHIMPER
             : WORDS_BARK;
    return set[Math.floor(Math.random() * set.length)];
  };

  // Rapid-click escalation — each click within 700ms of the last
  // compounds. Resets when the gap exceeds the window.
  const lastClickRef = useNavRef(0);
  const streakRef = useNavRef(0);

  const onBark = (e) => {
    e.preventDefault();

    const now = performance.now();
    if (now - lastClickRef.current < 700) streakRef.current += 1;
    else streakRef.current = 0;
    lastClickRef.current = now;
    const streak = streakRef.current;

    const barks = playBark();

    // Button shake.
    if (btnRef.current) {
      btnRef.current.classList.remove('is-barking');
      void btnRef.current.offsetWidth;
      btnRef.current.classList.add('is-barking');
    }

    const rect = btnRef.current?.getBoundingClientRect();
    const originX = rect ? rect.left + rect.width / 2 : window.innerWidth - 80;
    const originY = rect ? rect.top + rect.height / 2 : 32;

    // One pop per bark in the burst, staggered vertically so they don't
    // all stack on top of each other.
    const newPops = barks.map((b, i) => {
      const id = ++_barkId;
      const word = pickWord(b.mode);
      // Howls get bigger, yips smaller — visual scale tracks audio.
      const base = b.mode === 'howl' ? 56 : b.mode === 'yip' ? 22 : 32;
      const size = base + Math.random() * 30 + streak * 4;
      const rot = (Math.random() - 0.5) * (14 + streak * 2);
      const drift = (Math.random() - 0.5) * (180 + streak * 40);
      return {
        id, word,
        x: originX + (Math.random() - 0.5) * 40,
        y: originY + i * 12,
        size, rot, drift,
      };
    });
    setPops((p) => [...p, ...newPops]);
  };

  const removePop = (id) => setPops((p) => p.filter((x) => x.id !== id));

  return (
    <>
      <button
        ref={btnRef}
        className="bark-btn"
        onClick={onBark}
        aria-label="Bark"
      >
        BARK
      </button>
      <BarkStage pops={pops} onDone={removePop} />
    </>
  );
};

// Renders into document.body via a portal so the stage can cover the
// whole viewport regardless of where Nav sits in the tree.
const BarkStage = ({ pops, onDone }) => {
  return ReactDOM.createPortal(
    <div className="bark-stage" aria-hidden="true">
      {pops.map((p) => (
        <span
          key={p.id}
          className="bark-pop"
          style={{
            left: p.x + 'px',
            top: p.y + 'px',
            fontSize: p.size + 'px',
            '--rot': p.rot + 'deg',
            '--dx-start': (p.drift * 0.2) + 'px',
            '--dx-mid':   (p.drift * 0.6) + 'px',
            '--dx-end':   (p.drift * 1.0) + 'px',
            transform: 'translate(-50%, -50%)', // initial centering
          }}
          onAnimationEnd={() => onDone(p.id)}
        >
          {p.word}
        </span>
      ))}
    </div>,
    document.body
  );
};

// ---- DaysSatCounter -----------------------------------------------------
// Live "N DAYS SAT" tally pinned top-center on the home route. Sums every
// completed sit's duration (end - start + 1, inclusive) plus the
// in-progress slice of any sit whose date range contains today, so the
// number ticks up each day Dylan is on a sit. Recomputes hourly; a full
// page load picks up the final value either way.
const computeDaysSat = () => {
  const sits = window.SITS || [];
  const today = new Date();
  today.setUTCHours(0, 0, 0, 0);
  let total = 0;
  for (const s of sits) {
    if (s.status === 'available') continue;  // future/unbooked — not yet sat
    const start = new Date(s.start_date + 'T00:00:00Z');
    const end = new Date(s.end_date + 'T00:00:00Z');
    if (start > today) continue;              // hasn't started yet
    const capped = end < today ? end : today; // cap in-progress at today
    const days = Math.floor((capped - start) / 86400000) + 1;
    if (days > 0) total += days;
  }
  return total;
};

const DaysSatCounter = () => {
  const [days, setDays] = useNavState(computeDaysSat);
  useNavEffect(() => {
    // Hourly recompute picks up the day rollover if the page is left
    // open overnight. Cheap — just iterates window.SITS.
    const id = setInterval(() => setDays(computeDaysSat()), 60 * 60 * 1000);
    return () => clearInterval(id);
  }, []);
  return (
    <div
      className="site-nav__center mono-caps intro-fade"
      aria-live="polite"
      aria-label={`${days} days pet-sitting to date`}
    >
      <span>{days} days pet-sitting</span>
    </div>
  );
};

window.Nav = Nav;
