import { lazy } from "react";
import ReactDOM from "react-dom/client";
import {
  createBrowserRouter,
  isRouteErrorResponse,
  redirect,
  type RouteObject,
  RouterProvider,
  useRouteError,
} from "react-router";
import "./index.css";
import { ExclamationTriangleIcon } from "@heroicons/react/16/solid";

import { CLOUD_API, CLOUD_ENABLE_VERSIONED_UI, DEVICE_API } from "@/ui.config";
import api from "@/api";
import Root from "@/root";
import { m } from "@localizations/messages.js";
import Card from "@components/Card";
import EmptyCard from "@components/EmptyCard";
import NotFoundPage from "@components/NotFoundPage";
import DeviceRoute, { LocalDevice } from "@routes/devices.$id";
import WelcomeRoute, { DeviceStatus } from "@routes/welcome-local";
import LoginLocalRoute from "@routes/login-local";
import WelcomeLocalModeRoute from "@routes/welcome-local.mode";
import WelcomeLocalPasswordRoute from "@routes/welcome-local.password";
import AdoptRoute from "@routes/adopt";
import SetupRoute from "@routes/devices.$id.setup";
import DevicesIdDeregister from "@routes/devices.$id.deregister";
import DeviceIdRename from "@routes/devices.$id.rename";
import DevicesRoute from "@routes/devices";
import SettingsIndexRoute from "@routes/devices.$id.settings._index";
import SettingsAccessIndexRoute from "@routes/devices.$id.settings.access._index";
import Notifications from "@/notifications";
const SignupRoute = lazy(() => import("@routes/signup"));
const LoginRoute = lazy(() => import("@routes/login"));
const DevicesAlreadyAdopted = lazy(() => import("@routes/devices.already-adopted"));
const OtherSessionRoute = lazy(() => import("@routes/devices.$id.other-session"));
const MountRoute = lazy(() => import("@routes/devices.$id.mount"));
const SettingsRoute = lazy(() => import("@routes/devices.$id.settings"));
const SettingsMouseRoute = lazy(() => import("@routes/devices.$id.settings.mouse"));
const SettingsKeyboardRoute = lazy(() => import("@routes/devices.$id.settings.keyboard"));
const SettingsAdvancedRoute = lazy(() => import("@routes/devices.$id.settings.advanced"));
const SettingsHardwareRoute = lazy(() => import("@routes/devices.$id.settings.hardware"));
const SettingsVideoRoute = lazy(() => import("@routes/devices.$id.settings.video"));
const SettingsAppearanceRoute = lazy(() => import("@routes/devices.$id.settings.appearance"));
const SettingsGeneralIndexRoute = lazy(() => import("@routes/devices.$id.settings.general._index"));
const SettingsGeneralRebootRoute = lazy(() => import("@routes/devices.$id.settings.general.reboot"));
const SettingsGeneralUpdateRoute = lazy(() => import("@routes/devices.$id.settings.general.update"));
const SettingsNetworkRoute = lazy(() => import("@routes/devices.$id.settings.network"));
const SecurityAccessLocalAuthRoute = lazy(() => import("@routes/devices.$id.settings.access.local-auth"));
const SettingsMacrosRoute = lazy(() => import("@routes/devices.$id.settings.macros"));
const SettingsMacrosAddRoute = lazy(() => import("@routes/devices.$id.settings.macros.add"));
const SettingsMacrosEditRoute = lazy(() => import("@routes/devices.$id.settings.macros.edit"));

export const isOnDevice = import.meta.env.MODE === "device";
export const isInCloud = !isOnDevice;

export async function checkCloudAuth() {
  const res = await fetch(`${CLOUD_API}/me`, {
    mode: "cors",
    credentials: "include",
    headers: { "Content-Type": "application/json" },
  });

  if (res.status === 401) {
    throw redirect(`/login?returnTo=${window.location.href}`);
  }

  return await res.json();
}

export async function checkDeviceAuth() {
  const res = await api
    .GET(`${DEVICE_API}/device/status`)
    .then(res => res.json() as Promise<DeviceStatus>);

  if (!res.isSetup) throw redirect("/welcome");

  const deviceRes = await api.GET(`${DEVICE_API}/device`);
  if (deviceRes.status === 401) throw redirect("/login-local");
  if (deviceRes.ok) {
    const device = (await deviceRes.json()) as LocalDevice;
    return { authMode: device.authMode };
  }

  throw new Error("Error fetching device");
}

export async function checkAuth() {
  return isOnDevice ? checkDeviceAuth() : checkCloudAuth();
}

let router: ReturnType<typeof createBrowserRouter>;

const getDeviceRoute = (r: Omit<RouteObject, "children" | "index">): RouteObject => ({
  element: <DeviceRoute />,
  loader: DeviceRoute.loader,
  ...r,
  children: [
    {
      path: "other-session",
      element: <OtherSessionRoute />,
    },
    {
      path: "mount",
      element: <MountRoute />,
    },
    {
      path: "settings",
      element: <SettingsRoute />,
      children: [
        {
          index: true,
          loader: SettingsIndexRoute.loader,
        },
        {
          path: "general",
          children: [
            {
              index: true,
              element: <SettingsGeneralIndexRoute />,
            },
            // was previously only present on device routes
            {
              path: "reboot",
              element: <SettingsGeneralRebootRoute />,
            },
            {
              path: "update",
              element: <SettingsGeneralUpdateRoute />,
            },
          ],
        },
        {
          path: "mouse",
          element: <SettingsMouseRoute />,
        },
        {
          path: "keyboard",
          element: <SettingsKeyboardRoute />,
        },
        {
          path: "advanced",
          element: <SettingsAdvancedRoute />,
        },
        {
          path: "hardware",
          element: <SettingsHardwareRoute />,
        },
        {
          path: "network",
          element: <SettingsNetworkRoute />,
        },
        {
          path: "access",
          children: [
            {
              index: true,
              element: <SettingsAccessIndexRoute />,
              loader: SettingsAccessIndexRoute.loader,
            },
            {
              path: "local-auth",
              element: <SecurityAccessLocalAuthRoute />,
            },
          ],
        },
        {
          path: "video",
          element: <SettingsVideoRoute />,
        },
        {
          path: "appearance",
          element: <SettingsAppearanceRoute />,
        },
        {
          path: "macros",
          children: [
            {
              index: true,
              element: <SettingsMacrosRoute />,
            },
            {
              path: "add",
              element: <SettingsMacrosAddRoute />,
            },
            {
              path: ":macroId/edit",
              element: <SettingsMacrosEditRoute />,
            },
          ],
        },
      ],
    },
  ],
});

