import React, { Suspense } from "react";
import { createRoot } from "react-dom/client";
import {
  BrowserRouter,
  Routes,
  Route,
  useLocation,
  useNavigate,
  useSearchParams,
} from "react-router-dom";
import { HelmetProvider } from "react-helmet-async";
import { Toaster } from "sonner";
import { Icon } from "@iconify/react";
import dynamic from "next/dynamic"; // shim -> React.lazy (+ Suspense)
import { AppWebSocketProvider } from "@/context/WebSocketContext";
import { useDashboardStore } from "@/stores/dashboard";
import { restoreLayoutFromStorage } from "@/stores/layout";
import { applyTheme } from "@/utils/theme";
import { gate } from "@/utils/gate";
import OnboardingGate from "@/components/pages/onboarding/OnboardingGate";
import ConsentGate from "@/components/pages/legal/ConsentGate";
import ImpersonationBanner from "@/components/layouts/shared/ImpersonationBanner/ImpersonationBanner";
// Wires installed modules' UI/hooks into the core feature registry (mirrors _app).
import "@/featureModules";
import "@/i18n";
import "@/styles/globals.css";

const GoogleAnalytics = dynamic(
  () => import("@/components/elements/addons/GoogleAnalytics"),
  { ssr: false }
);
const FacebookPixel = dynamic(
  () => import("@/components/elements/addons/FacebookPixel"),
  { ssr: false }
);
const GoogleTranslate = dynamic(
  () => import("@/components/elements/addons/GoogleTranslate"),
  { ssr: false }
);

// ---- config mirrored from middleware.ts -------------------------------------
const DEFAULT_USER_PATH = process.env.NEXT_PUBLIC_DEFAULT_USER_PATH || "/user";
const FRONTEND_ENABLED = process.env.NEXT_PUBLIC_FRONTEND === "true";
const AUTH_PAGES = ["/login", "/register", "/forgot", "/reset"];
const PROTECTED_PREFIXES = ["/user", "/admin", "/cfd", "/trade"];

const isProtected = (p: string) =>
  PROTECTED_PREFIXES.some((x) => p === x || p.startsWith(`${x}/`));

// gate.ts is keyed by the Next file path (".../index" / "[param]"); the live
// pathname has neither, so try both forms, then fall back to the blanket admin
// permission (same intent as middleware.ts hasPermission).
function requiredPermission(p: string): string | undefined {
  const g = gate as Record<string, string>;
  let req = g[p] || g[`${p}/index`];
  if (!req && p.startsWith("/admin")) req = "Access Admin Dashboard";
  return req;
}

function hasPerm(profile: any, req: string): boolean {
  if (!profile) return false;
  if (profile.role?.name === "Super Admin") return true;
  const perms = profile.role?.permissions || [];
  // role.permissions is typed string[] but checkPermission reads rp.name —
  // tolerate both shapes.
  return perms.some((rp: any) => (typeof rp === "string" ? rp : rp?.name) === req);
}

// Returns the path to redirect to, or null to stay. Returns null until the
// profile fetch resolves so we never decide auth on a half-loaded session.
function computeAuthRedirect(
  path: string,
  profile: any,
  profileResolved: boolean,
  search: URLSearchParams
): string | null {
  if (!profileResolved) return null;
  const authed = !!profile;

  if (!FRONTEND_ENABLED && path === "/")
    return authed ? DEFAULT_USER_PATH : "/login";

  if (authed && AUTH_PAGES.includes(path))
    return search.get("return") || DEFAULT_USER_PATH;

  if (!authed && isProtected(path))
    return `/login?return=${encodeURIComponent(path)}`;

  if (authed) {
    const req = requiredPermission(path);
    if (req && !hasPerm(profile, req)) return DEFAULT_USER_PATH;
  }
  return null;
}

