"use client";

// Registers whichever theme this build compiled in. It has to be imported
// from a client component: the server and the browser get separate module
// instances, and this is the side that draws.
import "@/lib/theme-active";
import { slot, showTheme } from "@/lib/theme-registry";

import Link from "next/link";

import { LiveToasts } from "./LiveToasts";
import { SiteBanner } from "./SiteBanner";
import { SiteFooter } from "./SiteFooter";
import { useIsPathBlocked } from "@/lib/modules";
import { useSite } from "@/lib/site-context";
import { ThemeApplier } from "./ThemeApplier";

import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { useAuth } from "@/lib/auth-context";
import { Sidebar } from "./Sidebar";
import { MobileNav } from "./MobileNav";
import { MobileTopBar } from "./MobileTopBar";
import { CreatePostModal } from "./CreatePostModal";
import { PostActionGradients } from "./PostActionIcons";
import { DevToolsGuard } from "./DevToolsGuard";
import { CreatePostStateProvider } from "@/lib/create-post-context";
import { BadgeProvider } from "@/lib/badge-context";

const PUBLIC_PATHS = ["/login", "/signup", "/recover", "/install"];

export function AppShell({ children }: { children: React.ReactNode }) {
  const { siteName } = useSite();
  const { user, loading } = useAuth();
  const pathname = usePathname();
  const router = useRouter();
  const isPublicPath = PUBLIC_PATHS.includes(pathname);
  const isAdminPath = pathname === "/admin" || pathname.startsWith("/admin/");
  // Typing the URL of a switched-off section shouldn't get you in either.
  const blocked = useIsPathBlocked(pathname);
  const { siteOnline, shutdownMessage, publicFeed } = useSite();
  const isMessagesPath = pathname === "/messages" || pathname.startsWith("/messages/");
  const isMarketPath = pathname === "/marketplace" || pathname.startsWith("/marketplace/");
  // Reels is black edge to edge. Capped at the shared 1267px it left a white
  // strip down each side of the player on a wide screen.
  const isReelsPath = pathname === "/reels" || pathname.startsWith("/reels/");
  const isWidePath = isMessagesPath || isMarketPath || isReelsPath;

  const [composerOpen, setComposerOpen] = useState(false);

  /** True once the browser has taken over from the server's HTML. */
  const [mounted, setMounted] = useState(false);

  /** Whether this site still has to be set up. */
  const [needsInstall, setNeedsInstall] = useState(false);
  const [installChecked, setInstallChecked] = useState(false);

  // Asked once. A fresh site sends everyone to the wizard; an existing one
  // was marked installed when its database loaded, so nobody who already
  // has a working site is sent through a setup they've done.
  useEffect(() => {
    fetch("/api/install/status")
      .then((r) => (r.ok ? r.json() : null))
      .then((d) => setNeedsInstall(d?.installed === false))
      .catch(() => {
        // Unreachable: assume it's installed rather than blocking the site.
      })
      .finally(() => setInstallChecked(true));
  }, []);

  useEffect(() => {
    if (needsInstall && pathname !== "/install") {
      router.replace("/install");
    }
  }, [needsInstall, pathname, router]);

  useEffect(() => setMounted(true), []);

  /** Re-rendered when someone switches, so the change is immediate. */
  const [themeTick, setThemeTick] = useState(0);

  // Whichever theme this reader chose. Signed out there's nobody to
  // remember, so the site's default is what shows.
  useEffect(() => {
    const apply = () =>
      fetch("/api/themes/available")
        .then((r) => (r.ok ? r.json() : null))
        .then((d) => {
          showTheme(d?.active ?? null);
          setThemeTick((n) => n + 1);
        })
        .catch(() => {});

    apply();

    // Switching announces itself, so every part of the page follows.
    const onChanged = () => setThemeTick((n) => n + 1);
    window.addEventListener("xr-theme-changed", onChanged);
    return () => window.removeEventListener("xr-theme-changed", onChanged);
  }, [user?.id]);

  void themeTick;
  const [postsVersion, setPostsVersion] = useState(0);
  /** A theme's frame, if it brought one. */
  const ThemeShell = slot("Shell", null as never);

  useEffect(() => {
    if (loading) return;
    // Nothing else redirects while setup is unfinished: /install is a
    // public path, so a signed-in admin was being sent home by this and
    // sent back by the install check, over and over.
    if (needsInstall || !installChecked) return;

    if (!user && !isPublicPath) router.replace("/login");
    if (user && isPublicPath) router.replace("/");
  }, [loading, user, isPublicPath, router, needsInstall, installChecked]);

  // The server can't know whether anyone is signed in, so it and the
  // browser's first render must draw the same nothing — otherwise React
  // finds a mismatch and throws the whole tree away. Waiting for mount
  // makes them identical by construction rather than by hoping.
  // The wizard stands on its own — no menu, no session, nothing that
  // assumes a site that hasn't been set up yet. Below every hook, because
  // React counts them and refuses a number that changes.
  if (pathname === "/install") {
    return <>{children}</>;
  }

  if (!mounted || loading || !installChecked) {
    return <div className="w-full h-screen" />;
  }


  // A theme's frame, if it brought one — but not around login or signup,
  // which have their own page and shouldn't sit inside a menu nobody can
  // use yet.

  if (isPublicPath) {
    // Auth pages render full-width, no sidebar
    return <div className="flex-1">{children}</div>;
  }

  if (!user) {
    // About to redirect — render nothing to avoid a flash of protected content
    return null;
  }

  // With the feed closed to visitors, signing in comes first. Open, and a
  // visitor can read without an account.
  if (publicFeed === false && !user && !PUBLIC_PATHS.includes(pathname)) {
    return (
      <div className="mod-off">
        <h1>{siteName}</h1>
        <p>Sign in to see what people are posting.</p>
        <Link href="/login" className="mod-home">
          Sign in
        </Link>
      </div>
    );
  }

  // Maintenance mode. Admins keep working so they can put it back.
  if (siteOnline === false && !user?.admin && !PUBLIC_PATHS.includes(pathname)) {
    return (
      <div className="mod-off">
        <h1>{siteName} is offline</h1>
        <p>{shutdownMessage || "We're doing some maintenance and will be back shortly."}</p>
      </div>
    );
  }

  if (blocked && !isAdminPath) {
    return (
      <div className="mod-off">
        <h1>Not available</h1>
        <p>This section has been turned off.</p>
        <Link href="/" className="mod-home">
          Back to the feed
        </Link>
      </div>
    );
  }

  if (isAdminPath) {
    // The admin section has its own layout (app/admin/layout.tsx) with its
    // own nav and gating — the normal Sidebar/MobileNav/composer chrome
    // would just double up here, so skip straight to the page content.
    return <>{children}</>;
  }

  const hasOwnBottomBar = /^\/post\/[^/]+$/.test(pathname) || /^\/messages\/[^/]+$/.test(pathname);

  return (
    <CreatePostStateProvider
      isOpen={composerOpen}
      setIsOpen={setComposerOpen}
      postsVersion={postsVersion}
    >
      {/* The gradients the action icons paint themselves with. Outside the
          branch on purpose: they used to live only in the unthemed one, so
          under a theme url(#saveGrad) matched nothing and a saved bookmark
          rendered with no fill and no stroke — the icon vanished on click. */}
      <PostActionGradients />

      {/* A theme's frame goes inside the providers, not instead of them:
          it replaces the layout, and the machinery underneath stays. */}
      {ThemeShell && !isPublicPath ? (
        <BadgeProvider>
          <DevToolsGuard />
          <SiteBanner />
          <ThemeShell>{children}</ThemeShell>
          {/* The composer, so a theme's "what's on your mind" opens
              something rather than nothing. */}
          <CreatePostModal
            open={composerOpen}
            onClose={() => setComposerOpen(false)}
            onPosted={() => setPostsVersion((v) => v + 1)}
          />
        </BadgeProvider>
      ) : (
        <>
      <DevToolsGuard />
      <BadgeProvider>
        <SiteBanner />
        <ThemeApplier />
        {/* The whole 3-column layout (sidebar + content) is capped and
            centered as one block — without this, the sidebar sits flush
            against the raw viewport edge on wide screens while the content
            area centers independently, so the two edges never match up and
            the whole page feels unbalanced instead of a single organized
            block with even gaps on both sides. */}
        <div
          className={
            // Messages is a full-bleed three-pane view, so it opts out of the
            // shared max-width that keeps the rest of the site centred.
            isWidePath
              ? "w-full flex"
              : "app-shell flex"
          }
        >
          <Sidebar />
          <div className="flex-1 flex flex-col min-w-0">
            <MobileTopBar />
            <div className={`flex-1 flex justify-center ${hasOwnBottomBar ? "" : "pb-16 md:pb-0"}`}>
              {children}
            </div>
          </div>
        </div>
        <MobileNav />
        <LiveToasts />
      </BadgeProvider>
      <CreatePostModal
        open={composerOpen}
        onClose={() => setComposerOpen(false)}
        onPosted={() => setPostsVersion((v) => v + 1)}
      />
        </>
      )}
    </CreatePostStateProvider>
  );
}
