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

<template>
<div>
	<MkStickyContainer>
		<template #header><XHeader :tabs="headerTabs"/></template>
		<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
			<div class="_gaps">
				<MkInfo warn :class="$style.warn">{{ i18n.ts.clientSettingOverridesWarn }}</MkInfo>
				<div v-if="fetching">
					<MkLoading/>
				</div>
				<div v-else class="_gaps_s">
					<MkInput v-model="query" type="search">
						<template #prefix><i class="ti ti-search"></i></template>
					</MkInput>

					<MkFolder
						v-for="def, key in clientSettingOverrides"
						:key="key"
						v-show="query === '' || key.toLowerCase().includes(query.toLowerCase())"
					>
						<template #label>{{ key }}</template>
						<template #suffix>
							<span v-if="def.enableOverride && def.overrideValue != null && def.overrideValue !== def.defaultValue" class="_warn">{{ i18n.ts.modified }}</span>
						</template>
						<div class="_gaps">
							<MkKeyValue>
								<template #key>{{ i18n.ts.default }}</template>
								<template #value>
									<MkCode v-bind="getMkCodeProps(def)"></MkCode>
								</template>
							</MkKeyValue>
							<MkSwitch v-model="def.enableOverride">{{ i18n.ts.enableOverride }}</MkSwitch>
							<MkInput v-if="def.formType === 'text'" v-model="def.overrideValue" :disabled="!def.enableOverride" type="text">
								<template #label>{{ i18n.ts.overrideValue }}</template>
							</MkInput>
							<MkInput v-else-if="def.formType === 'number'" v-model="def.overrideValue" :disabled="!def.enableOverride" type="number">
								<template #label>{{ i18n.ts.overrideValue }}</template>
							</MkInput>
							<MkSwitch v-else-if="def.formType === 'boolean'" v-model="def.overrideValue" :disabled="!def.enableOverride">
								<template #label>{{ i18n.ts.overrideValue }}</template>
								<template #caption>{{ i18n.ts.onToTrue }}</template>
							</MkSwitch>
							<MkTextarea v-else-if="def.formType === 'codeEditor'" v-model="def.overrideValue" :disabled="!def.enableOverride" pre>
								<template #label>{{ i18n.ts.overrideValue }}</template>
							</MkTextarea>
						</div>
					</MkFolder>
				</div>
			</div>
		</MkSpacer>
		<template #footer>
			<div :class="$style.footer">
				<div :class="$style.footerInner">
					<div class="_buttons">
						<MkButton primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
						<MkButton danger @click="reset"><i class="ti ti-trash"></i> {{ i18n.ts.reset }}</MkButton>
					</div>
				</div>
			</div>
		</template>
	</MkStickyContainer>
</div>
</template>

<script lang="ts" setup>
import { ref, computed } from 'vue';
import XHeader from './_header_.vue';
import * as os from '@/os.js';
import { ColdDeviceStorage, defaultStore } from '@/store.js';
import { pruneInstanceCache } from '@/instance.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkInfo from '@/components/MkInfo.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkCode from '@/components/MkCode.vue';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { reloadAsk } from '@/scripts/reload-ask.js';
import MkSwitch from '@/components/MkSwitch.vue';
import MkTextarea from '@/components/MkTextarea.vue';

const query = ref('');

const notConfigurableDefaultStoreSettings = [
	'accountSetupWizard',
	'timelineTutorials',
	'abusesTutorial',
	'memo',
	'mutedAds',
	'statusbars',
	'widgets',
	'pinnedUserLists',
	'recentlyUsedEmojis',
	'recentlyUsedUsers',
	'forceShowAds',
	'additionalUnicodeEmojiIndexes',
	'themeInitial',

	// 光過敏性対策のためあえて鯖管に設定させない
	'animation',
	'animatedMfm',
	'disableShowingAnimatedImages'
] satisfies (keyof typeof defaultStore.def)[];

const notConfigurableColdDeviceStorageSettings = [
	'darkTheme',
	'lightTheme',
	'plugins',
] satisfies (keyof typeof ColdDeviceStorage.default)[];

type ClientSettingOverridesUIDefObj = {
	formType: 'text' | 'number' | 'boolean' | 'codeEditor';
	enableOverride: boolean;
	defaultValue: any;
	overrideValue?: any;
}

const fetching = ref(true);
const clientSettingOverrides = ref<Record<string, ClientSettingOverridesUIDefObj>>();

