/* global React, ReactDOM, Nav, Footer, Home, ProjectAva, ProjectStratco, ProjectHand, About, Contact,
          useTweaks, TweaksPanel, TweakSection, TweakRadio, TweakColor, TweakToggle */

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

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "homeLayout": "catalogue",
  "thumbStyle": "image",
  "accent": "steel",
  "dark": false,
  "density": "regular"
}/*EDITMODE-END*/;

/* ============================================================
   Sunrise / sunset auto theme
   ------------------------------------------------------------
   On first visit, picks dark or light based on whether the sun
   is currently above the horizon at the visitor's location.
   Location is inferred from the browser's IANA timezone. no
   permission prompt, no external API. Manual toggle (in the
   nav) overrides and is remembered in localStorage.
   ============================================================ */

const SUN_OVERRIDE_KEY = "ueno:theme-override";

// Approx lat/lon for common IANA zones. covers the bulk of
// visitors without a network call. Anything outside the table
// falls back to a timezone-offset estimate (good to ±15° lon).
const TZ_COORDS = {
  "Asia/Phnom_Penh":   [11.55, 104.92],
  "Asia/Bangkok":      [13.75, 100.50],
  "Asia/Ho_Chi_Minh":  [10.78, 106.70],
  "Asia/Singapore":    [ 1.35, 103.82],
  "Asia/Kuala_Lumpur": [ 3.14, 101.69],
  "Asia/Jakarta":      [-6.21, 106.85],
  "Asia/Manila":       [14.60, 120.98],
  "Asia/Tokyo":        [35.68, 139.69],
  "Asia/Hong_Kong":    [22.32, 114.17],
  "Asia/Shanghai":     [31.23, 121.47],
  "Asia/Seoul":        [37.57, 126.98],
  "Asia/Kolkata":      [28.61,  77.21],
  "Asia/Dubai":        [25.20,  55.27],
  "Australia/Sydney":      [-33.87, 151.21],
  "Australia/Melbourne":   [-37.81, 144.96],
  "Australia/Brisbane":    [-27.47, 153.03],
  "Australia/Perth":       [-31.95, 115.86],
  "Australia/Adelaide":    [-34.93, 138.60],
  "Pacific/Auckland":      [-36.85, 174.76],
  "Europe/London":     [51.51,  -0.13],
  "Europe/Paris":      [48.86,   2.35],
  "Europe/Berlin":     [52.52,  13.40],
  "Europe/Madrid":     [40.42,  -3.70],
  "Europe/Rome":       [41.90,  12.50],
  "Europe/Amsterdam":  [52.37,   4.90],
  "Europe/Stockholm":  [59.33,  18.07],
  "Europe/Moscow":     [55.76,  37.62],
  "America/New_York":     [40.71, -74.01],
  "America/Chicago":      [41.88, -87.63],
  "America/Denver":       [39.74,-104.99],
  "America/Los_Angeles":  [34.05,-118.24],
  "America/Toronto":      [43.65, -79.38],
  "America/Vancouver":    [49.28,-123.12],
  "America/Sao_Paulo":    [-23.55, -46.63],
  "America/Mexico_City":  [19.43, -99.13],
  "Africa/Johannesburg":  [-26.20,  28.05],
  "Africa/Cairo":         [30.04,  31.24],
};

function getVisitorCoords() {
  let tz = "Asia/Phnom_Penh";
  try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone || tz; } catch (e) {}
  if (TZ_COORDS[tz]) return { lat: TZ_COORDS[tz][0], lon: TZ_COORDS[tz][1], tz };
  // Fallback: estimate longitude from current UTC offset, assume mid-latitude.
  const offsetHours = -new Date().getTimezoneOffset() / 60;
  return { lat: 35, lon: offsetHours * 15, tz };
}

