/**
 * Timezone-aware date/time formatting.
 *
 * Every call site in the app should format through this module — the point of
 * the user-selected timezone is defeated if any page slips back to the raw
 * `toLocaleString()` which silently uses the browser zone.
 *
 * All functions accept the same input shapes: an ISO string, a `Date`, or a
 * unix timestamp (seconds since epoch — matches Docker API). Falsy inputs
 * return an em dash placeholder so callers don't need a guard.
 */

import { derived, get, type Readable } from 'svelte/store';
import { effectiveTimezone } from '$lib/stores/timezone';
import { locale } from '$lib/i18n';

export type DateInput = string | number | Date | null | undefined;

const PLACEHOLDER = '—';

/**
 * Backend emits `store.Now()` as `"2006-01-02 15:04:05"` (UTC, but no `Z`).
 * Bare ISO strings without a zone (e.g. `"2026-04-23T14:05:32"`) show up too.
 * JavaScript interprets both as **local** time, which silently skews every
 * relative label ("3h ago") by the user's UTC offset. Normalise anything
 * that lacks an explicit zone marker to UTC before constructing the Date.
 */
function normalizeIsoUtc(input: string): string {
	const trimmed = input.trim();
	// Already has Z or ±HH:MM / ±HHMM offset → trust it.
	if (/(Z|[+-]\d{2}:?\d{2})$/.test(trimmed)) return trimmed;
	// SQLite-style "YYYY-MM-DD HH:MM:SS(.ffff)?" — swap space for T, append Z.
	const sqliteMatch = trimmed.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?)$/);
	if (sqliteMatch) return `${sqliteMatch[1]}T${sqliteMatch[2]}Z`;
	return trimmed;
}

function toDate(input: DateInput): Date | null {
	if (input === null || input === undefined || input === '') return null;
	if (input instanceof Date) return isNaN(input.getTime()) ? null : input;
	if (typeof input === 'number') {
		// Docker timestamps come in as unix seconds; JS Date wants ms.
		// Values below 1e12 are plausibly seconds, above are ms.
		const ms = input < 1e12 ? input * 1000 : input;
		const d = new Date(ms);
		return isNaN(d.getTime()) ? null : d;
	}
	const d = new Date(normalizeIsoUtc(input));
	return isNaN(d.getTime()) ? null : d;
}

function localeTag(loc: string): string {
	// Map our app locale codes to BCP-47 tags. Unknown codes fall back to en-GB
	// which gives an ISO-like day-month order — less confusing internationally
	// than US en-US month-first.
	switch (loc) {
		case 'ru':
			return 'ru-RU';
		case 'en':
			return 'en-GB';
		default:
			return loc || 'en-GB';
	}
}

function makeFormatters(tz: string, loc: string) {
	const tag = localeTag(loc);
	const baseOpts: Intl.DateTimeFormatOptions = { timeZone: tz };

	// Instantiate once per (tz, locale) pair — Intl.DateTimeFormat construction
	// is surprisingly expensive when called inside tight loops (event log list).
	const dateTimeFmt = new Intl.DateTimeFormat(tag, {
		...baseOpts,
		year: 'numeric',
		month: 'short',
		day: '2-digit',
		hour: '2-digit',
		minute: '2-digit',
		second: '2-digit',
		hour12: false
	});
	const dateFmt = new Intl.DateTimeFormat(tag, {
		...baseOpts,
		year: 'numeric',
		month: 'short',
		day: '2-digit'
	});
	const timeFmt = new Intl.DateTimeFormat(tag, {
		...baseOpts,
		hour: '2-digit',
		minute: '2-digit',
		second: '2-digit',
		hour12: false
	});
	const shortDateFmt = new Intl.DateTimeFormat(tag, {
		...baseOpts,
		month: 'short',
		day: 'numeric',
		year: 'numeric'
	});
	const compactFmt = new Intl.DateTimeFormat(tag, {
		...baseOpts,
		month: 'short',
		day: 'numeric',
		hour: '2-digit',
		minute: '2-digit',
		hour12: false
	});
	const clockFmt = new Intl.DateTimeFormat(tag, {
		...baseOpts,
		hour: '2-digit',
		minute: '2-digit',
		second: '2-digit',
		hour12: false
	});

	return {
		/** Full timestamp: "23 Apr 2026, 14:05:32". Use for event log detail, logs. */
		dateTime(input: DateInput): string {
			const d = toDate(input);
			return d ? dateTimeFmt.format(d) : PLACEHOLDER;
		},
		/** Date only: "23 Apr 2026". Use for created_at columns. */
		date(input: DateInput): string {
			const d = toDate(input);
			return d ? dateFmt.format(d) : PLACEHOLDER;
		},
		/** Clock time: "14:05:32". Use for live headers and the settings preview. */
		time(input: DateInput): string {
			const d = toDate(input);
			return d ? timeFmt.format(d) : PLACEHOLDER;
		},
		/** "Apr 23, 2026" — matches legacy StaleContainerCard look. */
		shortDate(input: DateInput): string {
			const d = toDate(input);
			return d ? shortDateFmt.format(d) : PLACEHOLDER;
		},
		/** Compact: "Apr 23, 14:05". Use in dense tables. */
		compact(input: DateInput): string {
			const d = toDate(input);
			return d ? compactFmt.format(d) : PLACEHOLDER;
		},
		/** Clock HH:MM:SS — used by the live clock in the timezone card. */
		clock(input: DateInput): string {
			const d = toDate(input);
			return d ? clockFmt.format(d) : PLACEHOLDER;
		},
		/** Relative "5m ago" — timezone-independent but locale-aware. */
		relative(input: DateInput): string {
			const d = toDate(input);
			if (!d) return PLACEHOLDER;
			const diffSec = Math.floor((Date.now() - d.getTime()) / 1000);
			if (diffSec < 60) return relativeLabel(loc, diffSec, 's');
			const diffMin = Math.floor(diffSec / 60);
			if (diffMin < 60) return relativeLabel(loc, diffMin, 'm');
			const diffHour = Math.floor(diffMin / 60);
			if (diffHour < 24) return relativeLabel(loc, diffHour, 'h');
			const diffDay = Math.floor(diffHour / 24);
			if (diffDay < 30) return relativeLabel(loc, diffDay, 'd');
			return relativeLabel(loc, Math.floor(diffDay / 30), 'mo');
		},
		/** Currently active zone — exposed so UIs can show "rendered in X". */
		timezone: tz,
		locale: tag
	};
}

function relativeLabel(loc: string, value: number, unit: 's' | 'm' | 'h' | 'd' | 'mo'): string {
	if (loc === 'ru') {
		const ruUnits: Record<typeof unit, string> = { s: 'с', m: 'м', h: 'ч', d: 'д', mo: 'мес' };
		return `${value}${ruUnits[unit]} назад`;
	}
	return `${value}${unit} ago`;
}

export type DateFormatter = ReturnType<typeof makeFormatters>;

/**
 * Reactive formatter — re-derives whenever the user changes timezone or
 * locale. Consume with `$fmt.dateTime(...)` in templates.
 */
export const fmt: Readable<DateFormatter> = derived(
	[effectiveTimezone, locale],
	([$tz, $loc]) => makeFormatters($tz, $loc)
);

/** One-shot formatter snapshot for imperative code (event handlers, tooltips). */
export function currentFormatter(): DateFormatter {
	return get(fmt);
}
