// app.jsx — Home page composition.
// Loads after copy.js, tweaks-panel.jsx, sections.jsx.
// Uses TWEAK_DEFAULTS marker block (host rewrites this on disk).

const { useState, useEffect, useRef, useMemo, useCallback } = React;

// EDITMODE block — host parses this JSON, rewrites on tweak.
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "lang": "es",
  "palette": "fog",
  "typePair": "neue-montreal",
  "heroVariant": 0,
  "workLayout": 1,
  "bioPhoto": "stagger"
} /*EDITMODE-END*/;

// ── Palette presets — off-white organic backgrounds ─────────────────────
const PALETTES = {
  paper: {
    label: "Papel",
    "--c-bg": "#F8F6F1",
    "--c-bg-soft": "#F2EFE8",
    "--c-paper": "#EAE6DC",
    "--c-rule": "#D6D0C2",
    "--c-mute": "#A39C90",
    "--c-text-soft": "#807870",
    "--c-text": "#12110D",
    "--c-text-deep": "#1F1D1A"
  },
  porcelain: {
    label: "Porcelana",
    "--c-bg": "#FAF8F4",
    "--c-bg-soft": "#F4F1EB",
    "--c-paper": "#ECE7DC",
    "--c-rule": "#D9D2C2",
    "--c-mute": "#A19A8E",
    "--c-text-soft": "#6E665C",
    "--c-text": "#12110D",
    "--c-text-deep": "#1A1815"
  },
  chalk: {
    label: "Tiza",
    "--c-bg": "#F6F4EE",
    "--c-bg-soft": "#EFEBE2",
    "--c-paper": "#E4DFD2",
    "--c-rule": "#CFC8B8",
    "--c-mute": "#9D958A",
    "--c-text-soft": "#5E574F",
    "--c-text": "#12110D",
    "--c-text-deep": "#17150F"
  },
  fog: {
    label: "Niebla",
    "--c-bg": "#FDFDFC",
    "--c-bg-soft": "#F4F4F2",
    "--c-paper": "#EAEAE7",
    "--c-rule": "#D6D5D0",
    "--c-mute": "#9C9A93",
    "--c-text-soft": "#6A6862",
    "--c-text": "#12110D",
    "--c-text-deep": "#12110D"
  },
  pure: {
    label: "Blanco puro",
    "--c-bg": "#FFFFFF",
    "--c-bg-soft": "#F7F7F5",
    "--c-paper": "#EDEDEA",
    "--c-rule": "#D8D8D4",
    "--c-mute": "#A0A09C",
    "--c-text-soft": "#5C5C56",
    "--c-text": "#12110D",
    "--c-text-deep": "#0E0E0C"
  },
  ash: {
    label: "Ceniza · F0",
    "--c-bg": "#F0F0F0",
    "--c-bg-soft": "#E8E8E8",
    "--c-paper": "#DEDEDE",
    "--c-rule": "#CACACA",
    "--c-mute": "#999999",
    "--c-text-soft": "#5A5A5A",
    "--c-text": "#12110D",
    "--c-text-deep": "#0F0F0F"
  }
};

// Sans-serif protagonist: pure-sans pairings. The "display" var is also a sans;
// italic accent comes from the sans family's own italic, not from a serif.
const TYPE_PAIRS = {
  "inter": {
    label: "Inter",
    "--f-display": '"Inter", -apple-system, system-ui, sans-serif',
    "--f-sans": '"Inter", -apple-system, system-ui, sans-serif'
  },
  "manrope": {
    label: "Manrope",
    "--f-display": '"Manrope", -apple-system, system-ui, sans-serif',
    "--f-sans": '"Manrope", -apple-system, system-ui, sans-serif'
  },
  "neue-montreal": {
    label: "Neue Montreal",
    // PP Neue Montreal is licensed (Pangram Pangram). Geist is the closest open
    // substitute in the same humanist-grotesk lineage; we keep the label for
    // brief intent and stack PP Neue Montreal first if a user has it installed.
    "--f-display": '"PP Neue Montreal", "Geist", "Inter", -apple-system, system-ui, sans-serif',
    "--f-sans": '"PP Neue Montreal", "Geist", "Inter", -apple-system, system-ui, sans-serif'
  }
};

