<template>
	<time :title="absolute">
		<template v-if="invalid">{{ i18n.ts._ago.invalid }}</template>
		<template v-else-if="mode === 'relative'">{{ relative }}</template>
		<template v-else-if="mode === 'absolute'">{{ absolute }}</template>
		<template v-else-if="mode === 'detail'"
			>{{ absolute }} ({{ relative }})</template
		>
	</time>
</template>

<script lang="ts" setup>
import { onMounted, onUnmounted, ref, computed } from 'vue';
import { i18n } from "@/i18n";
import { dateTimeFormat } from "@/scripts/intl-const";

const props = withDefaults(
	defineProps<{
		time: Date | string | number | null;
		origin?: Date | null;
		mode?: "relative" | "absolute" | "detail";
	}>(),
	{
		mode: "relative",
	},
);

const _time =
	props.time == null
		? NaN
		: typeof props.time === "number"
		? props.time
		: (props.time instanceof Date
				? props.time
				: new Date(props.time)
		  ).getTime();
const invalid = Number.isNaN(_time);
const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid;

let now = ref((props.origin ?? new Date()).getTime());

const SECOND = 1;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const WEEK = 7 * DAY;
const MONTH = 30 * DAY;
const YEAR = 365 * DAY;

function elapsed(ago: number, unit: number) {
	return Math.floor(ago / unit).toString();
}

function remainder(ago: number, unit: number, parentUnit: number) {
	return Math.floor((ago % parentUnit) / unit).toString();
}

const relative = computed<string>(() => {
	if (props.mode === "absolute") return ""; // absoluteではrelativeを使わないので計算しない
	if (invalid) return i18n.ts._ago.invalid;

	const ago = Math.floor((now.value - _time) / 1000);
	if (ago >= YEAR) {
		return i18n.t("_ago.yearsAgo", {
			n: elapsed(ago, YEAR),
			n2: remainder(ago, MONTH, YEAR),
		});
	}
	if (ago >= MONTH) {
		return i18n.t("_ago.monthsAgo", {
			n: elapsed(ago, MONTH),
			n2: remainder(ago, WEEK, MONTH),
		});
	}
	if (ago >= WEEK) {
		return i18n.t("_ago.weeksAgo", {
			n: elapsed(ago, WEEK),
			n2: remainder(ago, DAY, WEEK),
		});
	}
	if (ago >= DAY) {
		return i18n.t("_ago.daysAgo", {
			n: elapsed(ago, DAY),
			n2: remainder(ago, HOUR, DAY),
		});
	}
	if (ago >= HOUR) {
		return i18n.t("_ago.hoursAgo", {
			n: elapsed(ago, HOUR),
			n2: remainder(ago, MINUTE, HOUR),
		});
	}
	if (ago >= MINUTE) {
		return i18n.t("_ago.minutesAgo", {
			n: elapsed(ago, MINUTE),
			n2: remainder(ago, SECOND, MINUTE),
		});
	}
	if (ago >= 10) return i18n.t("_ago.secondsAgo", { n: (ago % MINUTE).toString() });
	if (ago >= -1) return i18n.ts._ago.justNow;
	return i18n.ts._ago.future;
});

let tickId: number;

function tick() {
	const _now = new Date().getTime();
	const agoPrev = (now.value - _time) / 1000; // 現状のinterval

	now.value = _now;

	const ago = (now.value - _time) / 1000; // 次のinterval
	const getInterval = (ago: number) =>
		ago < HOUR
			? 1000
			: ago < DAY
			? 60000
			: ago < WEEK
			? 3600000
			: 86400000;
	const prev = getInterval(agoPrev);
	const next = getInterval(ago);

	if (!tickId) {
		tickId = window.setInterval(tick, next);
	} else if (prev < next) {
		window.clearInterval(tickId);
		tickId = window.setInterval(tick, next);
	}
}

if (
	!invalid &&
	!props.origin &&
	(props.mode === "relative" || props.mode === "detail")
) {
	onMounted(() => {
		tick();
	});
	onUnmounted(() => {
		if (tickId) window.clearInterval(tickId);
	});
}
</script>