// ---- diagnostics ------------------------------------------------------------
function showError(label: string, e: any) {
  const root = document.getElementById("root");
  if (!root) return;
  root.textContent = "";
  const pre = document.createElement("pre");
  pre.style.cssText =
    "padding:16px;color:#f55;white-space:pre-wrap;font:12px monospace";
  pre.textContent = label + ": " + ((e && (e.stack || e.message)) || String(e));
  root.appendChild(pre);
}
// Only blank the page on boot errors in dev — in prod a stray async error from a
// third-party script shouldn't wipe the app; the React ErrorBoundary handles
// render errors regardless.
if (import.meta.env.DEV) {
  window.addEventListener("error", (ev) => showError("WINDOW_ERROR", ev.error || ev.message));
  window.addEventListener("unhandledrejection", (ev: any) => showError("REJECTION", ev.reason));
}

class ErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { err: any; info: any }
> {
  state = { err: null as any, info: null as any };
  static getDerivedStateFromError(err: any) {
    return { err, info: null };
  }
  componentDidCatch(err: any, info: any) {
    this.setState({ err, info });
  }
  render() {
    if (this.state.err)
      return (
        <pre style={{ padding: 16, color: "#f55", whiteSpace: "pre-wrap", font: "12px monospace" }}>
          RENDER_ERROR: {String(this.state.err.stack || this.state.err)}
          {"\n\n"}
          {this.state.info?.componentStack}
        </pre>
      );
    return this.props.children as any;
  }
}

function Spinner() {
  return (
    <div className="flex justify-center items-center h-screen">
      <div className="text-lg">
        <Icon icon="mingcute:loading-3-line" className="animate-spin mr-2 h-12 w-12" />
      </div>
    </div>
  );
}

// ---- app shell (mirrors _app MyApp + middleware auth gating) -----------------
function AppShell({ children }: { children: React.ReactNode }) {
  const location = useLocation();
  const navigate = useNavigate();
  const [search] = useSearchParams();
  const settings = useDashboardStore((s: any) => s.settings);
  const extensions = useDashboardStore((s: any) => s.extensions);
  const profile = useDashboardStore((s: any) => s.profile);
  const profileResolved = useDashboardStore((s: any) => s.profileResolved);
  const hasExtension = useDashboardStore((s: any) => s.hasExtension);
  const fetchProfile = useDashboardStore((s: any) => s.fetchProfile);

  const path = location.pathname;

  React.useEffect(() => {
    restoreLayoutFromStorage();
  }, []);
  React.useEffect(() => {
    if (!settings) fetchProfile();
  }, [settings, fetchProfile]);
  React.useEffect(() => {
    applyTheme(settings);
  }, [settings]);

  // Module-disabled gating — ported verbatim from _app.tsx (router -> location).
  React.useEffect(() => {
    if (!extensions) return;
    const p = path;

    const FOLDER_TO_EXT: Record<string, string> = {
      affiliate: "mlm",
      ai: "ai_investment",
      faq: "knowledge_base",
      payment: "payment_gateway",
    };
    const adminMatch = p.match(/^\/admin\/ext\/([^/]+)/);
    if (adminMatch) {
      const ext = FOLDER_TO_EXT[adminMatch[1]] || adminMatch[1];
      if (!hasExtension(ext)) {
        navigate("/admin/system/extension", { replace: true });
        return;
      }
    }

    const USER_GATED = [
      { prefix: "/store", extension: "ecommerce" },
      { prefix: "/nft", extension: "nft" },
      { prefix: "/p2p", extension: "p2p" },
      { prefix: "/user/p2p", extension: "p2p" },
      { prefix: "/user/wallet/statement", extension: "statement" },
      { prefix: "/user/wallet/transfer", extension: "transfer" },
      { prefix: "/user/airdrop", extension: "airdrop" },
      { prefix: "/user/prediction", extension: "prediction" },
      { prefix: "/user/betting", extension: "betting" },
      { prefix: "/user/loan", extension: "loan" },
      { prefix: "/user/affiliate", extension: "mlm" },
      { prefix: "/user/staking", extension: "staking" },
      { prefix: "/user/nft", extension: "nft" },
      { prefix: "/user/ico", extension: "ico" },
      { prefix: "/user/forex", extension: "forex" },
      { prefix: "/user/store", extension: "ecommerce" },
    ];
    const hit = USER_GATED.find((g) => p === g.prefix || p.startsWith(`${g.prefix}/`));
    if (hit && !hasExtension(hit.extension)) {
      navigate("/", { replace: true });
      return;
    }

    if (p.startsWith("/user/invest")) {
      const it = String(search.get("type") || "").toLowerCase();
      const ext = it === "forex" ? "forex" : "investment";
      if (!hasExtension(ext)) {
        navigate("/", { replace: true });
        return;
      }
    }

    const exchangeOn = hasExtension("exchange");
    if ((p === "/user/binary" || p.startsWith("/binary")) && !exchangeOn) {
      navigate("/", { replace: true });
      return;
    }
    if (
      (p === "/market" || p === "/trade" || p.startsWith("/trade/")) &&
      !exchangeOn &&
      !hasExtension("ecosystem")
    ) {
      navigate("/", { replace: true });
    }
  }, [path, search, extensions, hasExtension, navigate]);

  // Auth gating — replaces middleware.ts (client-side, off the profile fetch).
  const authRedirect = computeAuthRedirect(path, profile, profileResolved, search);
  React.useEffect(() => {
    if (authRedirect) navigate(authRedirect, { replace: true });
  }, [authRedirect, navigate]);

  if (!settings) return <Spinner />;
  // Hold protected routes until auth resolves / while redirecting, so we never
  // flash protected content to an unauthenticated or under-privileged user
  // (Next did this via the SSR middleware redirect).
  if (isProtected(path) && !profileResolved) return <Spinner />;
  if (authRedirect) return <Spinner />;

  return (
    <div className="dark:bg-muted-950/[0.96]">
      <Toaster
        closeButton
        richColors
        theme="system"
        position="top-center"
        toastOptions={{ duration: 3000 }}
      />
      <OnboardingGate />
      <ConsentGate />
      <ImpersonationBanner />
      <GoogleAnalytics />
      <FacebookPixel />
      {settings?.googleTranslateStatus === "true" && <GoogleTranslate />}
      {children}
    </div>
  );
}