// ── Dot cursor (hover-capable devices only) ─────────────────────────────
function DotCursor() {
  const dotRef = useRef(null);
  useEffect(() => {
    const supportsHover = window.matchMedia("(hover: hover) and (pointer: fine)").matches;
    if (!supportsHover) return;
    const dot = dotRef.current;
    if (!dot) return;
    let raf = 0;
    let tx = -100,ty = -100,x = -100,y = -100;
    const onMove = (e) => {
      tx = e.clientX;ty = e.clientY;
      if (!raf) raf = requestAnimationFrame(loop);
    };
    const loop = () => {
      x += (tx - x) * 0.45;
      y += (ty - y) * 0.45;
      dot.style.transform = `translate(${x}px, ${y}px) translate(-50%,-50%)`;
      if (Math.abs(tx - x) > 0.1 || Math.abs(ty - y) > 0.1) {
        raf = requestAnimationFrame(loop);
      } else {raf = 0;}
    };
    const onOver = (e) => {
      const t = e.target;
      if (t.closest("a, button, [role='button'], input, textarea, .interactive")) {
        dot.classList.add("hover");
      } else {
        dot.classList.remove("hover");
      }
      // Invert the cursor over dark surfaces
      if (t.closest(".on-dark, .omenu-panel.is-open, .footer, .fab-contact")) {
        dot.classList.add("on-dark");
      } else {
        dot.classList.remove("on-dark");
      }
    };
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseover", onOver);
    return () => {
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseover", onOver);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);
  return <div ref={dotRef} className="dot-cursor" />;
}

// ── Reveal-on-scroll hook ──────────────────────────────────────────────
// Note: we tried IntersectionObserver but it doesn't fire in some preview
// environments. Falls back to a scroll listener with getBoundingClientRect.
function useReveal() {
  useEffect(() => {
    let pending = new Set(document.querySelectorAll(".reveal, .reveal-stagger"));
    const reveal = () => {
      const vh = window.innerHeight;
      const entering = [];
      pending.forEach((el) => {
        const r = el.getBoundingClientRect();
        if (r.top < vh * 0.9 && r.bottom > 0) {
          entering.push({ el, top: r.top });
          pending.delete(el);
        }
      });
      // Sort top-to-bottom so elements higher on screen always reveal first
      entering.sort((a, b) => a.top - b.top);
      entering.forEach(({ el }, i) => {
        if (i === 0) {
          el.classList.add("in");
        } else {
          setTimeout(() => el.classList.add("in"), i * 60);
        }
      });
      if (pending.size === 0) {
        window.removeEventListener("scroll", reveal);
        window.removeEventListener("resize", reveal);
      }
    };
    // Run on next frame so layout is settled
    const raf = requestAnimationFrame(reveal);
    window.addEventListener("scroll", reveal, { passive: true });
    window.addEventListener("resize", reveal);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("scroll", reveal);
      window.removeEventListener("resize", reveal);
    };
  }, []);
}

// Always start at top on load/refresh
history.scrollRestoration = "manual";
window.scrollTo(0, 0);

// ── Top bar / nav ──────────────────────────────────────────────────────
function TopBar({ lang, onLang, copy, pageKind }) {
  const [scrolled, setScrolled] = React.useState(false);
  const [open, setOpen] = React.useState(false);
  const wrapRef = React.useRef(null);
  const wasOpenedRef = React.useRef(false);
  if (open) wasOpenedRef.current = true;

  React.useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 80);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  // Close on outside click / Esc
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
    };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);

  const Grid = (
    <span className="omenu-grid" aria-hidden="true">
      {Array.from({ length: 9 }).map((_, i) => <span key={i} />)}
    </span>
  );

  return (
    <header className={"topbar" + (scrolled ? " is-scrolled" : "")} data-screen-label="Top bar">
      <div className="nav-left">
        <a href="/" className="brand nav-link brand-swap" style={{ color: "var(--c-text-deep)" }}>
          <span className="brand-default">/freijo</span>
          <span className="brand-hover" aria-hidden="true">/valentina</span>
        </a>
      </div>

      <div className="omenu-wrap" ref={wrapRef}>
        <button
          type="button"
          className={"omenu-trigger interactive" + (open ? " is-open" : "")}
          aria-expanded={open}
          aria-haspopup="menu"
          aria-label={lang === "es" ? "Abrir menú" : "Open menu"}
          onClick={() => setOpen(v => !v)}
        >
          {Grid}
        </button>

        <div className={"omenu-panel" + (open ? " is-open" : (wasOpenedRef.current ? " is-closing" : ""))} role="menu" aria-hidden={!open}>
          <div className="omenu-panel-head">
            {Grid}
          </div>

          <nav className="omenu-links" aria-label="Primary">
            <a
              href="/"
              className={"omenu-link" + (pageKind === "home" ? " is-active" : "")}
              aria-current={pageKind === "home" ? "page" : undefined}
              role="menuitem"
            >
              {lang === "es" ? "Inicio" : "Home"}
            </a>
            <a
              href="/pages/servicios.html"
              className={"omenu-link" + (pageKind === "services" ? " is-active" : "")}
              aria-current={pageKind === "services" ? "page" : undefined}
              role="menuitem"
            >
              {lang === "es" ? "Servicios" : "Services"}
            </a>
            <a
              href="/pages/bio.html"
              className={"omenu-link" + (pageKind === "bio" ? " is-active" : "")}
              aria-current={pageKind === "bio" ? "page" : undefined}
              role="menuitem"
            >
              {copy.nav.bio}
            </a>
            <a
              href="/pages/contacto.html"
              className={"omenu-link" + (pageKind === "contact" ? " is-active" : "")}
              aria-current={pageKind === "contact" ? "page" : undefined}
              role="menuitem"
              onClick={() => setOpen(false)}
            >
              {lang === "es" ? "Conversemos" : "Let's talk"}
            </a>
          </nav>

          <div className="omenu-divider" />

          <nav className="omenu-socials" aria-label="Social">
            <a href="https://www.behance.net/valentinafe743" target="_blank" rel="noreferrer noopener" className="omenu-social interactive" aria-label="Behance">
              <svg viewBox="0 0 640 640" width="32" height="32" fill="currentColor" aria-hidden="true">
                <path d="M251.3 382.4C268.5 382.4 282.5 376.3 282.5 357C282.5 337.3 270.8 329.6 252.2 329.5L206.2 329.5L206.2 382.4L251.3 382.4zM245.9 252.8L206.3 252.8L206.3 297.6L249 297.6C264.1 297.6 274.8 291 274.8 274.7C274.8 257 261.1 252.8 245.9 252.8zM375.4 327.6L437.6 327.6C435.9 309.1 426.3 297.9 407.1 297.9C388.8 297.9 376.6 309.3 375.4 327.6zM480 96L160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 160C544 124.7 515.3 96 480 96zM445.5 249L367.7 249L367.7 230.1L445.5 230.1L445.5 249zM289.7 307.7C313.3 314.4 324.7 335.2 324.7 359.3C324.7 398.3 292 415 257.1 415.2L164 415.2L164 223.2L254.5 223.2C287.4 223.2 315.9 232.5 315.9 270.7C315.9 290 306.9 299.5 289.7 307.7zM408.4 269.1C451.9 269.1 476 303.4 476 344.5C476 346.1 475.9 347.8 475.8 349.5C475.8 350.3 475.7 351 475.7 351.7L375.5 351.7C375.5 373.9 387.2 387 409.6 387C421.2 387 436.1 380.8 439.8 368.9L473.5 368.9C463.1 400.8 441.6 415.7 408.4 415.7C364.6 415.7 337.3 386 337.3 342.7C337.3 300.9 366 269.1 408.4 269.1z"/>
              </svg>
            </a>
            <a href="https://www.linkedin.com/in/valentina-freijo-142417143/" target="_blank" rel="noreferrer noopener" className="omenu-social interactive" aria-label="LinkedIn">
              <svg viewBox="0 0 640 640" width="32" height="32" fill="currentColor" aria-hidden="true">
                <path d="M160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 160C544 124.7 515.3 96 480 96L160 96zM165 266.2L231.5 266.2L231.5 480L165 480L165 266.2zM236.7 198.5C236.7 219.8 219.5 237 198.2 237C176.9 237 159.7 219.8 159.7 198.5C159.7 177.2 176.9 160 198.2 160C219.5 160 236.7 177.2 236.7 198.5zM413.9 480L413.9 376C413.9 351.2 413.4 319.3 379.4 319.3C344.8 319.3 339.5 346.3 339.5 374.2L339.5 480L273.1 480L273.1 266.2L336.8 266.2L336.8 295.4L337.7 295.4C346.6 278.6 368.3 260.9 400.6 260.9C467.8 260.9 480.3 305.2 480.3 362.8L480.3 480L413.9 480z"/>
              </svg>
            </a>
            <a href="mailto:valentinafreijo13@gmail.com" className="omenu-social interactive" aria-label="Email">
              <img src="/assets/email.svg" width="32" height="32" className="omenu-social-img" style={{ display: "block" }} alt="" />
            </a>
          </nav>

        </div>
      </div>
    </header>
  );
}

