import { defineAsyncComponent, reactive } from "vue";
import * as misskey from "iceshrimp-sdk";
import { i18n } from "./i18n";
import { del, get, set } from "@/scripts/idb-proxy";
import { apiUrl } from "@/config";
import { waiting, api, popup, popupMenu, success, alert } from "@/os";
import { unisonReload, reloadChannel } from "@/scripts/unison-reload";

// TODO: 他のタブと永続化されたstateを同期

type Account = misskey.entities.MeDetailed;

const accountData = localStorage.getItem("account");

// TODO: 外部からはreadonlyに
export const $i = accountData
	? reactive(JSON.parse(accountData) as Account)
	: null;

export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator);
export const iAmAdmin = $i?.isAdmin;

export async function signout() {
	waiting();
	localStorage.removeItem("account");

	await removeAccount($i.id);

	const accounts = await getAccounts();

	//#region Remove service worker registration
	try {
		if (navigator.serviceWorker.controller) {
			const registration = await navigator.serviceWorker.ready;
			const push = await registration.pushManager.getSubscription();
			if (push) {
				await fetch(`${apiUrl}/sw/unregister`, {
					method: "POST",
					body: JSON.stringify({
						i: $i.token,
						endpoint: push.endpoint,
					}),
				});
			}
		}

		if (accounts.length === 0) {
			await navigator.serviceWorker.getRegistrations().then((registrations) => {
				return Promise.all(
					registrations.map((registration) => registration.unregister()),
				);
			});
		}
	} catch (err) {}
	//#endregion

	document.cookie = "igi=; path=/";

	if (accounts.length > 0) login(accounts[0].token);
	else unisonReload("/");
}

export async function getAccounts(): Promise<
	{ id: Account["id"]; token: Account["token"] }[]
> {
	return (await get("accounts")) || [];
}

export async function addAccount(id: Account["id"], token: Account["token"]) {
	const accounts = await getAccounts();
	if (!accounts.some((x) => x.id === id)) {
		await set("accounts", accounts.concat([{ id, token }]));
	}
}

export async function removeAccount(id: Account["id"]) {
	const accounts = await getAccounts();
	accounts.splice(
		accounts.findIndex((x) => x.id === id),
		1,
	);

	if (accounts.length > 0) await set("accounts", accounts);
	else await del("accounts");
}

function fetchAccount(token: string): Promise<Account> {
	return new Promise((done, fail) => {
		// Fetch user
		fetch(`${apiUrl}/i`, {
			method: "POST",
			body: JSON.stringify({
				i: token,
			}),
		})
			.then((res) => res.json())
			.then((res) => {
				if (res.error) {
					if (res.error.id === "a8c724b3-6e9c-4b46-b1a8-bc3ed6258370") {
						showSuspendedDialog().then(() => {
							signout();
						});
					} else {
						alert({
							type: "error",
							title: i18n.ts.failedToFetchAccountInformation,
							text: JSON.stringify(res.error),
						});
					}
				} else {
					res.token = token;
					done(res);
				}
			})
			.catch(fail);
	});
}

export function updateAccount(accountData) {
	for (const [key, value] of Object.entries(accountData)) {
		$i[key] = value;
	}
	localStorage.setItem("account", JSON.stringify($i));
}

export function refreshAccount() {
	return fetchAccount($i.token).then(updateAccount);
}

export async function login(token: Account["token"], redirect?: string) {
	waiting();
	if (_DEV_) console.log("logging as token ", token);
	const me = await fetchAccount(token);
	localStorage.setItem("account", JSON.stringify(me));
	document.cookie = `token=${token}; path=/; max-age=31536000`; // bull dashboardの認証とかで使う
	await addAccount(me.id, token);

	if (redirect) {
		// 他のタブは再読み込みするだけ
		reloadChannel.postMessage(null);
		// このページはredirectで指定された先に移動
		location.href = redirect;
		return;
	}

	unisonReload();
}

export async function openAccountMenu(
	opts: {
		includeCurrentAccount?: boolean;
		withExtraOperation: boolean;
		withoutProfileLink?: boolean;
		active?: misskey.entities.UserDetailed["id"];
		onChoose?: (account: misskey.entities.UserDetailed) => void;
	},
	ev: MouseEvent,
) {
	function showSigninDialog() {
		popup(
			defineAsyncComponent(() => import("@/components/MkSigninDialog.vue")),
			{},
			{
				done: (res) => {
					addAccount(res.id, res.i);
					switchAccountWithToken(res.i);
				},
			},
			"closed",
		);
	}

	function createAccount() {
		popup(
			defineAsyncComponent(() => import("@/components/MkSignupDialog.vue")),
			{},
			{
				done: (res) => {
					addAccount(res.id, res.i);
					switchAccountWithToken(res.i);
				},
			},
			"closed",
		);
	}

	async function switchAccount(account: misskey.entities.UserDetailed) {
		const storedAccounts = await getAccounts();
		const token = storedAccounts.find((x) => x.id === account.id).token;
		switchAccountWithToken(token);
	}

	function switchAccountWithToken(token: string) {
		login(token);
	}

	const storedAccounts = await getAccounts().then((accounts) =>
		accounts.filter((x) => x.id !== $i.id),
	);
	const accountsPromise = api("users/show", {
		userIds: storedAccounts.map((x) => x.id),
	});

	function createItem(account: misskey.entities.UserDetailed) {
		return {
			type: "user",
			user: account,
			active: opts.active != null ? opts.active === account.id : false,
			action: () => {
				if (opts.onChoose) {
					opts.onChoose(account);
				} else {
					switchAccount(account);
				}
			},
		};
	}

	const accountItemPromises = storedAccounts.map(
		(a) =>
			new Promise((res) => {
				accountsPromise.then((accounts) => {
					const account = accounts.find((x) => x.id === a.id);
					if (account == null) return res(null);
					res(createItem(account));
				});
			}),
	);

	if (opts.withExtraOperation) {
		const menu = [
			...(!opts.withoutProfileLink ? [
				{
					type: "link",
					text: i18n.ts.profile,
					to: `/@${$i.username}`,
					avatar: $i,
				},
				null,
			] : []),
			...(opts.includeCurrentAccount ? [createItem($i)] : []),
			...accountItemPromises,
			...(opts.withoutProfileLink ? [null] : []),
			{
				type: "parent",
				icon: "ph-plus ph-bold ph-lg",
				text: i18n.ts.addAccount,
				children: [
					{
						text: i18n.ts.existingAccount,
						action: () => {
							showSigninDialog();
						},
					},
					{
						text: i18n.ts.createAccount,
						action: () => {
							createAccount();
						},
					},
				],
			},
			{
				type: "link",
				icon: "ph-users ph-bold ph-lg",
				text: i18n.ts.manageAccounts,
				to: "/settings/accounts",
			},
			{
				type: "button",
				icon: "ph-sign-out ph-bold ph-lg",
				text: i18n.ts.logout,
				action: () => {
					signout();
				},
			},
		];

		popupMenu(
			menu,
			ev.currentTarget ?? ev.target,
			{
				align: "left",
			},
		);
	} else {
		popupMenu(
			[
				...(opts.includeCurrentAccount ? [createItem($i)] : []),
				...accountItemPromises,
			],
			ev.currentTarget ?? ev.target,
			{
				align: "left",
			},
		);
	}
}
