<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<div :class="$style.wrapper">
	<Transition
		mode="out-in"
		:enterActiveClass="$style.transition_enterActive"
		:leaveActiveClass="$style.transition_leaveActive"
		:enterFromClass="$style.transition_enterFrom"
		:leaveToClass="$style.transition_leaveTo"

		:inert="_waiting"
	>
		<div v-if="phase === 'accountSelect'" key="accountSelect" :class="$style.root" class="_gaps">
			<div :class="$style.header" class="_gaps_s">
				<div :class="$style.iconFallback">
					<i class="ti ti-user"></i>
				</div>
				<div :class="$style.headerText">{{ i18n.ts.pleaseSelectAccount }}</div>
			</div>
			<div>
				<div :class="$style.accountSelectorLabel">{{ i18n.ts.selectAccount }}</div>
				<div :class="$style.accountSelectorList">
					<template v-for="[id, user] in users">
						<input :id="'account-' + id" v-model="selectedUser" type="radio" name="accountSelector" :value="id" :class="$style.accountSelectorRadio"/>
						<label :for="'account-' + id" :class="$style.accountSelectorItem">
							<MkAvatar :user="user" :class="$style.accountSelectorAvatar"/>
							<div :class="$style.accountSelectorBody">
								<MkUserName :user="user" :class="$style.accountSelectorName"/>
								<MkAcct :user="user" :class="$style.accountSelectorAcct"/>
							</div>
						</label>
					</template>
					<button class="_button" :class="[$style.accountSelectorItem, $style.accountSelectorAddAccountRoot]" @click="clickAddAccount">
						<div :class="[$style.accountSelectorAvatar, $style.accountSelectorAddAccountAvatar]">
							<i class="ti ti-user-plus"></i>
						</div>
						<div :class="[$style.accountSelectorBody, $style.accountSelectorName]">{{ i18n.ts.addAccount }}</div>
					</button>
				</div>
			</div>
			<div class="_buttonsCenter">
				<MkButton rounded gradate :disabled="selectedUser === null" @click="clickChooseAccount">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
			</div>
		</div>
		<div v-else-if="phase === 'consent'" key="consent" :class="$style.root" class="_gaps">
			<div :class="$style.header" class="_gaps_s">
				<img v-if="icon" :class="$style.icon" :src="getProxiedImageUrl(icon, 'preview')"/>
				<div v-else :class="$style.iconFallback">
					<i class="ti ti-apps"></i>
				</div>
				<div :class="$style.headerText">{{ name ? i18n.tsx._auth.shareAccess({ name }) : i18n.ts._auth.shareAccessAsk }}</div>
			</div>
			<div v-if="permissions && permissions.length > 0" class="_gaps_s" :class="$style.permissionRoot">
				<div>{{ name ? i18n.tsx._auth.permission({ name }) : i18n.ts._auth.permissionAsk }}</div>
				<div :class="$style.permissionListWrapper">
					<ul :class="$style.permissionList">
						<li v-for="p in permissions" :key="p">{{ i18n.ts._permissions[p] }}</li>
					</ul>
				</div>
			</div>
			<slot name="consentAdditionalInfo"></slot>
			<div>
				<div :class="$style.accountSelectorLabel">
					{{ i18n.ts._auth.scopeUser }} <button class="_textButton" @click="clickBackToAccountSelect">{{ i18n.ts.switchAccount }}</button>
				</div>
				<div :class="$style.accountSelectorList">
					<div :class="[$style.accountSelectorItem, $style.static]">
						<MkAvatar :user="users.get(selectedUser!)!" :class="$style.accountSelectorAvatar"/>
						<div :class="$style.accountSelectorBody">
							<MkUserName :user="users.get(selectedUser!)!" :class="$style.accountSelectorName"/>
							<MkAcct :user="users.get(selectedUser!)!" :class="$style.accountSelectorAcct"/>
						</div>
					</div>
				</div>
			</div>
			<div class="_buttonsCenter">
				<MkButton rounded @click="clickCancel">{{ i18n.ts.reject }}</MkButton>
				<MkButton rounded gradate @click="clickAccept">{{ i18n.ts.accept }}</MkButton>
			</div>
		</div>
		<div v-else-if="phase === 'success'" key="success" :class="$style.root" class="_gaps_s">
			<div :class="$style.header" class="_gaps_s">
				<div :class="$style.iconFallback">
					<i class="ti ti-check"></i>
				</div>
				<div :class="$style.headerText">{{ i18n.ts._auth.accepted }}</div>
				<div :class="$style.headerTextSub">{{ i18n.ts._auth.pleaseGoBack }}</div>
			</div>
		</div>
		<div v-else-if="phase === 'denied'" key="denied" :class="$style.root" class="_gaps_s">
			<div :class="$style.header" class="_gaps_s">
				<div :class="$style.iconFallback">
					<i class="ti ti-x"></i>
				</div>
				<div :class="$style.headerText">{{ i18n.ts._auth.denied }}</div>
			</div>
		</div>
		<div v-else-if="phase === 'failed'" key="failed" :class="$style.root" class="_gaps_s">
			<div :class="$style.header" class="_gaps_s">
				<div :class="$style.iconFallback">
					<i class="ti ti-x"></i>
				</div>
				<div :class="$style.headerText">{{ i18n.ts.somethingHappened }}</div>
			</div>
		</div>
	</Transition>
	<div v-if="_waiting" :class="$style.waitingRoot">
		<MkLoading/>
	</div>