// ── Footer ─────────────────────────────────────────────────────────────
function Footer({ copy, lang }) {
  const ref = React.useRef(null);

  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const check = () => {
      if (window.scrollY + window.innerHeight >= document.documentElement.scrollHeight - 40) {
        el.classList.add("footer--visible");
        window.removeEventListener("scroll", check);
      }
    };
    requestAnimationFrame(check);
    window.addEventListener("scroll", check, { passive: true });
    return () => window.removeEventListener("scroll", check);
  }, []);

  const year = new Date().getFullYear();

  return (
    <footer className="footer footer--dark" id="footer" ref={ref}>
      <div className="footer-inner shell">
        <div className="footer-simple-row">
          <div className="footer-icons">
            <a href="https://www.behance.net/valentinafe743" target="_blank" rel="noreferrer noopener" className="footer-icon-link interactive" aria-label="Behance">
              <svg viewBox="0 0 640 640" width="34" height="34" fill="currentColor" aria-hidden="true">
                <path d="M251.3 382.4C268.5 382.4 282.5 376.3 282.5 357C282.5 337.3 270.8 329.6 252.2 329.5L206.2 329.5L206.2 382.4L251.3 382.4zM245.9 252.8L206.3 252.8L206.3 297.6L249 297.6C264.1 297.6 274.8 291 274.8 274.7C274.8 257 261.1 252.8 245.9 252.8zM375.4 327.6L437.6 327.6C435.9 309.1 426.3 297.9 407.1 297.9C388.8 297.9 376.6 309.3 375.4 327.6zM480 96L160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 160C544 124.7 515.3 96 480 96zM445.5 249L367.7 249L367.7 230.1L445.5 230.1L445.5 249zM289.7 307.7C313.3 314.4 324.7 335.2 324.7 359.3C324.7 398.3 292 415 257.1 415.2L164 415.2L164 223.2L254.5 223.2C287.4 223.2 315.9 232.5 315.9 270.7C315.9 290 306.9 299.5 289.7 307.7zM408.4 269.1C451.9 269.1 476 303.4 476 344.5C476 346.1 475.9 347.8 475.8 349.5C475.8 350.3 475.7 351 475.7 351.7L375.5 351.7C375.5 373.9 387.2 387 409.6 387C421.2 387 436.1 380.8 439.8 368.9L473.5 368.9C463.1 400.8 441.6 415.7 408.4 415.7C364.6 415.7 337.3 386 337.3 342.7C337.3 300.9 366 269.1 408.4 269.1z"/>
              </svg>
            </a>
            <a href="https://www.linkedin.com/in/valentina-freijo-142417143/" target="_blank" rel="noreferrer noopener" className="footer-icon-link interactive" aria-label="LinkedIn">
              <svg viewBox="0 0 640 640" width="34" height="34" fill="currentColor" aria-hidden="true">
                <path d="M160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 160C544 124.7 515.3 96 480 96L160 96zM165 266.2L231.5 266.2L231.5 480L165 480L165 266.2zM236.7 198.5C236.7 219.8 219.5 237 198.2 237C176.9 237 159.7 219.8 159.7 198.5C159.7 177.2 176.9 160 198.2 160C219.5 160 236.7 177.2 236.7 198.5zM413.9 480L413.9 376C413.9 351.2 413.4 319.3 379.4 319.3C344.8 319.3 339.5 346.3 339.5 374.2L339.5 480L273.1 480L273.1 266.2L336.8 266.2L336.8 295.4L337.7 295.4C346.6 278.6 368.3 260.9 400.6 260.9C467.8 260.9 480.3 305.2 480.3 362.8L480.3 480L413.9 480z"/>
              </svg>
            </a>
            <a href="mailto:valentinafreijo13@gmail.com" className="footer-icon-link interactive" aria-label="Email">
              <img src="/assets/email.svg" width="34" height="34" style={{ display: "block", filter: "brightness(0) invert(0.5)" }} alt="" />
            </a>
          </div>
          <span className="footer-copy">© {year} Valentina Freijo</span>
          <a href="/pages/terminos.html" className="footer-terms interactive">
            {lang === "es" ? "Legal" : "Legal"}
          </a>
        </div>
      </div>
    </footer>
  );
}

