/* global React, ReactDOM, DEFAULT_CONTENT, RT, CMSPanel */
const { useState, useEffect, useRef, useCallback } = React;

/* ---------------- scroll reveal (rAF based — robust everywhere) ---------------- */
function useReveal(dep) {
  useEffect(() => {
    const els = [...document.querySelectorAll(".reveal")];
    const show = (el) => el.classList.add("in");
    let raf = 0;
    const check = () => {
      raf = 0;
      const vh = window.innerHeight || document.documentElement.clientHeight;
      for (const el of els) {
        if (el.classList.contains("in")) continue;
        const r = el.getBoundingClientRect();
        if (r.top < vh * 0.92 && r.bottom > 0) show(el);
      }
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(check); };
    check();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    const failsafe = setTimeout(() => {
      // Alleen forceren als er nog niets zichtbaar werd (bevroren omgeving);
      // anders blijven de scroll-reveals gewoon werken tijdens het scrollen.
      if (els.some((el) => el.classList.contains("in"))) return;
      document.body.classList.add("reveal-done");
      els.forEach(show);
    }, 1800);
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      clearTimeout(failsafe);
    };
  }, [dep]);
}

/* ---------------- sticky stacking: hoge secties pinnen op hun onderkant ---------------- */
// Een sectie die hoger is dan het scherm krijgt een negatieve sticky-top,
// zodat je hem eerst volledig kunt uitlezen voordat hij vastklikt en de
// volgende sectie eroverheen schuift. Essentieel op telefoon, waar bijna
// elke sectie hoger is dan de viewport.
function useStackOffsets(dep) {
  useEffect(() => {
    if (document.documentElement.getAttribute("data-stack") !== "on") return;
    const sections = [...document.querySelectorAll("main > section")].filter(
      (s) => !s.classList.contains("workpin")
    );
    let raf = 0;
    const apply = () => {
      raf = 0;
      const vh = window.innerHeight;
      for (const s of sections) {
        const off = Math.min(0, vh - s.offsetHeight);
        s.style.top = `${off}px`;
      }
    };
    const schedule = () => { if (!raf) raf = requestAnimationFrame(apply); };
    apply();
    const ro = new ResizeObserver(schedule);
    sections.forEach((s) => ro.observe(s));
    window.addEventListener("resize", schedule);
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(schedule);
    return () => {
      ro.disconnect();
      window.removeEventListener("resize", schedule);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [dep]);
}

/* ---------------- nav ---------------- */
function Nav({ brand, nav }) {
  const [scrolled, setScrolled] = useState(false);
  const [open, setOpen] = useState(false);
  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 24);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  // Scroll-lock terwijl het mobiele menu open staat.
  useEffect(() => {
    document.body.classList.toggle("menu-open", open);
    return () => document.body.classList.remove("menu-open");
  }, [open]);
  // Klasse direct weghalen zodat de anchor-navigatie meteen kan scrollen.
  const close = () => { document.body.classList.remove("menu-open"); setOpen(false); };
  const links = [["#work", nav.work], ["#about", nav.about], ["#skills", nav.skills], ["#contact", nav.contact]];
  return (
    <header className={"nav" + (scrolled ? " scrolled" : "") + (open ? " open" : "")}>
      <div className="container nav__inner">
        <a className="nav__brand" href="#top" onClick={close}><span className="dot" />{brand.initials}<span style={{ color: "var(--muted)" }}>{brand.suffix}</span></a>
        <nav className="nav__links">
          <a href="#work">{nav.work}</a>
          <a href="#about">{nav.about}</a>
          <a href="#skills">{nav.skills}</a>
          <a className="nav__cta nav__links-cta" href="#contact" style={{ padding: "8px 16px", borderRadius: 8 }}>{nav.contact}</a>
        </nav>
        <button className="nav__burger" aria-label={open ? "Menu sluiten" : "Menu openen"} aria-expanded={open} onClick={() => setOpen(!open)}>
          <span /><span />
        </button>
      </div>
      <div className="nav__sheet" onClick={close}>
        <nav className="nav__sheet-links">
          {links.map(([href, label], i) => (
            <a key={href} href={href} onClick={close}>
              <span className="nav__sheet-num">{String(i + 1).padStart(2, "0")}</span>
              {label}
              <span className="nav__sheet-arr">↗</span>
            </a>
          ))}
        </nav>
      </div>
    </header>
  );
}

/* ---------------- parallax hook ---------------- */
function useParallax(items) {
  useEffect(() => {
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    let raf = 0;
    const update = () => {
      raf = 0;
      const sy = window.scrollY;
      for (const { ref, speed } of items) {
        if (!ref.current) continue;
        ref.current.style.transform = `translateY(${(sy * (1 - speed)).toFixed(2)}px)`;
      }
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(update); };
    update();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => {
      window.removeEventListener("scroll", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);
}

/* ---------------- hero ---------------- */
function Hero({ hero, projectCount, name }) {
  const asideRef = useRef(null);
  useParallax([{ ref: asideRef, speed: 0.88 }]);
  return (
    <section className="hero" id="top">
      <div className="container hero__inner">
        <div className="hero__masthead reveal">
          {hero.masthead.map((m, i) => <span key={i}><RT text={m} /></span>)}
          <span>{projectCount} projecten</span>
        </div>
        <div className="hero__main">
          <span className="hero__status"><span className="live" />{hero.status}</span>
          <h1 className="hero__title reveal">
            <span className="ln"><span>{hero.titleLine1}</span></span>
            <span className="ln"><span className="muted2">{hero.titleLine2}</span></span>
          </h1>
          <div className="hero__meta">
            <p className="hero__lead reveal" data-d="1"><RT text={hero.lead} /></p>
            <div className="hero__facts reveal" data-d="2">
              {hero.facts.map((f, i) => (
                <div className="hero__fact" key={i}><span>↳</span><RT text={f} /></div>
              ))}
            </div>
          </div>
          <div className="hero__cta-row reveal" data-d="3">
            <a className="btn btn--primary" href="#work">{hero.ctaPrimary} <span className="arr">↗</span></a>
            <a className="btn btn--ghost" href="#contact">{hero.ctaGhost}</a>
          </div>
        </div>
        <aside className="hero__aside reveal" data-d="2" ref={asideRef}>
          {hero.portrait && (
            <div className="hero__photo">
              <img src={hero.portrait} alt={name} />
              <span className="hero__photo-tag">{name}</span>
            </div>
          )}
          <p className="hero__currently">
            <i />
            <span><RT text={hero.currently} /></span>
          </p>
        </aside>
      </div>
    </section>
  );
}

/* ---------------- about ---------------- */
function About({ about }) {
  return (
    <section className="section section--panel" id="about">
      <div className="container">
        <div className="section__head">
          <span className="section__index reveal">{about.index}</span>
        </div>
        <div className="about__grid">
          <div className="about__left">
            <h2 className="about__big reveal"><RT text={about.big} /></h2>
            <div className="about__approach reveal" data-d="2">
              <span className="about__approach-h">{about.approachHeading}</span>
              <ol>
                {about.approach.map((step, i) => (
                  <li key={i}><span>{String(i + 1).padStart(2, "0")}</span>{step}</li>
                ))}
              </ol>
              <span className="about__sign">{about.sign}</span>
            </div>
          </div>
          <div className="about__col reveal" data-d="1">
            {about.paragraphs.map((p, i) => <p key={i}><RT text={p} /></p>)}
            <div className="about__list">
              {about.facts.map(([k, v]) => (
                <div className="about__li" key={k}><span>{k}</span><span>{v}</span></div>
              ))}
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---------------- site screenshot ---------------- */
function SiteScreenshot({ url }) {
  const [src, setSrc] = useState(null);
  const [failed, setFailed] = useState(false);

  useEffect(() => {
    setSrc(null);
    setFailed(false);
    fetch(`https://api.microlink.io/?url=${encodeURIComponent(url)}&screenshot=true&meta=false`)
      .then((r) => r.json())
      .then((data) => {
        const imgUrl = data?.data?.screenshot?.url;
        if (imgUrl) setSrc(imgUrl); else setFailed(true);
      })
      .catch(() => setFailed(true));
  }, [url]);

  if (failed) return (
    <div className="site-preview__placeholder">
      <span>{new URL(url).hostname}</span>
    </div>
  );
  if (!src) return <div className="site-preview__loading"><i /></div>;
  return <img className="site-preview__img" src={src} alt="Website preview" loading="lazy" />;
}

/* ---------------- project modal ---------------- */
function ProjectModal({ project: p, onClose }) {
  useEffect(() => {
    document.body.style.overflow = "hidden";
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => {
      document.body.style.overflow = "";
      window.removeEventListener("keydown", onKey);
    };
  }, [onClose]);

  let hostname = null;
  try { hostname = p.url ? new URL(p.url).hostname : null; } catch (e) { hostname = p.url; }

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-drawer" onClick={(e) => e.stopPropagation()}>
        <div className="modal-handle" />
        <div className="modal-header">
          <div>
            <span className="modal-num">{p.n}</span>
            <h2 className="modal-title">{p.title}</h2>
            <span className="modal-year">{p.year}</span>
          </div>
          <button className="modal-close" onClick={onClose} aria-label="Sluiten">✕</button>
        </div>
        <div className="modal-body">
          {p.image && <img className="modal-cover" src={p.image} alt={p.title} />}
          <div className="modal-cols">
            <div className="modal-info">
              <div className="modal-section">
                <span className="modal-label">Over dit project</span>
                <p className="modal-desc">{p.description}</p>
              </div>
              <div className="modal-section">
                <span className="modal-label">Rol</span>
                <p className="modal-desc">{p.role}</p>
              </div>
              <div className="modal-section" style={{ borderBottom: "none" }}>
                <span className="modal-label">Stack</span>
                <div className="card__tags">{(p.stack || []).map((t) => <span className="tag" key={t}>{t}</span>)}</div>
              </div>
            </div>
            <div className="modal-sidebar">
              <div className="modal-section" style={{ borderBottom: "none" }}>
                <span className="modal-label">Links</span>
                <div className="modal-links">
                  {p.github && (
                    <a className="modal-link" href={p.github} target="_blank" rel="noopener noreferrer">
                      GitHub <span className="arr">↗</span>
                    </a>
                  )}
                  {p.url && (
                    <a className="modal-link modal-link--accent" href={p.url} target="_blank" rel="noopener noreferrer">
                      Website <span className="arr">↗</span>
                    </a>
                  )}
                  {!p.github && !p.url && <span style={{ color: "var(--muted)", fontSize: 14 }}>Binnenkort beschikbaar</span>}
                </div>
                {p.url && (
                  <a className="site-preview" href={p.url} target="_blank" rel="noopener noreferrer">
                    <div className="site-preview__chrome">
                      <span className="site-preview__dots"><i /><i /><i /></span>
                      <span className="site-preview__bar">{hostname}</span>
                    </div>
                    <div className="site-preview__body">
                      <SiteScreenshot url={p.url} />
                    </div>
                  </a>
                )}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------------- projecten: pinned horizontale scroll ---------------- */
// De sectie 'pint' op het scherm zodra je hem bereikt; verticaal scrollen
// beweegt dan horizontaal door de projecten. Pas als alle projecten voorbij
// zijn gekomen, scrollt de pagina verder naar beneden. Werkt met native scroll
// (geen preventDefault), dus ook soepel op touch.
function Work({ projects, work, onSelect }) {
  const spacerRef = useRef(null);
  const viewportRef = useRef(null);
  const trackRef = useRef(null);
  const distRef = useRef(0);
  const [dist, setDist] = useState(0);
  const [progress, setProgress] = useState(0);

  const measure = useCallback(() => {
    const track = trackRef.current, vp = viewportRef.current;
    if (!track || !vp) return;
    const H = Math.max(0, track.scrollWidth - vp.clientWidth);
    distRef.current = H;
    setDist(H);
  }, []);

  // Hermeet bij resize, contentwijziging en nadat beelden/fonts geladen zijn.
  useEffect(() => {
    measure();
    const t1 = setTimeout(measure, 250);
    const t2 = setTimeout(measure, 1200);
    window.addEventListener("resize", measure);
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(measure);
    return () => { clearTimeout(t1); clearTimeout(t2); window.removeEventListener("resize", measure); };
  }, [measure, projects]);

  // Verticale scrollpositie binnen de 'spacer' → horizontale verplaatsing.
  useEffect(() => {
    const onScroll = () => {
      const spacer = spacerRef.current, track = trackRef.current;
      if (!spacer || !track) return;
      const H = distRef.current;
      const scrolled = Math.min(Math.max(-spacer.getBoundingClientRect().top, 0), H);
      const p = H > 0 ? scrolled / H : 0;
      track.style.transform = `translate3d(${(-p * H).toFixed(1)}px,0,0)`;
      setProgress(p);
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, [dist]);

  // Pijlknoppen: scroll de pagina één kaart vooruit/achteruit (= horizontaal).
  const step = (dir) => {
    const track = trackRef.current;
    const card = track && track.querySelector(".card");
    const gap = track ? parseFloat(getComputedStyle(track).columnGap || "24") : 24;
    const amt = card ? card.getBoundingClientRect().width + gap : window.innerHeight * 0.8;
    window.scrollBy({ top: dir * amt, behavior: "smooth" });
  };

  const pinned = dist > 0;

  return (
    <section className="section workpin" id="work" ref={spacerRef}
             style={pinned ? { height: `calc(100vh + ${Math.round(dist)}px)` } : undefined}>
      <div className="workpin__sticky">
        <div className="container">
          <div className="section__head">
            <div>
              <span className="section__index reveal">{work.index}</span>
              <h2 className="section__title reveal" data-d="1" style={{ marginTop: 14 }}>{work.title}</h2>
            </div>
            <div className="work__head-extra reveal" data-d="2">
              <span className="work__count">{projects.length} projecten</span>
              <div className="car__controls">
                <button className="car__btn" aria-label="Vorige" disabled={progress <= 0.001} onClick={() => step(-1)}>←</button>
                <button className="car__btn" aria-label="Volgende" disabled={progress >= 0.999} onClick={() => step(1)}>→</button>
              </div>
            </div>
          </div>
        </div>

        <div className="workpin__viewport" ref={viewportRef}>
          <div className="workpin__track" ref={trackRef}>
            {projects.map((p) => (
              <article className="card reveal" key={p.n} style={{ cursor: "pointer" }} onClick={() => onSelect(p)}>
                <div className="card__media">
                  {p.image && <img className="card__img" src={p.image} alt={p.title} loading="lazy" />}
                  <span className="card__num">{p.n}</span>
                  {p.featured && <span className="card__star">★ Featured</span>}
                  <div className="card__overlay"><span className="card__open">↗</span></div>
                </div>
                <div className="card__body">
                  <div className="card__row">
                    <h3 className="card__title">{p.title}</h3>
                    <span className="card__year">{p.year}</span>
                  </div>
                  <p style={{ color: "var(--fg-dim)", fontSize: 15, marginTop: -4 }}>{p.blurb}</p>
                  <div className="card__tags">{(p.tags || []).map((t) => <span className="tag" key={t}>{t}</span>)}</div>
                </div>
              </article>
            ))}
          </div>
        </div>

        <div className="container">
          <div className="car__progress"><i style={{ width: `${30 + progress * 70}%` }} /></div>
        </div>
      </div>
    </section>
  );
}

/* ---------------- skills ---------------- */
function Skills({ skills, meta }) {
  return (
    <section className="section section--panel" id="skills">
      <div className="container">
        <div className="section__head">
          <div>
            <span className="section__index reveal">{meta.index}</span>
            <h2 className="section__title reveal" data-d="1" style={{ marginTop: 14 }}>{meta.title}</h2>
          </div>
        </div>
        <div className="skills__grid">
          {skills.map((c, i) => (
            <div className="skillcol reveal" data-d={i + 1} key={c.heading + i}>
              <h4>{c.heading}</h4>
              <ul>{(c.items || []).map(([name]) => <li key={name}>{name}</li>)}</ul>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------------- contact ---------------- */
function useLocalClock(tz) {
  const [now, setNow] = useState("");
  useEffect(() => {
    const fmt = new Intl.DateTimeFormat("nl-NL", { hour: "2-digit", minute: "2-digit", timeZone: tz });
    const tick = () => setNow(fmt.format(new Date()));
    tick();
    const id = setInterval(tick, 1000 * 20);
    return () => clearInterval(id);
  }, [tz]);
  return now;
}

function Contact({ contact }) {
  const time = useLocalClock("Europe/Amsterdam");
  const mailHref = contact.email.startsWith("mailto:") ? contact.email : "mailto:" + contact.email;
  return (
    <section className="section contact contact--light" id="contact">
      <div className="container">
        <div className="contact__top reveal">
          <span className="section__index">{contact.index}</span>
          <span className="contact__clock"><i className="contact__clock-dot" />{time || "—:—"} in {contact.clockCity}</span>
        </div>

        <p className="contact__intro reveal" data-d="1">{contact.intro}</p>
        <a className="contact__mail reveal" data-d="1" href={mailHref}>
          {contact.email.replace(/^mailto:/, "")}<span className="contact__mail-arr">↗</span>
        </a>

        <div className="contact__meta reveal" data-d="2">
          {contact.meta.map(([k, v]) => (
            <div className="cmeta" key={k}><small>{k}</small><b>{v}</b></div>
          ))}
        </div>

        <div className="contact__bottom reveal" data-d="3">
          <p className="contact__note">{contact.note}</p>
          <div className="socials socials--ink">
            {contact.socials.map(([label, href]) => (
              <a key={label} href={href}>{label}</a>
            ))}
          </div>
        </div>
      </div>
      <footer className="foot">
        <div className="container foot__inner">
          <span>{contact.footerLeft}</span>
          <span>{contact.footerRight}</span>
          <a href="#top">Terug naar boven ↑</a>
        </div>
      </footer>
    </section>
  );
}

/* ---------------- content merge ---------------- */
// Vul ontbrekende velden aan met de defaults zodat de UI nooit op undefined stuit.
function withDefaults(data) {
  const d = DEFAULT_CONTENT;
  const c = data && typeof data === "object" ? data : {};
  return {
    brand: { ...d.brand, ...(c.brand || {}) },
    nav: { ...d.nav, ...(c.nav || {}) },
    hero: { ...d.hero, ...(c.hero || {}) },
    about: { ...d.about, ...(c.about || {}) },
    work: { ...d.work, ...(c.work || {}) },
    skillsMeta: { ...d.skillsMeta, ...(c.skillsMeta || {}) },
    contact: { ...d.contact, ...(c.contact || {}) },
    projects: Array.isArray(c.projects) ? c.projects : d.projects,
    skills: Array.isArray(c.skills) ? c.skills : d.skills,
  };
}

function App() {
  const [content, setContent] = useState(() => withDefaults(DEFAULT_CONTENT));
  const [selected, setSelected] = useState(null);
  const [loaded, setLoaded] = useState(false);
  useReveal(loaded);
  useStackOffsets(loaded);

  // Content uit de database laden (valt terug op defaults als de API niet bereikbaar is).
  useEffect(() => {
    fetch("api/content.php", { credentials: "same-origin" })
      .then((r) => r.json())
      .then((data) => { if (data && !data.error) setContent(withDefaults(data)); })
      .catch(() => {})
      .finally(() => setLoaded(true));
  }, []);

  // Projecten verrijken met weergavenummer.
  const projects = content.projects.map((p, i) => ({ ...p, n: String(i + 1).padStart(2, "0") }));

  return (
    <>
      <Nav brand={content.brand} nav={content.nav} />
      <main>
        <Hero hero={content.hero} projectCount={projects.length} name={content.brand.name} />
        <About about={content.about} />
        <Work projects={projects} work={content.work} onSelect={setSelected} />
        <Skills skills={content.skills} meta={content.skillsMeta} />
        <Contact contact={content.contact} />
      </main>
      {selected && <ProjectModal project={selected} onClose={() => setSelected(null)} />}

      <CMSPanel content={content} onChange={setContent} onReload={(data) => setContent(withDefaults(data))} />
    </>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
