<template>
	<MkModal
		ref="modal"
		:prefer-type="'dialog'"
		@click="done(true)"
		@closed="emit('closed')"
	>
		<div :class="$style.root">
			<div v-if="icon" :class="$style.icon">
				<i :class="icon"></i>
			</div>
			<div
				v-else-if="!input && !select"
				:class="[$style.icon, $style[`type_${type}`]]"
			>
				<i
					v-if="type === 'success'"
					:class="[$style.iconInner, iconClass('ph-check')]"
				></i>
				<i
					v-else-if="type === 'error'"
					:class="[
						$style.iconInner,
						iconClass('ph-circle-wavy-warning'),
					]"
				></i>
				<i
					v-else-if="type === 'warning'"
					:class="[$style.iconInner, iconClass('ph-warning')]"
				></i>
				<i
					v-else-if="type === 'info'"
					:class="[$style.iconInner, iconClass('ph-info')]"
				></i>
				<i
					v-else-if="type === 'question'"
					:class="[$style.iconInner, iconClass('ph-question')]"
				></i>
				<MkLoading
					v-else-if="type === 'waiting'"
					:class="$style.iconInner"
					:em="true"
				/>
			</div>
			<header v-if="title" :class="$style.title">
				<Mfm :text="title" />
			</header>
			<header
				v-if="title == null && input && input.type === 'password'"
				:class="$style.title"
			>
				<Mfm :text="i18n.ts.password" />
			</header>
			<div v-if="text" :class="$style.text">
				<span
					v-if="isPlaintext === true"
					style="white-space: pre-line"
					>{{ text }}</span
				>
				<Mfm v-else :text="text" />
			</div>
			<MkInput
				v-if="input && input.type !== 'paragraph'"
				ref="inputEl"
				v-model="inputValue"
				autofocus
				:autocomplete="input.autocomplete"
				:type="input.type == 'search' ? 'search' : input.type || 'text'"
				:placeholder="input.placeholder || undefined"
				:style="{
					width: input.type === 'search' ? '300px' : null,
				}"
				@keydown="onInputKeydown"
			>
				<template v-if="input.type === 'password'" #prefix
					><i :class="iconClass('ph-password')"></i
				></template>
				<template #caption>
					<span
						v-if="
							okButtonDisabled &&
							disabledReason === 'charactersExceeded'
						"
						v-text="
							i18n.t('_dialog.charactersExceeded', {
								current: (inputValue as string).length,
								max: input.maxLength ?? 'NaN',
							})
						"
					/>
					<span
						v-else-if="
							okButtonDisabled &&
							disabledReason === 'charactersBelow'
						"
						v-text="
							i18n.t('_dialog.charactersBelow', {
								current: (inputValue as string).length,
								min: input.minLength ?? 'NaN',
							})
						"
					/>
				</template>
				<template v-if="input.type === 'search'" #suffix>
					<button
						v-tooltip.noDelay="i18n.ts.filter"
						class="_buttonIcon"
						@click.stop="openSearchFilters"
					>
						<i :class="iconClass('ph-funnel', false)"></i>
					</button>
				</template>
			</MkInput>
			<MkTextarea
				v-if="input && input.type === 'paragraph'"
				v-model="inputValue"
				autofocus
				type="paragraph"
				:placeholder="input.placeholder || undefined"
			>
			</MkTextarea>
			<MkSelect v-if="select" v-model="selectedValue" autofocus>
				<template v-if="select.items">
					<option v-for="item in select.items" :value="item.value">
						{{ item.text }}
					</option>
				</template>
				<template v-else>
					<optgroup
						v-for="groupedItem in select.groupedItems"
						:label="groupedItem.label"
					>
						<option
							v-for="item in groupedItem.items"
							:value="item.value"
						>
							{{ item.text }}
						</option>
					</optgroup>
				</template>
			</MkSelect>

			<div
				v-if="(showOkButton || showCancelButton) && !actions"
				:class="$style.buttons"
			>
				<div v-if="!isYesNo">
					<MkButton
						v-if="showOkButton"
						inline
						primary
						:autofocus="!input && !select"
						:disabled="okButtonDisabled"
						@click="ok"
						>{{
							showCancelButton || input || select
								? i18n.ts.ok
								: i18n.ts.gotIt
						}}</MkButton
					>
					<MkButton
						v-if="showCancelButton || input || select"
						inline
						@click="cancel"
						>{{ i18n.ts.cancel }}</MkButton
					>
				</div>
				<div v-else>
					<MkButton
						v-if="showOkButton"
						inline
						primary
						:autofocus="!input && !select"
						@click="ok"
						>{{ i18n.ts.yes }}
					</MkButton>
					<MkButton
						v-if="showCancelButton || input || select"
						inline
						@click="cancel"
						>{{ i18n.ts.no }}</MkButton
					>
				</div>
			</div>
			<div v-if="actions" :class="$style.buttons">
				<MkButton
					v-for="action in actions"
					:key="action.text"
					inline
					:primary="action.primary"
					@click="
						() => {
							action.callback();
							modal?.close(null);
						}
					"
					>{{ action.text }}</MkButton
				>
			</div>
		</div>
	</MkModal>