// ── App ────────────────────────────────────────────────────────────────
function App({ pageKind = "home" }) {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const lang = t.lang === "en" ? "en" : "es";
  const copy = window.COPY[lang];

  // Apply palette + type pair as CSS variables on :root
  useEffect(() => {
    const pal = PALETTES[t.palette] || PALETTES.paper;
    const tp = TYPE_PAIRS[t.typePair] || TYPE_PAIRS["inter"];
    const root = document.documentElement;
    Object.entries(pal).forEach(([k, v]) => {if (k.startsWith("--")) root.style.setProperty(k, v);});
    Object.entries(tp).forEach(([k, v]) => {if (k.startsWith("--")) root.style.setProperty(k, v);});
  }, [t.palette, t.typePair]);

  useEffect(() => {
    document.documentElement.lang = lang;
  }, [lang]);

  useReveal();

  return (
    <>
      <DotCursor />
      <TopBar lang={lang} onLang={(v) => setTweak("lang", v)} copy={copy} pageKind={pageKind} />
      {pageKind === "home" ?
      <ProjectsStack copy={copy.work} lang={lang} /> :
      pageKind === "services" ?
      <ServicesPage copy={copy} lang={lang} /> :
      pageKind === "projects" ?
      <ProjectsStack copy={copy.work} lang={lang} /> :
      pageKind === "project" ? (() => {
        const params = new URLSearchParams(window.location.search);
        const id = params.get("id");
        const items = copy.work.items || [];
        const project = items.find((p) => p.n === id);
        return project ? <ProjectDetail project={project} allProjects={items} lang={lang} /> : <ProjectNotFound id={id} />;
      })() :
      pageKind === "contact" ?
      <ContactPage copy={copy.contact} lang={lang} /> :
      pageKind === "legal" ?
      <LegalPage copy={copy} lang={lang} /> :

      <BioPage copy={copy.bio} lang={lang} variant={t.bioPhoto || "stagger"} />
      }

      {pageKind !== "home" && (
        <div className="footer-clip-wrap"><Footer copy={copy} lang={lang} /></div>
      )}

      <TweaksPanel title={copy.tweaksLabel}>
        <TweakSection label={lang === "es" ? "Tipografía" : "Typography"} />
        <TweakSelect
          label={lang === "es" ? "Pareja tipográfica" : "Type pair"}
          value={t.typePair}
          options={Object.entries(TYPE_PAIRS).map(([k, v]) => ({ value: k, label: v.label }))}
          onChange={(v) => setTweak("typePair", v)} />
        

        <TweakSection label={lang === "es" ? "Paleta" : "Palette"} />
        <TweakRadio
          label={lang === "es" ? "Neutros" : "Neutrals"}
          value={t.palette}
          options={[
          { value: "fog", label: "Niebla" },
          { value: "pure", label: "Blanco" },
          { value: "ash", label: "F0" }]
          }
          onChange={(v) => setTweak("palette", v)} />
        

        <TweakSection label={lang === "es" ? "Hero" : "Hero"} />
        <TweakRadio
          label={lang === "es" ? "Manifiesto" : "Manifesto"}
          value={String(t.heroVariant)}
          options={[
          { value: "0", label: "001" },
          { value: "1", label: "002" },
          { value: "2", label: "003" }]
          }
          onChange={(v) => setTweak("heroVariant", parseInt(v, 10))} />
        

        <TweakSection label={lang === "es" ? "Proyectos" : "Work"} />
        <TweakRadio
          label={lang === "es" ? "Layout" : "Layout"}
          value={String(t.workLayout)}
          options={[
          { value: "0", label: lang === "es" ? "Tabla" : "Table" },
          { value: "1", label: lang === "es" ? "Bloques" : "Blocks" },
          { value: "2", label: lang === "es" ? "Índice" : "Index" }]
          }
          onChange={(v) => setTweak("workLayout", parseInt(v, 10))} />
        

        <TweakSection label={lang === "es" ? "Bio · Foto" : "Bio · Photo"} />
        <TweakRadio
          label={lang === "es" ? "Composición" : "Composition"}
          value={t.bioPhoto || "stagger"}
          options={[
          { value: "stagger", label: lang === "es" ? "Escalonada" : "Stagger" },
          { value: "polaroid", label: "Polaroid" },
          { value: "panel", label: lang === "es" ? "Panel" : "Panel" },
          { value: "inline", label: lang === "es" ? "Insertada" : "Inline" }]
          }
          onChange={(v) => setTweak("bioPhoto", v)} />
        

        <TweakSection label={lang === "es" ? "Idioma" : "Language"} />
        <TweakRadio
          label="Idioma · Language"
          value={lang}
          options={[
          { value: "es", label: "ES" },
          { value: "en", label: "EN" }]
          }
          onChange={(v) => setTweak("lang", v)} />
        
      </TweaksPanel>
    </>);

}

// Bootstrap — pageKind injected by host page
window.__mountPortfolio = function (kind) {
  const root = ReactDOM.createRoot(document.getElementById("root"));
  root.render(<App pageKind={kind} />);
};