</div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import { $i } from '@/i.js';
import { getAccounts, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/accounts.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
import { misskeyApi } from '@/utility/misskey-api.js';

const props = defineProps<{
	name?: string;
	icon?: string;
	permissions?: (typeof Misskey.permissions[number])[];
	manualWaiting?: boolean;
	waitOnDeny?: boolean;
}>();

const emit = defineEmits<{
	(ev: 'accept', token: string): void;
	(ev: 'deny', token: string): void;
}>();

const waiting = ref(true);
const _waiting = computed(() => waiting.value || props.manualWaiting);
const phase = ref<'accountSelect' | 'consent' | 'success' | 'denied' | 'failed'>('accountSelect');

const selectedUser = ref<string | null>(null);

const users = ref(new Map<string, Misskey.entities.UserDetailed & { token: string; }>());

async function init() {
	waiting.value = true;

	users.value.clear();

	if ($i) {
		users.value.set($i.id, $i);
	}

	const accounts = await getAccounts();

	const accountIdsToFetch = accounts.map(a => a.id).filter(id => !users.value.has(id));

	if (accountIdsToFetch.length > 0) {
		const usersRes = await misskeyApi('users/show', {
			userIds: accountIdsToFetch,
		});

		for (const user of usersRes) {
			if (users.value.has(user.id)) continue;

			const account = accounts.find(a => a.id === user.id);

			if (!account || account.token == null) continue;

			users.value.set(user.id, {
				...user,
				token: account.token,
			});
		}
	}

	waiting.value = false;
}

init();

function clickAddAccount(ev: MouseEvent) {
	selectedUser.value = null;

	os.popupMenu([{
		text: i18n.ts.existingAccount,
		action: () => {
			getAccountWithSigninDialog().then(async (res) => {
				if (res != null) {
					os.success();
					await init();
					if (users.value.has(res.id)) {
						selectedUser.value = res.id;
					}
				}
			});
		},
	}, {
		text: i18n.ts.createAccount,
		action: () => {
			getAccountWithSignupDialog().then(async (res) => {
				if (res != null) {
					os.success();
					await init();
					if (users.value.has(res.id)) {
						selectedUser.value = res.id;
					}
				}
			});
		},
	}], ev.currentTarget ?? ev.target);
}

function clickChooseAccount() {
	if (selectedUser.value === null) return;

	phase.value = 'consent';
}

function clickBackToAccountSelect() {
	selectedUser.value = null;
	phase.value = 'accountSelect';
}

function clickCancel() {
	if (selectedUser.value === null) return;

	const user = users.value.get(selectedUser.value)!;

	const token = user.token;

	if (props.waitOnDeny) {
		waiting.value = true;
	}
	emit('deny', token);
}

async function clickAccept() {
	if (selectedUser.value === null) return;

	const user = users.value.get(selectedUser.value)!;

	const token = user.token;

	waiting.value = true;
	emit('accept', token);
}

function showUI(state: 'success' | 'denied' | 'failed') {
	phase.value = state;
	waiting.value = false;
}

defineExpose({
	showUI,
});
</script>

<style lang="scss" module>
.transition_enterActive,
.transition_leaveActive {
	transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1);
}
.transition_enterFrom {
	opacity: 0;
	transform: translateX(50px);
}
.transition_leaveTo {
	opacity: 0;
	transform: translateX(-50px);
}

