// App shell — routing, cart, search, menu, toast, and the Shopify boot.
// ----------------------------------------------------------------------------
// On mount we hit the Storefront API for the catalog, populate window.PRODUCTS
// + window.CATEGORIES, then render. The cart is persisted to localStorage so
// a refresh doesn't wipe it. "Zur Kasse" creates a real Shopify cart via the
// Cart API and redirects the customer to Shopify's hosted checkout.
//
// ROUTING: every page has a real URL (History API). next.config.ts serves
// index.html for every non-asset path, so deep links survive a reload and
// the back/forward buttons work. The URL is the single source of truth —
// `route` is derived from it on load, push/popstate, and product/recipe/
// story objects are resolved from the loaded catalog at render time.
//
//   /                     home
//   /shop                 collection (+ ?anlass=&geschmack=&art= filters)
//   /shop/:handle         product page
//   /rezepte[/:slug]      recipes / one recipe
//   /produzenten          producers
//   /stories[/:slug]      stories / one story
//   /ueber-uns …          static pages (see VIEW_PATHS)

const VIEW_PATHS = {
  home: "/",
  collection: "/shop",
  recipes: "/rezepte",
  produzenten: "/produzenten",
  stories: "/stories",
  about: "/ueber-uns",
  checkout: "/kasse",
  account: "/konto",
  faq: "/faq",
  versand: "/versand",
  kontakt: "/kontakt",
  geschaeftskunden: "/geschaeftskunden",
  haendler: "/haendler",
  nachhaltigkeit: "/nachhaltigkeit",
  impressum: "/impressum",
  datenschutz: "/datenschutz",
  agb: "/agb",
  cookies: "/cookies",
};
const PATH_VIEWS = Object.fromEntries(Object.entries(VIEW_PATHS).map(([v, p]) => [p, v]));

/** Parse a location (or path string) into a route object. */
function parseRoute(pathname, search) {
  let path = (pathname || "/").replace(/\/+$/, "") || "/";
  // Legacy alias from before the Stories rename.
  if (path === "/journal") path = "/stories";

  if (PATH_VIEWS[path]) {
    const route = { view: PATH_VIEWS[path] };
    if (route.view === "collection" && search) {
      const q = new URLSearchParams(search);
      const filter = {};
      for (const k of ["anlass", "geschmack", "art"]) {
        if (q.get(k)) filter[k] = q.get(k);
      }
      if (Object.keys(filter).length) route.filter = filter;
    }
    return route;
  }
  let m;
  if ((m = path.match(/^\/shop\/([^/]+)$/)))     return { view: "product", handle: decodeURIComponent(m[1]) };
  if ((m = path.match(/^\/rezepte\/([^/]+)$/)))  return { view: "recipe",  slug: decodeURIComponent(m[1]) };
  if ((m = path.match(/^\/stories\/([^/]+)$/)))  return { view: "story",   slug: decodeURIComponent(m[1]) };
  return { view: "home", unknown: true };
}

/** Build the URL for a view (+ optional collection filter / object payload). */
function buildPath(view, opts) {
  if (view === "collection") {
    const base = VIEW_PATHS.collection;
    if (!opts) return base;
    const q = new URLSearchParams();
    for (const k of ["anlass", "geschmack", "art"]) {
      if (opts[k] && opts[k] !== "Alle") q.set(k, opts[k]);
    }
    const qs = q.toString();
    return qs ? `${base}?${qs}` : base;
  }
  return VIEW_PATHS[view] || "/";
}

