declare let self: ServiceWorkerGlobalScope;

import {
	createEmptyNotification,
	createNotification,
} from "@/scripts/create-notification";
import { swLang } from "@/scripts/lang";
import { swNotificationRead } from "@/scripts/notification-read";
import { pushNotificationDataMap } from "@/types";
import * as swos from "@/scripts/operations";
import { acct as getAcct } from "@/filters/user";

self.addEventListener("install", (ev) => {
	ev.waitUntil(self.skipWaiting());
});

self.addEventListener("activate", (ev) => {
	ev.waitUntil(
		caches
			.keys()
			.then((cacheNames) =>
				Promise.all(
					cacheNames
						.filter((v) => v !== swLang.cacheName)
						.map((name) => caches.delete(name)),
				),
			)
			.then(() => self.clients.claim()),
	);
});

self.addEventListener("fetch", (ev) => {
	let isHTMLRequest = false;
	if (ev.request.headers.get("sec-fetch-dest") === "document") {
		isHTMLRequest = true;
	} else if (ev.request.headers.get("accept")?.includes("/html")) {
		isHTMLRequest = true;
	} else if (ev.request.url.endsWith("/")) {
		isHTMLRequest = true;
	}

	if (!isHTMLRequest) return;
	ev.respondWith(
		fetch(ev.request).catch(
			() =>
				new Response(`Offline. Service Worker @${_VERSION_}`, { status: 200 }),
		),
	);
});

self.addEventListener("push", (ev) => {
	// クライアント取得
	ev.waitUntil(
		self.clients
			.matchAll({
				includeUncontrolled: true,
				type: "window",
			})
			.then(
				async <K extends keyof pushNotificationDataMap>(
					clients: readonly WindowClient[],
				) => {
					const data: pushNotificationDataMap[K] = ev.data?.json();

					switch (data.type) {
						// case 'driveFileCreated':
						case "notification":
						case "unreadMessagingMessage":
							// 1日以上経過している場合は無視
							if (new Date().getTime() - data.dateTime > 1000 * 60 * 60 * 24)
								break;

							// クライアントがあったらストリームに接続しているということなので通知しない
							if (clients.length !== 0) break;

							return createNotification(data);
						case "readAllNotifications":
							for (const n of await self.registration.getNotifications()) {
								if (n?.data?.type === "notification") n.close();
							}
							break;
						case "readAllMessagingMessages":
							for (const n of await self.registration.getNotifications()) {
								if (n?.data?.type === "unreadMessagingMessage") n.close();
							}
							break;
						case "readNotifications":
							for (const n of await self.registration.getNotifications()) {
								if (data.body?.notificationIds?.includes(n.data.body.id)) {
									n.close();
								}
							}
							break;
						case "readAllMessagingMessagesOfARoom":
							for (const n of await self.registration.getNotifications()) {
								if (
									n.data.type === "unreadMessagingMessage" &&
									("userId" in data.body
										? data.body.userId === n.data.body.userId
										: data.body.groupId === n.data.body.groupId)
								) {
									n.close();
								}
							}
							break;
					}

					return createEmptyNotification();
				},
			),
	);
});

self.addEventListener(
	"notificationclick",
	<K extends keyof pushNotificationDataMap>(
		ev: ServiceWorkerGlobalScopeEventMap["notificationclick"],
	) => {
		ev.waitUntil(
			(async () => {
				if (_DEV_) {
					console.log("notificationclick", ev.action, ev.notification.data);
				}

				const { action, notification } = ev;
				const data: pushNotificationDataMap[K] = notification.data;
				const { userId: id } = data;
				let client: WindowClient | null = null;

				switch (data.type) {
					case "notification":
						switch (action) {
							case "follow":
								if ("userId" in data.body)
									await swos.api("following/create", id, {
										userId: data.body.userId,
									});
								break;
							case "showUser":
								if ("user" in data.body)
									client = await swos.openUser(getAcct(data.body.user), id);
								break;
							case "reply":
								if ("note" in data.body)
									client = await swos.openPost({ reply: data.body.note }, id);
								break;
							case "renote":
								if ("note" in data.body)
									await swos.api("notes/create", id, {
										renoteId: data.body.note.id,
									});
								break;
							case "accept":
								switch (data.body.type) {
									case "receiveFollowRequest":
										await swos.api("following/requests/accept", id, {
											userId: data.body.userId,
										});
										break;
									case "groupInvited":
										await swos.api("users/groups/invitations/accept", id, {
											invitationId: data.body.invitation.id,
										});
										break;
								}
								break;
							case "reject":
								switch (data.body.type) {
									case "receiveFollowRequest":
										await swos.api("following/requests/reject", id, {
											userId: data.body.userId,
										});
										break;
									case "groupInvited":
										await swos.api("users/groups/invitations/reject", id, {
											invitationId: data.body.invitation.id,
										});
										break;
								}
								break;
							case "showFollowRequests":
								client = await swos.openClient(
									"push",
									"/my/follow-requests",
									id,
								);
								break;
							default:
								switch (data.body.type) {
									case "receiveFollowRequest":
										client = await swos.openClient(
											"push",
											"/my/follow-requests",
											id,
										);
										break;
									case "groupInvited":
										client = await swos.openClient("push", "/my/groups", id);
										break;
									case "reaction":
										client = await swos.openNote(data.body.note.id, id);
										break;
									default:
										if ("note" in data.body) {
											client = await swos.openNote(data.body.note.id, id);
										} else if ("user" in data.body) {
											client = await swos.openUser(getAcct(data.body.user), id);
										}
										break;
								}
						}
						break;
					case "unreadMessagingMessage":
						client = await swos.openChat(data.body, id);
						break;
				}

				if (client) {
					client.focus();
				}
				if (data.type === "notification") {
					swNotificationRead.then((that) => that.read(data));
				}

				notification.close();
			})(),
		);
	},
);

self.addEventListener(
	"notificationclose",
	<K extends keyof pushNotificationDataMap>(
		ev: ServiceWorkerGlobalScopeEventMap["notificationclose"],
	) => {
		const data: pushNotificationDataMap[K] = ev.notification.data;

		if (data.type === "notification") {
			swNotificationRead.then((that) => that.read(data));
		}
	},
);

self.addEventListener(
	"message",
	(ev: ServiceWorkerGlobalScopeEventMap["message"]) => {
		ev.waitUntil(
			(async () => {
				switch (ev.data) {
					case "clear":
						// Cache Storage全削除
						await caches
							.keys()
							.then((cacheNames) =>
								Promise.all(cacheNames.map((name) => caches.delete(name))),
							);
						return; // TODO
				}

				if (typeof ev.data === "object") {
					// E.g. '[object Array]' → 'array'
					const otype = Object.prototype.toString
						.call(ev.data)
						.slice(8, -1)
						.toLowerCase();

					if (otype === "object") {
						if (ev.data.msg === "initialize") {
							swLang.setLang(ev.data.lang);
						}
					}
				}
			})(),
		);
	},
);
