import { Route, Router } from "@solidjs/router";
import { ThemeProvider } from "@suid/material";
import {
  Component,
  createEffect,
  createMemo,
  createRenderEffect,
  createSignal,
  ErrorBoundary,
  lazy,
  onCleanup,
} from "solid-js";
import { useRootTheme } from "./material/theme.js";
import {
  Provider as ClientProvider,
  createMastoClientFor,
} from "./masto/clients.js";
import { $accounts, updateAcctInf } from "./accounts/stores.js";
import { useStore } from "@nanostores/solid";
import { DateFnScope, useLanguage } from "./platform/i18n.jsx";
import { useRegisterSW } from "virtual:pwa-register/solid";
import {
  isJSONRPCResult,
  ResultDispatcher,
  type JSONRPC,
} from "./serviceworker/workerrpc.js";
import { Service } from "./serviceworker/services.js";
import { makeEventListener } from "@solid-primitives/event-listener";
import { ServiceWorkerProvider } from "./platform/host.js";

const AccountSignIn = lazy(() => import("./accounts/SignIn.js"));
const AccountMastodonOAuth2Callback = lazy(
  () => import("./accounts/MastodonOAuth2Callback.js"),
);
const TimelineHome = lazy(() => import("./timelines/Home.js"));
const Settings = lazy(() => import("./settings/Settings.js"));
const TootBottomSheet = lazy(() => import("./timelines/TootBottomSheet.js"));
const MotionSettings = lazy(() => import("./settings/Motions.js"));
const LanguageSettings = lazy(() => import("./settings/Language.js"));
const RegionSettings = lazy(() => import("./settings/Region.jsx"));
const UnexpectedError = lazy(() => import("./UnexpectedError.js"));
const Profile = lazy(() => import("./profiles/Profile.js"));

const Routing: Component = () => {
  return (
    <Router>
      <Route path="/" component={TimelineHome}>
        <Route path=""></Route>
        <Route path="/settings" component={Settings}>
          <Route path=""></Route>
          <Route path="/language" component={LanguageSettings}></Route>
          <Route path="/region" component={RegionSettings}></Route>
          <Route path="/motions" component={MotionSettings}></Route>
        </Route>
        <Route path="/:acct/toot/:id" component={TootBottomSheet}></Route>
        <Route path="/:acct/profile/:id" component={Profile}></Route>
      </Route>
      <Route path={"/accounts"}>
        <Route path={"/sign-in"} component={AccountSignIn} />
        <Route
          path={"/oauth2/mastodon"}
          component={AccountMastodonOAuth2Callback}
        />
      </Route>
    </Router>
  );
};

const App: Component = () => {
  const theme = useRootTheme();
  const accts = useStore($accounts);
  const lang = useLanguage();
  const [serviceWorker, setServiceWorker] = createSignal<ServiceWorker>();
  const dispatcher = new ResultDispatcher();

  let checkAge = 0;
  const untilServiceWorkerAlive = async (
    worker: ServiceWorker,
    expectedAge: number,
  ) => {
    const [call, ret] = dispatcher.createTypedCall<Service>("ping");
    worker.postMessage(await call);
    const result = await ret;
    console.assert(!result.error, result);
    if (expectedAge === checkAge) {
      setServiceWorker(worker);
    }
  };

  makeEventListener(window, "message", (event: MessageEvent<JSONRPC>) => {
    if (isJSONRPCResult(event.data)) {
      dispatcher.dispatch(event.data.id, event.data);
    }
  });

  const {
    needRefresh: [needRefresh],
    offlineReady: [offlineReady],
  } = useRegisterSW({
    onRegisteredSW(scriptUrl, reg) {
      console.info("service worker is registered from %s", scriptUrl);
      const active = reg?.active;
      if (!active) {
        console.warn("No service is in activating or activated");
        return;
      }
      untilServiceWorkerAlive(active, checkAge++);
    },
  });

  const clients = createMemo(() => {
    return accts().map((x) => ({
      account: x,
      client: createMastoClientFor(x),
    }));
  });

  createEffect(() => {
    const neededUpdates = accts()
      .map((x, i) => [i, x] as const)
      .filter(([, x]) => !x.inf);
    if (neededUpdates.length > 0) {
      // FIXME: we might need some kind of concurrent control
      Promise.all(neededUpdates.map(([i]) => updateAcctInf(i))).then(
        (x) => {
          console.info("acct info updated for %d acct(s)", x.length);
        },
        (reason) => {
          console.error("acct info update is fail", reason);
        },
      );
    }
  });

  createRenderEffect(() => {
    const root = document.querySelector(":root")!;
    root.setAttribute("lang", lang());
  });

  onCleanup(() => {
    const root = document.querySelector(":root")!;
    root.removeAttribute("lang");
  });

  return (
    <ErrorBoundary
      fallback={(err, reset) => {
        console.error(err);
        return <UnexpectedError error={err} />;
      }}
    >
      <ThemeProvider theme={theme}>
        <DateFnScope>
          <ClientProvider value={clients}>
            <ServiceWorkerProvider
              value={{
                needRefresh,
                offlineReady,
                serviceWorker,
              }}
            >
              <Routing />
            </ServiceWorkerProvider>
          </ClientProvider>
        </DateFnScope>
      </ThemeProvider>
    </ErrorBoundary>
  );
};

export default App;