// ---- route generation -------------------------------------------------------
const pageModules = import.meta.glob([
  "/src/pages/**/*.{tsx,jsx}",
  "!/src/pages/_app.{tsx,jsx}",
  "!/src/pages/_document.{tsx,jsx}",
  "!/src/pages/api/**",
]);
// Branded 404 for unknown routes (mirrors the Next /404 page).
const NotFoundPage = React.lazy(() => import("@/pages/404"));

const generatedRoutes = Object.entries(pageModules)
  .map(([file, loader]) => {
    let p = file
      .replace("/src/pages", "")
      .replace(/\.(tsx|jsx)$/, "")
      .replace(/\/index$/, "");
    if (/\/?_app$|\/?_document$/.test(p) || p.startsWith("/api")) return null;
    if (p === "") p = "/";
    p = p.replace(/\[\.\.\.(\w+)\]/g, "*").replace(/\[(\w+)\]/g, ":$1");
    const Comp = React.lazy(loader as any);
    return { path: p, Comp };
  })
  .filter(Boolean) as { path: string; Comp: React.LazyExoticComponent<any> }[];

const App = () => (
  <ErrorBoundary>
    <HelmetProvider>
      <AppWebSocketProvider>
        <BrowserRouter>
          <AppShell>
            <Suspense fallback={<Spinner />}>
              <Routes>
                {generatedRoutes.map(({ path, Comp }) => (
                  <Route key={path} path={path} element={<Comp />} />
                ))}
                <Route path="*" element={<NotFoundPage />} />
              </Routes>
            </Suspense>
          </AppShell>
        </BrowserRouter>
      </AppWebSocketProvider>
    </HelmetProvider>
  </ErrorBoundary>
);

try {
  createRoot(document.getElementById("root")!).render(<App />);
} catch (e) {
  showError("MOUNT_ERROR", e);
}