</template>

<script lang="ts" setup>
import { computed, onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
import * as Acct from "firefish-js/built/acct";
import MkModal from "@/components/MkModal.vue";
import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue";
import MkTextarea from "@/components/form/textarea.vue";
import MkSelect from "@/components/form/select.vue";
import * as os from "@/os";
import { i18n } from "@/i18n";
import iconClass from "@/scripts/icon";

interface Input {
	type: HTMLInputElement["type"];
	placeholder?: string | null;
	autocomplete?: string;
	default: string | number | null;
	minLength?: number;
	maxLength?: number;
}

interface Select {
	items: {
		value: string;
		text: string;
	}[];
	groupedItems: {
		label: string;
		items: {
			value: string;
			text: string;
		}[];
	}[];
	default: string | null;
}

const props = withDefaults(
	defineProps<{
		type?:
			| "success"
			| "error"
			| "warning"
			| "info"
			| "question"
			| "waiting"
			| "search";
		title: string;
		text?: string;
		isPlaintext?: boolean;
		input?: Input;
		select?: Select;
		icon?: string;
		actions?: {
			text: string;
			primary?: boolean;
			callback: (...args: any[]) => void;
		}[];
		showOkButton?: boolean;
		showCancelButton?: boolean;
		isYesNo?: boolean;

		cancelableByBgClick?: boolean;
		okText?: string;
		cancelText?: string;
	}>(),
	{
		type: "info",
		showOkButton: true,
		showCancelButton: false,
		isYesNo: false,

		cancelableByBgClick: true,
		isPlaintext: false,
	},
);

const emit = defineEmits<{
	(ev: "done", v: { canceled: boolean; result: any }): void;
	(ev: "closed"): void;
}>();

const modal = shallowRef<InstanceType<typeof MkModal>>();

const inputValue = ref<string | number | null>(props.input?.default ?? null);
const selectedValue = ref(props.select?.default ?? null);

const disabledReason = ref<null | "charactersExceeded" | "charactersBelow">(
	null,
);
const okButtonDisabled = computed<boolean>(() => {
	if (props.input) {
		if (props.input.minLength) {
			if (
				(inputValue.value || inputValue.value === "") &&
				(inputValue.value as string).length < props.input.minLength
			) {
				disabledReason.value = "charactersBelow";
				return true;
			}
		}
		if (props.input.maxLength) {
			if (
				inputValue.value &&
				(inputValue.value as string).length > props.input.maxLength
			) {
				disabledReason.value = "charactersExceeded";
				return true;
			}
		}
	}
	return false;
});

const inputEl = ref<typeof MkInput>();

function done(canceled: boolean, result?) {
	emit("done", { canceled, result });
	modal.value?.close(null);
}

async function ok() {
	if (!props.showOkButton) return;

	const result = props.input
		? inputValue.value
		: props.select
		  ? selectedValue.value
		  : true;
	done(false, result);
}

function cancel() {
	done(true);
}
/*
function onBgClick() {
	if (props.cancelableByBgClick) cancel();
}
*/
function onKeydown(evt: KeyboardEvent) {
	if (evt.key === "Escape") cancel();
}

function onInputKeydown(evt: KeyboardEvent) {
	if (evt.key === "Enter") {
		evt.preventDefault();
		evt.stopPropagation();
		ok();
	}
}

function formatDateToYYYYMMDD(date) {
	const year = date.getFullYear();
	const month = ("0" + (date.getMonth() + 1)).slice(-2);
	const day = ("0" + (date.getDate() + 1)).slice(-2);
	return `${year}-${month}-${day}`;
}

/**
 * Appends a new search parameter to the value in the input field.
 * Trims any extra whitespace before and after, then adds a space at the end so a user can immediately
 * begin typing a new criteria.
 * @param value The value to append.
 */
function appendFilter(value: string) {
	return (
		[
			typeof inputValue.value === "string"
				? inputValue.value.trim()
				: inputValue.value,
			value,
		]
			.join(" ")
			.trim() + " "
	);
}

async function openSearchFilters(ev) {
	await os.popupMenu(
		[
			{
				icon: `${iconClass("ph-user")}`,
				text: i18n.ts._filters.fromUser,
				action: () => {
					os.selectUser().then((user) => {
						inputValue.value = appendFilter(
							"from:@" + Acct.toString(user),
						);
					});
				},
			},
			{
				type: "parent",
				text: i18n.ts._filters.withFile,
				icon: `${iconClass("ph-paperclip")}`,
				children: [
					{
						text: i18n.ts.image,
						icon: `${iconClass("ph-image-square")}`,
						action: () => {
							inputValue.value = appendFilter("has:image");
						},
					},
					{
						text: i18n.ts.video,
						icon: `${iconClass("ph-video-camera")}`,
						action: () => {
							inputValue.value = appendFilter("has:video");
						},
					},
					{
						text: i18n.ts.audio,
						icon: `${iconClass("ph-music-note")}`,
						action: () => {
							inputValue.value = appendFilter("has:audio");
						},
					},
					{
						text: i18n.ts.file,
						icon: `${iconClass("ph-file")}`,
						action: () => {
							inputValue.value = appendFilter("has:file");
						},
					},
				],
			},
			{
				icon: `${iconClass("ph-link")}`,
				text: i18n.ts._filters.fromDomain,
				action: () => {
					inputValue.value = appendFilter("domain:");
				},
			},
			{
				icon: `${iconClass("ph-calendar-blank")}`,
				text: i18n.ts._filters.notesBefore,
				action: () => {
					os.inputDate({
						title: i18n.ts._filters.notesBefore,
					}).then((res) => {
						if (res.canceled) return;
						inputValue.value = appendFilter(
							"before:" + formatDateToYYYYMMDD(res.result),
						);
					});
				},
			},
			{
				icon: `${iconClass("ph-calendar-blank")}`,
				text: i18n.ts._filters.notesAfter,
				action: () => {
					os.inputDate({
						title: i18n.ts._filters.notesAfter,
					}).then((res) => {
						if (res.canceled) return;
						inputValue.value = appendFilter(
							"after:" + formatDateToYYYYMMDD(res.result),
						);
					});
				},
			},
			{
				icon: `${iconClass("ph-eye")}`,
				text: i18n.ts._filters.followingOnly,
				action: () => {
					inputValue.value = appendFilter("filter:following");
				},
			},
			{
				icon: `${iconClass("ph-users-three")}`,
				text: i18n.ts._filters.followersOnly,
				action: () => {
					inputValue.value = appendFilter("filter:followers");
				},
			},
		],
		ev.target,
		{ noReturnFocus: true },
	);
	inputEl.value?.focus();
	if (typeof inputValue.value === "string") {
		inputEl.value?.selectRange(
			inputValue.value.length,
			inputValue.value.length,
		); // cursor at end
	}
}

onMounted(() => {
	document.addEventListener("keydown", onKeydown);
});

onBeforeUnmount(() => {
	document.removeEventListener("keydown", onKeydown);
});
</script>

<style lang="scss" module>
.root {
	position: relative;
	margin: auto;
	padding: 32px;
	min-width: 320px;
	max-width: 480px;
	box-sizing: border-box;
	text-align: center;
	background: var(--panel);
	border-radius: var(--radius);
}

.icon {
	font-size: 24px;

	& + .title {
		margin-top: 8px;
	}
}

.iconInner {
	display: block;
	margin: 0 auto;
}

.type_info {
	color: var(--accent);
}

.type_success {
	color: var(--success);
}

.type_error {
	color: var(--error);
}

.type_warning {
	color: var(--warn);
}

.title {
	margin: 0 0 8px 0;
	font-weight: bold;
	font-size: 1.1em;

	& + .text {
		margin-top: 8px;
	}
}

.text {
	margin: 16px 0 0 0;
}

.buttons {
	margin-top: 16px;
	display: flex;
	gap: 8px;
	flex-wrap: wrap;
	justify-content: center;
}
</style>