function App() {
  // ----- boot: load products from Shopify -------------------------------
  const [products, setProducts] = useState(null);
  const [bootError, setBootError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const data = await window.shopify.getProducts();
        if (cancelled) return;
        // Populate the window globals every component reads from.
        window.PRODUCTS = data;
        window.CATEGORIES = ["Alle", ...Array.from(new Set(data.map((p) => p.category))).filter(Boolean)];
        // Pull recipes, producers, stories, FAQ and the site copy from
        // their Shopify metaobjects in parallel. Any that Inga hasn't set
        // up yet (or that fail) keep the hardcoded fallbacks their
        // components already put on window.
        try {
          // Site copy: in DE (the shop default) the entries are always
          // authoritative. In EN, Shopify falls back to German for
          // untranslated entries — so fetch both languages and only let
          // entries override that are ACTUALLY translated (en ≠ de);
          // everything else keeps the built-in English from i18n.js.
          const copyPromise = (async () => {
            if (window.AG_LANG === "en") {
              const [de, en] = await Promise.all([
                window.shopify.getSiteCopy("de"),
                window.shopify.getSiteCopy("en"),
              ]);
              if (!en) return null;
              const translated = {};
              for (const k in en) {
                if (!de || en[k] !== de[k]) translated[k] = en[k];
              }
              return Object.keys(translated).length ? translated : null;
            }
            return window.shopify.getSiteCopy("de");
          })();

          const [recipes, producers, stories, copy, faq] = await Promise.all([
            window.shopify.getRecipes(),
            window.shopify.getProducers(),
            window.shopify.getStories(),
            copyPromise,
            window.shopify.getFaqItems(),
          ]);
          if (recipes   && recipes.length)   window.RECIPES   = recipes;
          if (producers && producers.length) window.PRODUCERS = producers;
          if (stories   && stories.length)   window.STORIES   = stories;
          if (copy)                          window.COPY      = copy;
          if (faq && faq.length)             window.FAQ_ITEMS = faq;
        } catch (_) { /* fallbacks already in place */ }
        setProducts(data);
      } catch (err) {
        console.error("Shopify load failed:", err);
        if (!cancelled) setBootError(err.message || "Verbindung zu Shopify fehlgeschlagen.");
      }
    })();
    return () => { cancelled = true; };
  }, []);

  // ----- routing + ephemeral UI state -----------------------------------
  // The URL is the source of truth. `_ts` forces dependent effects (e.g.
  // Collection's filter sync) to re-run even when the same link is clicked
  // twice in a row.
  const [route, setRoute] = useState(() => ({
    ...parseRoute(window.location.pathname, window.location.search),
    _ts: Date.now(),
  }));
  const view = route.view;

  // Unknown deep link → clean the URL to / (route already says "home").
  useEffect(() => {
    if (route.unknown) window.history.replaceState({}, "", "/");
  }, []);

  // Back/forward buttons re-parse the location.
  useEffect(() => {
    const onPop = () => {
      setRoute({ ...parseRoute(window.location.pathname, window.location.search), _ts: Date.now() });
      window.scrollTo({ top: 0 });
    };
    window.addEventListener("popstate", onPop);
    return () => window.removeEventListener("popstate", onPop);
  }, []);
  const [cartOpen, setCartOpen] = useState(false);
  const [searchOpen, setSearchOpen] = useState(false);
  const [menuOpen, setMenuOpen] = useState(false);
  const [bump, setBump] = useState(false);
  const [toast, setToast] = useState(null);
  const [checkoutLoading, setCheckoutLoading] = useState(false);
  const toastTimer = useRef(null);
  const bumpTimer = useRef(null);

  // ----- cart, persisted to localStorage --------------------------------
  const [cart, setCart] = useState(() => {
    try {
      const saved = localStorage.getItem("ag.cart");
      return saved ? JSON.parse(saved) : [];
    } catch { return []; }
  });
  useEffect(() => {
    try { localStorage.setItem("ag.cart", JSON.stringify(cart)); } catch {}
  }, [cart]);

  // After products come back from Shopify, refresh cart line snapshots
  // against the live catalog. Items added before the Shopify wiring
  // landed don't have a `variantId` and can't be checked out — drop
  // those silently. Items that still exist get their price / image /
  // variantId refreshed from the source of truth.
  useEffect(() => {
    if (!products) return;
    setCart((c) => {
      const refreshed = c
        .map((i) => {
          const fresh = products.find((p) => p.id === i.id);
          return fresh ? { ...fresh, qty: i.qty } : null;
        })
        .filter(Boolean);
      return refreshed;
    });
  }, [products]);

  // ----- helpers --------------------------------------------------------
  const showToast = (msg) => {
    setToast(msg);
    clearTimeout(toastTimer.current);
    toastTimer.current = setTimeout(() => setToast(null), 2200);
  };
  const triggerBump = () => {
    setBump(true);
    clearTimeout(bumpTimer.current);
    bumpTimer.current = setTimeout(() => setBump(false), 460);
  };

  // Central navigation: push the URL, derive the route from it.
  const go = (path) => {
    window.history.pushState({}, "", path);
    setRoute({ ...parseRoute(window.location.pathname, window.location.search), _ts: Date.now() });
    setMenuOpen(false); setSearchOpen(false);
    window.scrollTo({ top: 0 });
  };
  const nav = (v, opts) => go(buildPath(v, opts));
  const openProduct = (p) => go(`/shop/${encodeURIComponent(p.handle)}`);
  const openRecipe = (r) => go(`/rezepte/${encodeURIComponent(r.slug)}`);
  const openStory = (s) => go(`/stories/${encodeURIComponent(s.slug)}`);

  // Collection mirrors its filter state into the URL (replace, not push —
  // chip clicks shouldn't pile up in the back-button history).
  const onCollectionFilter = (f) => {
    window.history.replaceState({}, "", buildPath("collection", f));
  };

  // Resolve route payloads against the loaded catalog/content at render
  // time (everything is on window by the time the boot splash clears).
  const active = view === "product"
    ? (window.PRODUCTS || []).find((p) => p.handle === route.handle) : null;
  const activeRecipe = view === "recipe"
    ? ((window.RECIPES || []).find((r) => r.slug === route.slug) || null) : null;
  const activeStory = view === "story"
    ? ((window.STORIES || []).find((s) => s.slug === route.slug) || null) : null;
  const collectionFilter = view === "collection"
    ? { ...(route.filter || {}), _ts: route._ts } : null;

  // Per-page document titles (client-side; the future Next port makes
  // these server-rendered).
  useEffect(() => {
    const brand = "The Apéro Garden";
    let page = null;
    if (view === "product" && active) page = active.name;
    else if (view === "recipe" && activeRecipe) page = activeRecipe.name;
    else if (view === "story" && activeStory) page = activeStory.title;
    else if (view === "collection") page = t("nav.shop");
    else if (view === "recipes") page = t("footer.recipes");
    else if (view === "produzenten") page = t("nav.producers");
    else if (view === "stories") page = t("nav.stories");
    else if (view === "about") page = t("nav.about");
    else if (view !== "home" && VIEW_PATHS[view]) page = view.charAt(0).toUpperCase() + view.slice(1);
    document.title = page ? `${page} – ${brand}` : `${brand} – ${t("hero.eyebrow")}`;
  }, [view, route._ts, active, activeRecipe, activeStory]);

  const addToCart = (p, qty = 1, sourceEl = null) => {
    setCart((c) => {
      const found = c.find((i) => i.id === p.id);
      if (found) return c.map((i) => i.id === p.id ? { ...i, qty: i.qty + qty } : i);
      return [...c, { ...p, qty }];
    });
    triggerBump();
    // Pure-visual delight: the product photo arcs toward the cart icon.
    // No-ops if no sourceEl was passed or the user prefers reduced motion.
    if (sourceEl) launchFlyToCart(p, sourceEl);
    showToast(`${p.name} ${t("toast.added")}`);
  };
  const setQty = (id, q) => {
    if (q <= 0) return setCart((c) => c.filter((i) => i.id !== id));
    setCart((c) => c.map((i) => i.id === id ? { ...i, qty: q } : i));
  };
  const removeItem = (id) => setCart((c) => c.filter((i) => i.id !== id));

  /**
   * Build a Shopify cart from our local lines and send the customer to
   * Shopify's hosted checkout. We keep the local cart untouched — if the
   * customer abandons the checkout and comes back, their cart is still here.
   */
  const goCheckout = async () => {
    if (!cart.length || checkoutLoading) return;
    console.log("[checkout] cart items:", cart);
    setCheckoutLoading(true);
    try {
      const shopCart = await window.shopify.createCheckout(cart);
      console.log("[checkout] cart created, redirecting:", shopCart.checkoutUrl);
      setCartOpen(false);
      window.location.href = shopCart.checkoutUrl;
    } catch (err) {
      console.error("[checkout] failed:", err);
      // Show the actual error message so we can see what's wrong instead of
      // a generic 'try again' that hides the cause.
      showToast(`Kasse: ${err.message || "Verbindung zu Shopify fehlgeschlagen"}`);
      setCheckoutLoading(false);
    }
  };

  const cartCount = cart.reduce((s, i) => s + i.qty, 0);

  // ----- boot states ----------------------------------------------------
  if (bootError) {
    return (
      <div className="boot-splash boot-splash--error">
        <Logo style={{ height: 30, color: "var(--brand-deep)" }} />
        <h2>{t("boot.error.h2")}</h2>
        <p>{bootError}</p>
        <Button variant="primary" size="md" onClick={() => window.location.reload()}>{t("boot.retry")}</Button>
      </div>
    );
  }
  if (!products) {
    return (
      <div className="boot-splash">
        <Logo style={{ height: 30, color: "var(--brand-deep)" }} />
        <p>{t("boot.loading")}</p>
      </div>
    );
  }

  // ----- normal render --------------------------------------------------
  return (
    <div className="app">
      <Header onNav={nav} view={view} cartCount={cartCount} cartBump={bump}
        onCartOpen={() => setCartOpen(true)} onMenu={() => setMenuOpen(true)}
        onSearch={() => setSearchOpen(true)} onAccount={() => nav("account")} />
      <main>
        {view === "home" && <Home onNav={nav} onOpen={openProduct} onAdd={addToCart} onOpenRecipe={openRecipe} />}
        {view === "recipes" && <RecipesIndex onOpenRecipe={openRecipe} />}
        {view === "recipe" && activeRecipe && <RecipePage recipe={activeRecipe} onNav={nav} onOpen={openProduct} onAdd={addToCart} />}
        {view === "collection" && <Collection onOpen={openProduct} onAdd={addToCart} filter={collectionFilter} onFilterChange={onCollectionFilter} />}
        {view === "product" && active && <ProductPage p={active} onNav={nav} onOpen={openProduct} onAdd={addToCart} />}
        {view === "produzenten" && <Producers onNav={nav} onOpen={openProduct} />}
        {view === "stories" && <Stories onOpenRecipe={openRecipe} onOpenStory={openStory} />}
        {view === "story" && activeStory && <StoryPage story={activeStory} onNav={nav} />}
        {/* Dead deep link (mistyped handle, unpublished product, deleted
            recipe/story): friendly note + a way back instead of a blank. */}
        {((view === "product" && !active) || (view === "recipe" && !activeRecipe) || (view === "story" && !activeStory)) && (
          <div className="boot-splash" style={{ minHeight: "50vh" }}>
            <h2>{t("notfound.h2")}</h2>
            <Button variant="primary" size="md" onClick={() => nav(view === "product" ? "collection" : view === "recipe" ? "recipes" : "stories")}>
              {t("notfound.cta")}
            </Button>
          </div>
        )}
        {view === "about" && <About onNav={nav} />}
        {view === "checkout" && <Checkout cart={cart} onNav={nav} onPlaced={() => setCart([])} />}
        {view === "account" && <Account onNav={nav} onOpen={openProduct} />}
        {/* Static info pages — see StaticPages.jsx */}
        {view === "faq" && <Faq />}
        {view === "versand" && <Versand onNav={nav} />}
        {view === "kontakt" && <Kontakt />}
        {view === "geschaeftskunden" && <Geschaeftskunden />}
        {view === "haendler" && <Haendler />}
        {view === "nachhaltigkeit" && <Nachhaltigkeit />}
        {view === "impressum" && <Impressum />}
        {view === "datenschutz" && <Datenschutz />}
        {view === "agb" && <Agb />}
        {view === "cookies" && <Cookies />}
      </main>
      <Footer onNav={nav} />
      <CartDrawer open={cartOpen} onClose={() => setCartOpen(false)} cart={cart} onQty={setQty} onRemove={removeItem} onCheckout={goCheckout} onAdd={addToCart} checkoutLoading={checkoutLoading} />
      <SearchOverlay open={searchOpen} onClose={() => setSearchOpen(false)} onOpen={openProduct} onAdd={addToCart} />
      <MobileMenu open={menuOpen} onClose={() => setMenuOpen(false)} onNav={nav} onSearch={() => setSearchOpen(true)} />
      <div className={`toast${toast ? " show" : ""}`}>
        <Icon name="check" size={18} /> {toast}
      </div>
    </div>
  );
}

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