if (isOnDevice) {
  router = createBrowserRouter([
    {
      path: "/welcome/mode",
      element: <WelcomeLocalModeRoute />,
      action: WelcomeLocalModeRoute.action,
    },
    {
      path: "/welcome/password",
      element: <WelcomeLocalPasswordRoute />,
      action: WelcomeLocalPasswordRoute.action,
    },
    {
      path: "/welcome",
      element: <WelcomeRoute />,
      loader: WelcomeRoute.loader,
    },
    {
      path: "/login-local",
      element: <LoginLocalRoute />,
      action: LoginLocalRoute.action,
      loader: LoginLocalRoute.loader,
    },
    getDeviceRoute({
      path: "/",
      errorElement: <ErrorBoundary />,
      HydrateFallback: () => <div className="p-4">{m.loading()}</div>,
    }),
    {
      path: "/adopt",
      element: <AdoptRoute />,
      loader: AdoptRoute.loader,
      errorElement: <ErrorBoundary />,
    },
  ]);
} else {
  const routeObjects: RouteObject[] = [
    {
      errorElement: <ErrorBoundary />,
      children: [
        { path: "signup", element: <SignupRoute /> },
        { path: "login", element: <LoginRoute /> },
        {
          path: "/",
          element: <Root />,
          children: [
            {
              index: true,
              loader: async () => {
                await checkAuth();
                return redirect(`/devices`);
              },
            },
            {
              path: "devices/:id/setup",
              element: <SetupRoute />,
              action: SetupRoute.action,
              loader: SetupRoute.loader,
            },
            {
              path: "devices/already-adopted",
              element: <DevicesAlreadyAdopted />,
            },
            getDeviceRoute({
              path: "devices/:id",
            }),
            {
              path: "devices/:id/deregister",
              element: <DevicesIdDeregister />,
              loader: DevicesIdDeregister.loader,
              action: DevicesIdDeregister.action,
            },
            {
              path: "devices/:id/rename",
              element: <DeviceIdRename />,
              loader: DeviceIdRename.loader,
              action: DeviceIdRename.action,
            },
            {
              path: "devices",
              element: <DevicesRoute />,
              loader: DevicesRoute.loader
            },
          ],
        },
      ],
    },
  ];

  // if versioned UI is not enabled, we need to add a route that redirects to the non-versioned route
  if (!CLOUD_ENABLE_VERSIONED_UI) {
    routeObjects.unshift({
      path: "v/:version/*",
      element: <Root />,
      loader: async ({ params }) => {
        throw redirect(`/${params['*']}`);
      },
    });
  }

  router = createBrowserRouter(routeObjects, { basename: import.meta.env.BASE_URL });
}

document.addEventListener("DOMContentLoaded", () => {
  ReactDOM.createRoot(document.getElementById("root")!).render(
    <>
      <RouterProvider router={router} />
      <Notifications
        toastOptions={{
          className:
            "rounded-sm border-none bg-white text-black shadow-sm outline-1 outline-slate-800/30",
        }}
        max={2}
      />
    </>,
  );
});

// eslint-disable-next-line react-refresh/only-export-components
function ErrorBoundary() {
  const error = useRouteError();
  if (isRouteErrorResponse(error)) {
    if (error.status === 404) return <NotFoundPage />;
  }

  const getErrorMessage = (err: unknown): string | null => {
    // If it's a route error response, try to read a string at err.data.error.message or err.data.error safely
    if (isRouteErrorResponse(err)) {
      const data = (err as { data?: unknown }).data;
      if (data && typeof data === "object") {
        const maybeError = (data as Record<string, unknown>)["error"];
        if (maybeError) {
          if (typeof maybeError === "object") {
            const msg = (maybeError as Record<string, unknown>)["message"];
            if (typeof msg === "string") return msg;
          } else if (typeof maybeError === "string") {
            return maybeError;
          }
        }
      }
    }

    // Fallback: check plain object message property
    if (err && typeof err === "object") {
      const maybeMsg = (err as Record<string, unknown>)["message"];
      if (typeof maybeMsg === "string") return maybeMsg;
    }

    return null;
  };

  const errorMessage = getErrorMessage(error);

  return (
    <div className="h-full w-full">
      <div className="flex h-full items-center justify-center">
        <div className="w-full max-w-2xl">
          <EmptyCard
            IconElm={ExclamationTriangleIcon}
            headline={m.oh_no()}
            description={m.something_went_wrong()}
            BtnElm={
              errorMessage && (
                <Card>
                  <div className="flex items-center font-mono">
                    <div className="flex p-2 text-black dark:text-white">
                      <span className="text-sm">{errorMessage}</span>
                    </div>
                  </div>
                </Card>
              )
            }
          />
        </div>
      </div>
    </div>
  );
}
