/**
 * Client entry point
 */

// https://vitejs.dev/config/build-options.html#build-modulepreload
import "vite/modulepreload-polyfill";

import "@/style.scss";
import "@phosphor-icons/web/bold";
import "@phosphor-icons/web/fill";

//#region account indexedDB migration
import { set } from "@/scripts/idb-proxy";
import {
    App,
    computed,
    createApp,
    defineAsyncComponent,
    markRaw,
    version as vueVersion,
    watch,
} from "vue";
import { compareVersions } from "compare-versions";

import widgets from "@/widgets";
import directives from "@/directives";
import { lang, ui, version } from "@/config";
import { applyTheme } from "@/scripts/theme";
import { isDeviceDarkmode } from "@/scripts/is-device-darkmode";
import { i18n } from "@/i18n";
import { alert, confirm, popup, post, toast } from "@/os";
import { stream } from "@/stream";
import * as sound from "@/scripts/sound";
import { $i, login, refreshAccount, signout, updateAccount } from "@/account";
import { ColdDeviceStorage, defaultStore } from "@/store";
import { fetchInstance, instance } from "@/instance";
import { makeHotkey } from "@/scripts/hotkey";
import { search } from "@/scripts/search";
import { deviceKind } from "@/scripts/device-kind";
import { initializeSw } from "@/scripts/initialize-sw";
import { reloadChannel } from "@/scripts/unison-reload";
import { reactionPicker } from "@/scripts/reaction-picker";
import { getUrlWithoutLoginId } from "@/scripts/login-id";
import { getAccountFromId } from "@/scripts/get-account-from-id";

import Mfm from "./components/global/MkMisskeyFlavoredMarkdown.vue";
import MkA from "./components/global/MkA.vue";
import MkAcct from "./components/global/MkAcct.vue";
import MagAvatar from "./components/global/MagAvatar.vue";
import MagAvatarResolvingProxy from "./components/global/MagAvatarResolvingProxy.vue";
import MagEmoji from "./components/global/MagEmoji.vue";
import MkUserName from "./components/global/MkUserName.vue";
import MkEllipsis from "./components/global/MkEllipsis.vue";
import MkTime from "./components/global/MkTime.vue";
import MkUrl from "./components/global/MkUrl.vue";
import I18n from "./components/global/i18n";
import RouterView from "./components/global/RouterView.vue";
import MkLoading from "./components/global/MkLoading.vue";
import MkError from "./components/global/MkError.vue";
import MkPageHeader from "./components/global/MkPageHeader.vue";
import MkSpacer from "./components/global/MkSpacer.vue";
import MkStickyContainer from "./components/global/MkStickyContainer.vue";

function globalComponents(app: App) {
    app.component("I18n", I18n);
    app.component("RouterView", RouterView);
    app.component("Mfm", Mfm);
    app.component("MkA", MkA);
    app.component("MkAcct", MkAcct);
    app.component("MagAvatar", MagAvatar);
    app.component("MagEmoji", MagEmoji);
    app.component("MkUserName", MkUserName);
    app.component("MkEllipsis", MkEllipsis);
    app.component("MkTime", MkTime);
    app.component("MkUrl", MkUrl);
    app.component("MkLoading", MkLoading);
    app.component("MkError", MkError);
    app.component("MkPageHeader", MkPageHeader);
    app.component("MkSpacer", MkSpacer);
    app.component("MkStickyContainer", MkStickyContainer);
    app.component("MagAvatarResolvingProxy", MagAvatarResolvingProxy);
}

declare module "@vue/runtime-core" {
    export interface GlobalComponents {
        I18n: typeof I18n;
        RouterView: typeof RouterView;
        Mfm: typeof Mfm;
        MkA: typeof MkA;
        MkAcct: typeof MkAcct;
        MagAvatar: typeof MagAvatar;
        MagEmoji: typeof MagEmoji;
        MkUserName: typeof MkUserName;
        MkEllipsis: typeof MkEllipsis;
        MkTime: typeof MkTime;
        MkUrl: typeof MkUrl;
        MkLoading: typeof MkLoading;
        MkError: typeof MkError;
        MkPageHeader: typeof MkPageHeader;
        MkSpacer: typeof MkSpacer;
        MkStickyContainer: typeof MkStickyContainer;
        MagAvatarResolvingProxy: typeof MagAvatarResolvingProxy;
    }
}

const accounts = localStorage.getItem("accounts");
if (accounts) {
    set("accounts", JSON.parse(accounts));
    localStorage.removeItem("accounts");
}
//#endregion

function checkForSplash() {
    const splash = document.getElementById("splash");
    // 念のためnullチェック(HTMLが古い場合があるため(そのうち消す))
    if (splash) {
        splash.style.opacity = "0";
        splash.style.pointerEvents = "none";
        splash.addEventListener("transitionend", () => {
            splash.remove();
        });
    }
}