// NOAA sunrise/sunset algorithm. returns UTC hours (0–24) for
// rise and set on the given date at the given coordinates.
// Reference: http://edwilliams.org/sunrise_sunset_algorithm.htm
function sunTimes(date, lat, lon) {
  const rad = Math.PI / 180;
  const N = Math.floor(
    (Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) -
      Date.UTC(date.getUTCFullYear(), 0, 0)) / 86400000
  );
  const lngHour = lon / 15;
  function calc(rising) {
    const t = N + ((rising ? 6 : 18) - lngHour) / 24;
    const M = (0.9856 * t) - 3.289;
    let L = M + 1.916 * Math.sin(M * rad) + 0.020 * Math.sin(2 * M * rad) + 282.634;
    L = ((L % 360) + 360) % 360;
    let RA = Math.atan(0.91764 * Math.tan(L * rad)) / rad;
    RA = ((RA % 360) + 360) % 360;
    const Lq = Math.floor(L / 90) * 90;
    const RAq = Math.floor(RA / 90) * 90;
    RA = (RA + (Lq - RAq)) / 15;
    const sinDec = 0.39782 * Math.sin(L * rad);
    const cosDec = Math.cos(Math.asin(sinDec));
    const zenith = 90.833; // official sunrise/sunset
    const cosH = (Math.cos(zenith * rad) - sinDec * Math.sin(lat * rad)) /
                 (cosDec * Math.cos(lat * rad));
    if (cosH > 1 || cosH < -1) return null; // polar day/night
    let H = rising ? 360 - Math.acos(cosH) / rad : Math.acos(cosH) / rad;
    H = H / 15;
    const T = H + RA - 0.06571 * t - 6.622;
    return ((T - lngHour) % 24 + 24) % 24;
  }
  return { rise: calc(true), set: calc(false) };
}

function isNightNow(date, lat, lon) {
  const { rise, set } = sunTimes(date, lat, lon);
  if (rise == null || set == null) {
    // Polar regions: fall back to hemisphere season (June ↔ December).
    const month = date.getUTCMonth();
    const northernSummer = month >= 3 && month <= 8;
    return lat >= 0 ? !northernSummer : northernSummer;
  }
  const hUTC = date.getUTCHours() + date.getUTCMinutes() / 60;
  // Rise/set are UTC hours; account for them crossing midnight.
  if (rise < set) return hUTC < rise || hUTC > set;
  return hUTC > set && hUTC < rise;
}

function autoDarkForNow() {
  const { lat, lon } = getVisitorCoords();
  return isNightNow(new Date(), lat, lon);
}

/* ============================================================
   Command palette (Cmd/Ctrl+K)
   ============================================================ */
