// Nodestone — main App, routing, theme, tweaks.

(function () {
const { useState, useEffect, useMemo } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "dark-pastel",
  "themeMode": "auto",
  "typeface": "general-sans",
  "navStyle": "mega",
  "heroVariant": "node",
  "density": "compact",
  "animation": "on"
}/*EDITMODE-END*/;

// Pastel palette presets (light + dark CSS variable sets)
const PALETTES = {
  "dark-pastel": {
    label: "Dark · muted",
    light: {
      "--pastel-1": "#FFE9D6", "--pastel-2": "#E8F0E1", "--pastel-3": "#E6E1F5",
      "--pastel-4": "#FFF6CC", "--pastel-5": "#E0EEFF", "--pastel-6": "#FFE4E1",
    },
    dark: {
      "--pastel-1": "#1D2129", "--pastel-2": "#1E2622", "--pastel-3": "#252029",
      "--pastel-4": "#2A2716", "--pastel-5": "#1B2429", "--pastel-6": "#2A1F23",
    },
  },
  "warm": {
    label: "Warm · peach + sage",
    light: {
      "--pastel-1": "#FFE9D6", "--pastel-2": "#E8F0E1", "--pastel-3": "#F5E8DD",
      "--pastel-4": "#FFF6CC", "--pastel-5": "#F0E6DC", "--pastel-6": "#FFE4E1",
    },
    dark: {
      "--pastel-1": "#2D2418", "--pastel-2": "#1F2618", "--pastel-3": "#2A211A",
      "--pastel-4": "#2A2510", "--pastel-5": "#26201A", "--pastel-6": "#2B1F1C",
    },
  },
  "candy": {
    label: "Candy · rose + sky",
    light: {
      "--pastel-1": "#FFE4E1", "--pastel-2": "#E0EEFF", "--pastel-3": "#FFF6CC",
      "--pastel-4": "#E6E1F5", "--pastel-5": "#FFE9D6", "--pastel-6": "#E8F0E1",
    },
    dark: {
      "--pastel-1": "#2C1E22", "--pastel-2": "#15212A", "--pastel-3": "#2A2615",
      "--pastel-4": "#1F1A2A", "--pastel-5": "#2A2018", "--pastel-6": "#1A2620",
    },
  },
  "earth": {
    label: "Earth · stone + clay",
    light: {
      "--pastel-1": "#E3DCC8", "--pastel-2": "#D8E4D0", "--pastel-3": "#C9D6DE",
      "--pastel-4": "#E8DDCB", "--pastel-5": "#D6CFC2", "--pastel-6": "#DBD4C8",
    },
    dark: {
      "--pastel-1": "#22201A", "--pastel-2": "#1A211A", "--pastel-3": "#181E22",
      "--pastel-4": "#211C14", "--pastel-5": "#1D1B16", "--pastel-6": "#1F1D17",
    },
  },
};

function normalizeRoutePath(path) {
  let next = String(path || "/");
  if (next.startsWith("#/")) next = next.slice(1);
  if (!next.startsWith("/")) next = "/" + next;
  next = next.replace(/\/+$/, "") || "/";
  return next;
}

if (window.location.hash.startsWith("#/")) {
  const hashPath = window.location.hash.slice(1);
  const [cleanHashPath, hashQuery = ""] = hashPath.split("?");
  const cleanPath = normalizeRoutePath(cleanHashPath);
  const cleanSearch = window.location.search || (hashQuery ? "?" + hashQuery : "");
  window.history.replaceState({}, "", cleanPath + cleanSearch);
}

window.NodestoneNavigate = function navigate(path, options = {}) {
  const nextPath = normalizeRoutePath(path);
  const currentPath = normalizeRoutePath(window.location.pathname);
  if (nextPath === currentPath && !window.location.hash) {
    window.scrollTo({ top: 0, behavior: "auto" });
    return;
  }

  const method = options.replace ? "replaceState" : "pushState";
  window.history[method]({}, "", nextPath);
  window.dispatchEvent(new Event("popstate"));
};

function usePathRoute() {
  const [route, setRoute] = useState(() => normalizeRoutePath(window.location.pathname));
  useEffect(() => {
    const onPopState = () => {
      setRoute(normalizeRoutePath(window.location.pathname));
      window.scrollTo({ top: 0, behavior: "auto" });
    };
    window.addEventListener("popstate", onPopState);
    return () => window.removeEventListener("popstate", onPopState);
  }, []);
  return route;
}

function getRouteMeta(route) {
  const N = window.NODESTONE || {};
  const r = normalizeRoutePath(route);
  const baseDescription = "Nodestone is a UK IT consultancy specialising in cloud, infrastructure, automation, applications, integrations and software delivery.";

  if (r === "/") {
    return {
      title: "Nodestone | IT consultancy",
      description: baseDescription,
    };
  }
  if (r === "/services") {
    return {
      title: "Services | Nodestone",
      description: "Cloud, platform engineering, automation, application development, SaaS product development and systems integration from Nodestone.",
    };
  }
  if (r.startsWith("/services/")) {
    const slug = r.replace("/services/", "");
    const service = (N.services || []).find((item) => item.slug === slug);
    return {
      title: (service ? service.label : "Service") + " | Nodestone",
      description: service ? service.short : baseDescription,
    };
  }
  if (r === "/about") {
    return {
      title: "Who we are | Nodestone",
      description: "Independent UK technology practitioners working across platforms, cloud, applications and delivery.",
    };
  }
  if (r === "/workshop") {
    return {
      title: "Workshop | Nodestone",
      description: "Client work, internal products and practical experiments from Nodestone.",
    };
  }
  if (r === "/contact") {
    return {
      title: "Contact | Nodestone",
      description: "Contact Nodestone about cloud, infrastructure, automation, applications, integrations or software delivery work.",
    };
  }
  if (r === "/privacy") {
    return {
      title: "Privacy | Nodestone",
      description: "How Nodestone handles personal data from website visitors, analytics and contact enquiries.",
    };
  }
  if (r === "/cookies") {
    return {
      title: "Cookies | Nodestone",
      description: "How Nodestone uses essential storage and optional Google Analytics measurement on this website.",
    };
  }
  if (r === "/terms-and-conditions") {
    return {
      title: "Terms and Conditions | Nodestone",
      description: "Website and service terms for Nodestone Labs Ltd trading as Nodestone.",
    };
  }
  return {
    title: "Page not found | Nodestone",
    description: baseDescription,
  };
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const route = usePathRoute();

  // resolved theme (auto → matches OS)
  const [systemDark, setSystemDark] = useState(() =>
    window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
  );
  useEffect(() => {
    const mql = window.matchMedia("(prefers-color-scheme: dark)");
    const fn = (e) => setSystemDark(e.matches);
    mql.addEventListener?.("change", fn);
    return () => mql.removeEventListener?.("change", fn);
  }, []);
  const resolvedTheme =
    t.themeMode === "auto" ? (systemDark ? "dark" : "light") :
    t.themeMode === "dark" ? "dark" : "light";

  // Apply theme + density + typeface + anim attrs to <html>
  useEffect(() => {
    const root = document.documentElement;
    root.setAttribute("data-theme", resolvedTheme);
    root.setAttribute("data-density", t.density);
    root.setAttribute("data-typeface", t.typeface);
    root.setAttribute("data-anim", t.animation === "off" ? "off" : "on");
  }, [resolvedTheme, t.density, t.typeface, t.animation]);

  // Apply selected palette to CSS variables
  useEffect(() => {
    const p = PALETTES[t.palette] || PALETTES["dark-pastel"];
    const vars = resolvedTheme === "dark" ? p.dark : p.light;
    const root = document.documentElement;
    for (const k in vars) root.style.setProperty(k, vars[k]);
  }, [t.palette, resolvedTheme]);

  useEffect(() => {
    const meta = getRouteMeta(route);
    document.title = meta.title;
    const description = document.querySelector('meta[name="description"]');
    if (description) description.setAttribute("content", meta.description);
    window.NodestoneAnalytics?.trackPageView?.(route, meta.title);
  }, [route]);

  // Pick page from route
  const page = useMemo(() => {
    const r = route || "/";
    if (r === "/" || r === "") return <HomePage heroVariant={t.heroVariant} />;
    if (r === "/services") return <ServicesPage />;
    if (r.startsWith("/services/")) return <ServiceDetailPage slug={r.replace("/services/", "")} />;
    if (r === "/about") return <AboutPage />;
    if (r === "/workshop") return <ProductsPage />;
    if (r === "/contact") return <ContactPage />;
    if (r === "/privacy") return <PrivacyPage />;
    if (r === "/cookies") return <CookiesPage />;
    if (r === "/terms-and-conditions") return <TermsPage />;
    return (
      <section className="page-hero">
        <div className="wrap">
          <div className="eyebrow" style={{ marginBottom: 24 }}>404</div>
          <h1>Nothing at that path.</h1>
          <p className="lede">Try the <a href="/" onClick={(e) => { e.preventDefault(); window.NodestoneNavigate("/"); }} style={{ borderBottom: "1px solid var(--line-strong)" }}>front page</a>.</p>
        </div>
      </section>
    );
  }, [route, t.heroVariant]);

  const toggleTheme = () => {
    if (t.themeMode === "auto") setTweak("themeMode", resolvedTheme === "dark" ? "light" : "dark");
    else setTweak("themeMode", t.themeMode === "dark" ? "light" : "dark");
  };

  return (
    <>
      {/* live-feeling ambient gradient mesh */}
      <div className="mesh" aria-hidden="true">
        <div className="mesh-blob" />
        <div className="mesh-blob" />
        <div className="mesh-blob" />
      </div>

      <Nav route={route} onNavigate={() => {}} navStyle={t.navStyle}
           theme={resolvedTheme} onToggleTheme={toggleTheme} />

      <main style={{ position: "relative", zIndex: 1 }}>
        {page}
      </main>

      <Footer />

      <CookieConsentBanner />

      <TweaksPanel>
        <TweakSection label="Theme" />
        <TweakRadio
          label="Mode"
          value={t.themeMode}
          options={["auto", "light", "dark"]}
          onChange={(v) => setTweak("themeMode", v)}
        />
        <TweakSelect
          label="Palette"
          value={t.palette}
          options={Object.keys(PALETTES).map((k) => ({ value: k, label: PALETTES[k].label }))}
          onChange={(v) => setTweak("palette", v)}
        />

        <TweakSection label="Typography" />
        <TweakSelect
          label="Type pairing"
          value={t.typeface}
          options={[
            { value: "general-sans", label: "General Sans + Mono" },
            { value: "satoshi",      label: "Satoshi + Mono" },
            { value: "serif",        label: "Serif headers + Sans" },
          ]}
          onChange={(v) => setTweak("typeface", v)}
        />

        <TweakSection label="Layout" />
        <TweakRadio
          label="Density"
          value={t.density}
          options={["compact", "regular", "comfy"]}
          onChange={(v) => setTweak("density", v)}
        />
        <TweakRadio
          label="Nav style"
          value={t.navStyle}
          options={["mega", "simple"]}
          onChange={(v) => setTweak("navStyle", v)}
        />

        <TweakSection label="Hero" />
        <TweakSelect
          label="Variant"
          value={t.heroVariant}
          options={[
            { value: "node",    label: "Animated node graph" },
            { value: "code",    label: "Live terminal/code" },
            { value: "minimal", label: "Minimal · type only" },
          ]}
          onChange={(v) => setTweak("heroVariant", v)}
        />

        <TweakSection label="Motion" />
        <TweakRadio
          label="Animation"
          value={t.animation}
          options={["on", "off"]}
          onChange={(v) => setTweak("animation", v)}
        />
      </TweaksPanel>
    </>
  );
}

function CookieConsentBanner() {
  const analytics = window.NodestoneAnalytics;
  const [visible, setVisible] = useState(() => {
    const consent = analytics?.getConsent?.();
    return !consent || consent.decided !== true;
  });
  const [mode, setMode] = useState("notice");
  const [analyticsEnabled, setAnalyticsEnabled] = useState(() => {
    const consent = analytics?.getConsent?.();
    return consent?.analytics === true;
  });

  useEffect(() => {
    window.NodestoneOpenCookiePreferences = function openCookiePreferences() {
      const consent = analytics?.getConsent?.();
      setAnalyticsEnabled(consent?.analytics === true);
      setMode("manage");
      setVisible(true);
    };

    const onConsentChange = (event) => {
      setAnalyticsEnabled(event.detail?.analytics === true);
    };

    window.addEventListener("nodestone:consentchange", onConsentChange);
    return () => {
      window.removeEventListener("nodestone:consentchange", onConsentChange);
      if (window.NodestoneOpenCookiePreferences) delete window.NodestoneOpenCookiePreferences;
    };
  }, [analytics]);

  if (!visible) return null;

  const save = (allowAnalytics) => {
    analytics?.saveConsent?.({ analytics: allowAnalytics, marketing: false });
    setAnalyticsEnabled(allowAnalytics);
    setMode("notice");
    setVisible(false);
  };

  const go = (event, path) => {
    event.preventDefault();
    window.NodestoneNavigate(path);
    setVisible(false);
  };

  return (
    <section className="cookie-consent" aria-label="Cookie preferences">
      <div className="cookie-consent-copy">
        <div className="eyebrow">Cookies</div>
        <h2>Analytics only if you say so.</h2>
        <p>
          We use essential storage for the site to work. Google Analytics is optional and stays off until you accept it.
          Read the <a href="/cookies" onClick={(e) => go(e, "/cookies")}>cookie notice</a> or <a href="/privacy" onClick={(e) => go(e, "/privacy")}>privacy notice</a>.
        </p>
      </div>

      {mode === "manage" && (
        <div className="cookie-preferences">
          <div className="cookie-pref-row">
            <div>
              <h3>Essential</h3>
              <p>Required for contact security, consent choices and basic site behaviour.</p>
            </div>
            <span className="cookie-required">Always on</span>
          </div>
          <label className="cookie-pref-row cookie-pref-toggle">
            <span>
              <h3>Analytics</h3>
              <p>Allows Google Analytics 4 (GA4) to measure page views, route changes, outbound clicks, file downloads and contact-form interaction events.</p>
            </span>
            <input
              type="checkbox"
              checked={analyticsEnabled}
              onChange={(event) => setAnalyticsEnabled(event.target.checked)}
            />
          </label>
        </div>
      )}

      <div className="cookie-consent-actions">
        {mode === "manage" ? (
          <>
            <button type="button" className="btn btn-primary" onClick={() => save(analyticsEnabled)}>Save preferences</button>
            <button type="button" className="btn btn-ghost" onClick={() => save(false)}>Reject all</button>
          </>
        ) : (
          <>
            <button type="button" className="btn btn-primary" onClick={() => save(true)}>Accept all</button>
            <button type="button" className="btn btn-ghost" onClick={() => save(false)}>Reject all</button>
            <button type="button" className="btn btn-ghost" onClick={() => setMode("manage")}>Manage preferences</button>
          </>
        )}
      </div>
    </section>
  );
}

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