(async () => {
    console.info(`Magnetar v${version}`);

    if (_DEV_) {
        console.warn("Development mode!!!");

        console.info(`vue ${vueVersion}`);

        (window as any).$i = $i;
        (window as any).$store = defaultStore;

        window.addEventListener("error", (event) => {
            console.error(event);
            /*
			alert({
				type: 'error',
				title: 'DEV: Unhandled error',
				text: event.message
			});
			*/
        });

        window.addEventListener("unhandledrejection", (event) => {
            console.error(event);
            /*
			alert({
				type: 'error',
				title: 'DEV: Unhandled promise rejection',
				text: event.reason
			});
			*/
        });
    }

    // タッチデバイスでCSSの:hoverを機能させる
    document.addEventListener("touchend", () => {}, { passive: true });

    // 一斉リロード
    reloadChannel.addEventListener("message", (path) => {
        if (path !== null) location.href = path;
        else location.reload();
    });

    //#region SEE: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
    // TODO: いつの日にか消したい
    const vh = window.innerHeight * 0.01;
    document.documentElement.style.setProperty("--vh", `${vh}px`);
    window.addEventListener("resize", () => {
        const vh = window.innerHeight * 0.01;
        document.documentElement.style.setProperty("--vh", `${vh}px`);
    });
    //#endregion

    //#region Set lang attr
    const html = document.documentElement;
    html.setAttribute("lang", lang || "en-US");
    //#endregion

    //#region loginId
    const params = new URLSearchParams(location.search);
    const loginId = params.get("loginId");

    if (loginId) {
        const target = getUrlWithoutLoginId(location.href);

        if (!$i || $i.id !== loginId) {
            const account = await getAccountFromId(loginId);
            if (account) {
                await login(account.token, target);
            }
        }

        history.replaceState({ misskey: "loginId" }, "", target);
    }

    //#endregion

    //#region Fetch user
    if ($i?.token) {
        if (_DEV_) {
            console.log("account cache found. refreshing...");
        }

        refreshAccount();
    } else {
        if (_DEV_) {
            console.log("no account cache found.");
        }

        // 連携ログインの場合用にCookieを参照する
        const i = (document.cookie.match(/igi=(\w+)/) || [null, null])[1];

        if (i != null && i !== "null") {
            if (_DEV_) {
                console.log("signing...");
            }

            try {
                document.body.innerHTML = "<div>Please wait...</div>";
                await login(i);
            } catch (err) {
                // Render the error screen
                // TODO: ちゃんとしたコンポーネントをレンダリングする(v10とかのトラブルシューティングゲーム付きのやつみたいな)
                document.body.innerHTML = '<div id="err">Oops!</div>';
            }
        } else {
            if (_DEV_) {
                console.log("not signed in");
            }
        }
    }
    //#endregion

    const fetchInstanceMetaPromise = fetchInstance();

    fetchInstanceMetaPromise.then(() => {
        localStorage.setItem("v", instance.version);

        // Init service worker
        initializeSw();
    });

    const app = createApp(
        window.location.search === "?zen"
            ? defineAsyncComponent(() => import("@/ui/zen.vue"))
            : !$i
              ? defineAsyncComponent(() => import("@/ui/visitor.vue"))
              : ui === "deck"
                ? defineAsyncComponent(() => import("@/ui/deck.vue"))
                : defineAsyncComponent(() => import("@/ui/universal.vue")),
    );

    if (_DEV_) {
        app.config.performance = true;
    }

    app.config.globalProperties = {
        $i,
        $store: defaultStore,
        $instance: instance,
        $t: i18n.t,
        $ts: i18n.ts,
    };

    widgets(app);
    directives(app);
    globalComponents(app);

    checkForSplash();

    // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
    // なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する
    const rootEl = (() => {
        const MISSKEY_MOUNT_DIV_ID = "calckey_app";

        const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID);

        if (currentEl) {
            console.warn("multiple import detected");
            return currentEl;
        }

        const rootEl = document.createElement("div");
        rootEl.id = MISSKEY_MOUNT_DIV_ID;
        document.body.appendChild(rootEl);
        return rootEl;
    })();

    app.mount(rootEl);

    // boot.jsのやつを解除
    window.onerror = null;
    window.onunhandledrejection = null;

    reactionPicker.init();

    checkForSplash();

    // クライアントが更新されたか？
    const lastVersion = localStorage.getItem("lastVersion");

    if (lastVersion !== version) {
        localStorage.setItem("lastVersion", version);

        // テーマリビルドするため
        localStorage.removeItem("theme");

        try {
            // 変なバージョン文字列来るとcompareVersionsでエラーになるため
            if (
                lastVersion != null &&
                compareVersions(version, lastVersion) === 1 &&
                defaultStore.state.showUpdates
            ) {
                // ログインしてる場合だけ
                if ($i) {
                    popup(
                        defineAsyncComponent(
                            () => import("@/components/MkUpdated.vue"),
                        ),
                        {},
                        {},
                        "closed",
                    );
                }
            }
        } catch (err) {
            console.error(err);
        }
    }

    // NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため)
    watch(
        defaultStore.reactiveState.darkMode,
        (darkMode) => {
            applyTheme(
                darkMode
                    ? ColdDeviceStorage.get("darkTheme")
                    : ColdDeviceStorage.get("lightTheme"),
            );
        },
        { immediate: localStorage.theme == null },
    );

    const darkTheme = computed(ColdDeviceStorage.makeGetterSetter("darkTheme"));
    const lightTheme = computed(
        ColdDeviceStorage.makeGetterSetter("lightTheme"),
    );

    watch(darkTheme, (theme) => {
        if (defaultStore.state.darkMode) {
            applyTheme(theme);
        }
    });

    watch(lightTheme, (theme) => {
        if (!defaultStore.state.darkMode) {
            applyTheme(theme);
        }
    });

    //#region Sync dark mode
    if (ColdDeviceStorage.get("syncDeviceDarkMode")) {
        defaultStore.set("darkMode", isDeviceDarkmode());
    }
    window.matchMedia("(prefers-color-scheme: dark)").onchange = (mql) => {
        if (ColdDeviceStorage.get("syncDeviceDarkMode")) {
            defaultStore.set("darkMode", mql.matches);
        }
    };
    //#endregion

    fetchInstanceMetaPromise.then(() => {
        if (defaultStore.state.themeInitial) {
            if (instance.defaultLightTheme != null)
                ColdDeviceStorage.set(
                    "lightTheme",
                    JSON.parse(instance.defaultLightTheme),
                );
            if (instance.defaultDarkTheme != null)
                ColdDeviceStorage.set(
                    "darkTheme",
                    JSON.parse(instance.defaultDarkTheme),
                );
            defaultStore.set("themeInitial", false);
        }
    });

    watch(
        defaultStore.reactiveState.useBlurEffectForModal,
        (v) => {
            document.documentElement.style.setProperty(
                "--modalBgFilter",
                v ? "blur(4px)" : "none",
            );
        },
        { immediate: true },
    );

    watch(
        defaultStore.reactiveState.useBlurEffect,
        (v) => {
            if (v && deviceKind !== "smartphone") {
                document.documentElement.style.removeProperty("--blur");
            } else {
                document.documentElement.style.setProperty("--blur", "none");
            }
        },
        { immediate: true },
    );

    let reloadDialogShowing = false;
    stream.on("_disconnected_", async () => {
        if (defaultStore.state.serverDisconnectedBehavior === "reload") {
            location.reload();
        } else if (defaultStore.state.serverDisconnectedBehavior === "dialog") {
            if (reloadDialogShowing) return;
            reloadDialogShowing = true;
            const { canceled } = await confirm({
                type: "warning",
                title: i18n.ts.disconnectedFromServer,
                text: i18n.ts.reloadConfirm,
            });
            reloadDialogShowing = false;
            if (!canceled) {
                location.reload();
            }
        }
    });

    stream.on("emojiAdded", (emojiData) => {
        // TODO
        //store.commit('instance/set', );
    });

    const hotkeys = {
        d: (): void => {
            defaultStore.set("darkMode", !defaultStore.state.darkMode);
        },
        s: search,
    };

    if ($i) {
        // only add post shortcuts if logged in
        hotkeys["p|n"] = post;

        if ($i.isDeleted) {
            alert({
                type: "warning",
                text: i18n.ts.accountDeletionInProgress,
            });
        }

        const lastUsed = localStorage.getItem("lastUsed");
        if (lastUsed) {
            const lastUsedDate = parseInt(lastUsed, 10);
            // 二時間以上前なら
            if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) {
                toast(
                    i18n.t("welcomeBackWithName", {
                        name: $i.name || $i.username,
                    }),
                );
            }
        }
        localStorage.setItem("lastUsed", Date.now().toString());

        if ("Notification" in window) {
            // 許可を得ていなかったらリクエスト
            if (Notification.permission === "default") {
                Notification.requestPermission();
            }
        }

        const main = markRaw(stream.useChannel("main", null, "System"));

        // 自分の情報が更新されたとき
        main.on("meUpdated", (i) => {
            updateAccount(i);
        });

        main.on("readAllNotifications", () => {
            updateAccount({ hasUnreadNotification: false });
        });

        main.on("unreadNotification", () => {
            updateAccount({ hasUnreadNotification: true });
        });

        main.on("unreadMention", () => {
            updateAccount({ hasUnreadMentions: true });
        });

        main.on("readAllUnreadMentions", () => {
            updateAccount({ hasUnreadMentions: false });
        });

        main.on("unreadSpecifiedNote", () => {
            updateAccount({ hasUnreadSpecifiedNotes: true });
        });

        main.on("readAllUnreadSpecifiedNotes", () => {
            updateAccount({ hasUnreadSpecifiedNotes: false });
        });

        main.on("readAllAntennas", () => {
            updateAccount({ hasUnreadAntenna: false });
        });

        main.on("unreadAntenna", () => {
            updateAccount({ hasUnreadAntenna: true });
            sound.play("antenna");
        });

        main.on("readAllAnnouncements", () => {
            updateAccount({ hasUnreadAnnouncement: false });
        });

        // トークンが再生成されたとき
        // このままではMisskeyが利用できないので強制的にサインアウトさせる
        main.on("myTokenRegenerated", () => {
            signout();
        });
    }

    // shortcut
    document.addEventListener("keydown", makeHotkey(hotkeys));
})();