function getMkCodeProps(def: ClientSettingOverridesUIDefObj) {
	if (typeof def.defaultValue === 'string') {
		return {
			code: def.defaultValue,
			forceShow: true,
		};
	} else {
		return {
			code: JSON.stringify(def.defaultValue, null, 4),
			lang: 'json',
			forceShow: true,
		};
	}
}

function typeSafeObjectEntries<T extends Record<string, any>>(obj: T) {
	return Object.entries(obj) as [keyof T, T[keyof T]][];
}

function getClientSettingOverridesUIDefObj(def: unknown): ClientSettingOverridesUIDefObj {
	return {
		formType: (() => {
			if (typeof def === 'boolean') {
				return 'boolean';
			} else if (typeof def === 'number') {
				return 'number';
			} else if (typeof def === 'object') {
				return 'codeEditor';
			} else {
				return 'text';
			}
		})() satisfies ClientSettingOverridesUIDefObj['formType'] as ClientSettingOverridesUIDefObj['formType'],
		enableOverride: false,
		defaultValue: def,
		overrideValue: def,
	};
}

async function fetch() {
	fetching.value = true;
	const overrideDefs = Object.fromEntries([
		...typeSafeObjectEntries(defaultStore.def)
			.filter(([key, _]) => !(notConfigurableDefaultStoreSettings as string[]).includes(key))
			.map(([key, def]) => [`defaultStore::${key}`, getClientSettingOverridesUIDefObj(def.default)]),
		...typeSafeObjectEntries(ColdDeviceStorage.default)
			.filter(([key, _]) => !(notConfigurableColdDeviceStorageSettings as string[]).includes(key))
			.map(([key, def]) => [`ColdDeviceStorage::${key}`, getClientSettingOverridesUIDefObj(def)]),
	]);
	const res = await misskeyApi('admin/meta');
	if (res.defaultClientSettingOverrides != null) {
		try {
			const parsed = JSON.parse(res.defaultClientSettingOverrides);
			for (const key in parsed) {
				if (key in overrideDefs) {
					overrideDefs[key].enableOverride = true;
					overrideDefs[key].overrideValue = parsed[key];
				}
			}
		} catch (e) {
			console.error(e);
		}
	}

	clientSettingOverrides.value = overrideDefs;
	fetching.value = false;
}

async function save() {
	if (clientSettingOverrides.value == null) return;

	const overrides = Object.fromEntries(
		typeSafeObjectEntries(clientSettingOverrides.value)
			.filter(([key, def]) => (
				def.enableOverride &&
				def.overrideValue !== def.defaultValue && (
					(typeof def.defaultValue === 'string' && typeof def.overrideValue === 'string' && def.overrideValue !== def.defaultValue) ||
					(typeof def.defaultValue === 'object' && typeof def.overrideValue === 'string' && JSON.stringify(def.overrideValue) !== JSON.stringify(def.defaultValue)) ||
					(typeof def.defaultValue !== 'string' && typeof def.overrideValue === 'string' && def.overrideValue !== JSON.stringify(def.defaultValue))
				)
			))
			.map(([key, def]) => [key, typeof def.overrideValue === 'string' && typeof def.defaultValue !== 'string' ? JSON.parse(def.overrideValue) : def.overrideValue])
	);

	let defaultClientSettingOverrides: string | null = JSON.stringify(overrides);

	if (Object.keys(overrides).length === 0) {
		defaultClientSettingOverrides = null;
	}

	await os.apiWithDialog('admin/update-meta', {
		defaultClientSettingOverrides,
	});

	await fetch();
	pruneInstanceCache();
	await reloadAsk({ reason: i18n.ts.reloadToApplySetting });
}

async function reset() {
	const { canceled } = await os.confirm({
		type: 'warning',
		text: i18n.ts.resetAreYouSure,
	});

	if (canceled) return;

	await os.apiWithDialog('admin/update-meta', {
		defaultClientSettingOverrides: null,
	});

	await fetch();
}

fetch();

const headerActions = computed(() => []);

const headerTabs = computed(() => []);

definePageMetadata(() => ({
	title: i18n.ts.clientSettingOverrides,
	icon: 'ti ti-checkbox',
}));
</script>

<style lang="scss" module>
.warn {
	white-space: pre-wrap;
}

.footer {
	backdrop-filter: var(--MI-blur, blur(15px));
	background: var(--MI_THEME-acrylicBg);
	border-top: solid .5px var(--MI_THEME-divider);
}

.footerInner {
	max-width: 700px;
	margin: 0 auto;
	padding: 16px;
}
</style>