function CommandPalette({ open, onClose, setRoute, setTweak, tweaks }) {
  const inputRef = useRef(null);
  const [q, setQ] = useState("");
  const [active, setActive] = useState(0);

  useEffect(() => {
    if (open) {
      setQ("");
      setActive(0);
      setTimeout(() => inputRef.current && inputRef.current.focus(), 30);
    }
  }, [open]);

  const items = useMemo(() => [
    { idx: "01", title: "Home: Index",                  meta: "nav", run: () => setRoute({ page: "home" }) },
    { idx: "02", title: "AVA Light",                     meta: "project", run: () => setRoute({ page: "ava" }) },
    { idx: "03", title: "Stratco Cambodia E&D Dept",     meta: "project", run: () => setRoute({ page: "stratco" }) },
    { idx: "04", title: "Robotic Hand (R&D)",            meta: "project", run: () => setRoute({ page: "hand" }) },
    { idx: "05", title: "About",                          meta: "nav", run: () => setRoute({ page: "about" }) },
    { idx: "06", title: "Contact",                        meta: "nav", run: () => setRoute({ page: "contact" }) },
    { idx: "07", title: "Email Olivier",                  meta: "action", run: () => { window.location.href = "mailto:olivierueno@gmail.com"; } },
    { idx: "08", title: "Open LinkedIn",                  meta: "action", run: () => window.open("https://www.linkedin.com/in/olivier-ueno/", "_blank") },
    { idx: "09", title: `Toggle theme · currently ${tweaks.dark ? "dark" : "light"}`, meta: "tweak", run: () => setTweak("dark", !tweaks.dark) },
    { idx: "10", title: "Accent: Slate",          meta: "tweak", run: () => setTweak("accent", "slate") },
    { idx: "11", title: "Accent: Steel blue",     meta: "tweak", run: () => setTweak("accent", "steel") },
    { idx: "12", title: "Accent: Oxide orange",   meta: "tweak", run: () => setTweak("accent", "oxide") },
    { idx: "13", title: "Accent: Graphite",       meta: "tweak", run: () => setTweak("accent", "graphite") },
    { idx: "14", title: "Home layout: Editorial", meta: "tweak", run: () => setTweak("homeLayout", "editorial") },
    { idx: "15", title: "Home layout: Technical", meta: "tweak", run: () => setTweak("homeLayout", "technical") },
    { idx: "16", title: "Home layout: Catalogue", meta: "tweak", run: () => setTweak("homeLayout", "catalogue") },
  ], [setRoute, setTweak, tweaks.dark]);

  const filtered = useMemo(() => {
    const s = q.trim().toLowerCase();
    if (!s) return items;
    return items.filter(i => i.title.toLowerCase().includes(s) || i.meta.includes(s));
  }, [q, items]);

  useEffect(() => { setActive(0); }, [q]);

  function onKey(e) {
    if (e.key === "Escape") { onClose(); }
    else if (e.key === "ArrowDown") { e.preventDefault(); setActive(a => Math.min(a + 1, filtered.length - 1)); }
    else if (e.key === "ArrowUp")   { e.preventDefault(); setActive(a => Math.max(a - 1, 0)); }
    else if (e.key === "Enter")     {
      const item = filtered[active];
      if (item) { item.run(); onClose(); }
    }
  }

  if (!open) return null;

  return (
    <div className="palette-backdrop" onClick={(e) => { if (e.target.classList.contains("palette-backdrop")) onClose(); }}>
      <div className="palette" onKeyDown={onKey}>
        <div className="palette-input-wrap">
          <span className="mono fg-faint" style={{ fontSize: 12 }}>›</span>
          <input ref={inputRef} placeholder="Jump to page, action, or tweak…" value={q} onChange={(e) => setQ(e.target.value)} />
          <span className="kbd">esc</span>
        </div>
        <div className="palette-list">
          {filtered.length === 0 ? (
            <div className="palette-row" style={{ cursor: "default", color: "var(--u-fg-muted)" }}>No matches.</div>
          ) : filtered.map((it, i) => (
            <div
              key={it.idx}
              className={`palette-row ${i === active ? "is-active" : ""}`}
              onMouseEnter={() => setActive(i)}
              onClick={() => { it.run(); onClose(); }}
            >
              <span className="idx">{it.idx}</span>
              <span>{it.title}</span>
              <span className="meta">{it.meta}</span>
            </div>
          ))}
        </div>
        <div className="palette-hint">
          <span>↑↓ navigate · ↵ select · esc close</span>
          <span>{filtered.length} results</span>
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   App
   ============================================================ */
function App() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [route, setRoute] = useState({ page: "home" });
  const [paletteOpen, setPaletteOpen] = useState(false);
  // Tracks whether the user has explicitly toggled the theme this session.
  // While false, the sunrise/sunset auto-toggle keeps overriding `tweaks.dark`.
  const manualOverrideRef = useRef(false);

  // Boot-time auto theme: honour a stored override, otherwise apply the
  // sunrise/sunset answer for the visitor's timezone.
  useEffect(() => {
    let stored = null;
    try { stored = localStorage.getItem(SUN_OVERRIDE_KEY); } catch (e) {}
    if (stored === "dark" || stored === "light") {
      manualOverrideRef.current = true;
      setTweak("dark", stored === "dark");
    } else {
      setTweak("dark", autoDarkForNow());
    }
  }, []);

  // Re-check sun position every 15 minutes; skip if user has overridden.
  useEffect(() => {
    const id = setInterval(() => {
      if (manualOverrideRef.current) return;
      const next = autoDarkForNow();
      setTweak("dark", next);
    }, 15 * 60 * 1000);
    return () => clearInterval(id);
  }, []);

  // Wrapped setter. when the user flips dark mode from the UI, store
  // their preference so we stop overriding it from the sun calculation.
  const setDarkManual = (v) => {
    manualOverrideRef.current = true;
    try { localStorage.setItem(SUN_OVERRIDE_KEY, v ? "dark" : "light"); } catch (e) {}
    setTweak("dark", v);
  };

  // Reset manual override (returns to sunrise/sunset auto-mode).
  const clearThemeOverride = () => {
    manualOverrideRef.current = false;
    try { localStorage.removeItem(SUN_OVERRIDE_KEY); } catch (e) {}
    setTweak("dark", autoDarkForNow());
  };

  // Apply tweaks as data-attrs on <html>
  useEffect(() => {
    const html = document.documentElement;
    html.setAttribute("data-theme", tweaks.dark ? "dark" : "light");
    html.setAttribute("data-accent", tweaks.accent);
    html.setAttribute("data-density", tweaks.density);
  }, [tweaks.dark, tweaks.accent, tweaks.density]);

  // Scroll-to-top on route change + emit a pageview to Vercel Analytics so
  // SPA navigation shows up as distinct pages (not just the initial load).
  useEffect(() => {
    window.scrollTo({ top: 0, behavior: "instant" in window ? "instant" : "auto" });
    if (typeof window.va === "function") {
      window.va("event", { name: "pageview", page: route.page });
    }
  }, [route.page]);

  // Cmd/Ctrl+K shortcut
  useEffect(() => {
    function onKey(e) {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
        e.preventDefault();
        setPaletteOpen(p => !p);
      } else if (e.key === "/" && !e.metaKey && !e.ctrlKey && document.activeElement.tagName !== "INPUT") {
        e.preventDefault();
        setPaletteOpen(true);
      }
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  let page;
  if (route.page === "home")     page = <Home setRoute={setRoute} layout={tweaks.homeLayout} thumbStyle={tweaks.thumbStyle} />;
  else if (route.page === "ava") page = <ProjectAva setRoute={setRoute} />;
  else if (route.page === "stratco") page = <ProjectStratco setRoute={setRoute} />;
  else if (route.page === "hand") page = <ProjectHand setRoute={setRoute} />;
  else if (route.page === "about") page = <About setRoute={setRoute} />;
  else if (route.page === "contact") page = <Contact setRoute={setRoute} />;
  else page = <Home setRoute={setRoute} layout={tweaks.homeLayout} thumbStyle={tweaks.thumbStyle} />;

  return (
    <>
      <Nav
        route={route}
        setRoute={setRoute}
        onOpenPalette={() => setPaletteOpen(true)}
        dark={tweaks.dark}
        onToggleTheme={() => setDarkManual(!tweaks.dark)}
        onResetTheme={clearThemeOverride}
        themeIsAuto={!manualOverrideRef.current}
      />
      {page}
      <Footer setRoute={setRoute} />

      <CommandPalette
        open={paletteOpen}
        onClose={() => setPaletteOpen(false)}
        setRoute={(r) => { setRoute(r); }}
        setTweak={setTweak}
        tweaks={tweaks}
      />

      <TweaksPanel title="Tweaks">
        <TweakSection label="Theme" />
        <TweakToggle label="Dark mode" value={tweaks.dark} onChange={(v) => setDarkManual(v)} />
        <TweakColor
          label="Accent"
          value={accentHex(tweaks.accent)}
          options={["#2c3e50", "#3a5a7c", "#b85c38", "#5a5448"]}
          onChange={(v) => setTweak("accent", accentName(v))}
        />

        <TweakSection label="Home" />
        <TweakRadio
          label="Layout"
          value={tweaks.homeLayout}
          options={["editorial", "technical", "catalogue"]}
          onChange={(v) => setTweak("homeLayout", v)}
        />
        <TweakRadio
          label="Project thumbnails"
          value={tweaks.thumbStyle}
          options={["image", "number", "text"]}
          onChange={(v) => setTweak("thumbStyle", v)}
        />

        <TweakSection label="Layout" />
        <TweakRadio
          label="Density"
          value={tweaks.density}
          options={["compact", "regular", "generous"]}
          onChange={(v) => setTweak("density", v)}
        />
      </TweaksPanel>
    </>
  );
}

function accentHex(name) {
  return { slate: "#2c3e50", steel: "#3a5a7c", oxide: "#b85c38", graphite: "#5a5448" }[name] || "#2c3e50";
}
function accentName(hex) {
  return { "#2c3e50": "slate", "#3a5a7c": "steel", "#b85c38": "oxide", "#5a5448": "graphite" }[hex] || "slate";
}

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