.wrapper {
	overflow-x: hidden;
	overflow-x: clip;

	position: relative;
	width: 100%;
	height: 100%;
}

.waitingRoot {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	background-color: color-mix(in srgb, var(--MI_THEME-panel), transparent 50%);
	display: flex;
	justify-content: center;
	align-items: center;
	z-index: 1;
	cursor: wait;
}

.root {
	position: relative;
	box-sizing: border-box;
	width: 100%;
	padding: 48px 24px;
}

.header {
	margin: 0 auto;
	max-width: 320px;
}

.icon,
.iconFallback {
	display: block;
	margin: 0 auto;
	width: 54px;
	height: 54px;
}

.icon {
	border-radius: 50%;
	border: 1px solid var(--MI_THEME-divider);
	background-color: #fff;
	object-fit: contain;
}

.iconFallback {
	border-radius: 50%;
	background-color: var(--MI_THEME-accentedBg);
	color: var(--MI_THEME-accent);
	text-align: center;
	line-height: 54px;
	font-size: 18px;
}

.headerText,
.headerTextSub {
	text-align: center;
	word-break: normal;
	word-break: auto-phrase;
}

.headerText {
	font-size: 16px;
	font-weight: 700;
}

.permissionRoot {
	padding: 16px;
	border-radius: var(--MI-radius);
	background-color: var(--MI_THEME-bg);
}

.permissionListWrapper {
	max-height: 350px;
	overflow-y: auto;
	padding: 12px;
	border-radius: var(--MI-radius);
	background-color: var(--MI_THEME-panel);
}

.permissionList {
	margin: 0 0 0 1.5em;
	padding: 0;
	font-size: 90%;
}

.accountSelectorLabel {
	font-size: 0.85em;
	opacity: 0.7;
	margin-bottom: 8px;
}

.accountSelectorList {
	border-radius: var(--MI-radius);
	border: 1px solid var(--MI_THEME-divider);
	overflow: hidden;
	overflow: clip;
}

.accountSelectorRadio {
	position: absolute;
	clip: rect(0, 0, 0, 0);
	pointer-events: none;

	&:focus-visible + .accountSelectorItem {
		outline: 2px solid var(--MI_THEME-accent);
		outline-offset: -4px;
	}

	&:checked:focus-visible + .accountSelectorItem {
		outline-color: #fff;
	}

	&:checked + .accountSelectorItem {
		background: var(--MI_THEME-accent);
		color: #fff;
	}
}

.accountSelectorItem {
	display: flex;
	align-items: center;
	padding: 8px;
	font-size: 14px;
	-webkit-tap-highlight-color: transparent;
	cursor: pointer;

	&:hover {
		background: var(--MI_THEME-buttonHoverBg);
	}

	&.static {
		cursor: unset;

		&:hover {
			background: none;
		}
	}
}

.accountSelectorAddAccountRoot {
	width: 100%;
}

.accountSelectorBody {
	padding: 0 8px;
	min-width: 0;
}

.accountSelectorAvatar {
	width: 45px;
	height: 45px;
}

.accountSelectorAddAccountAvatar {
	background-color: var(--MI_THEME-accentedBg);
	color: var(--MI_THEME-accent);
	font-size: 16px;
	line-height: 45px;
	text-align: center;
	border-radius: 50%;
}

.accountSelectorName {
	display: block;
	font-weight: bold;
}

.accountSelectorAcct {
	opacity: 0.5;
}
</style>
