// Time-zone option helpers for the Profile screen's `<select>`.
//
// The browser ships the full IANA list through
// `Intl.supportedValuesOf("timeZone")` (Chrome 99+, Firefox 93+,
// Safari 15.4+ — all within the PWA target). This module reads that
// list, groups the entries by their first slash-delimited segment
// (`Africa`, `America`, …), sorts both groups and entries within each
// group, and yields a shape that maps 1:1 onto `<optgroup>` /
// `<option>`.
//
// Two corner cases:
//   * Singletons like `UTC` / `GMT` / `EST` have no slash, so they
//     collapse into a single "Other" bucket at the bottom of the
//     dropdown.
//   * A stored value that is not in the browser-supplied list (an
//     older zone the runtime no longer ships, or a name from a
//     freshly-imported account) is appended as a one-entry "Other"
//     option through `withPreservedValue`. The Profile form calls
//     that helper so saving an unchanged form never silently
//     downgrades a stored value to the default.

const OTHER_GROUP = "Other";

export interface TimeZoneGroup {
	readonly label: string;
	readonly values: readonly string[];
}

/**
 * supportedTimeZones returns the browser-supplied IANA list, grouped
 * by leading segment and sorted alphabetically. Returns an empty
 * array when the runtime does not implement
 * `Intl.supportedValuesOf("timeZone")` so callers can fall back to a
 * text input.
 */
export function supportedTimeZones(): readonly TimeZoneGroup[] {
	const zones = listSupportedZones();
	if (zones.length === 0) return [];
	return groupZones(zones);
}

/**
 * withPreservedValue returns `groups` unchanged when the supplied
 * `value` is empty or already appears in one of the groups.
 * Otherwise it appends a single-entry "Other" group carrying the
 * value so the `<select>` can render it without losing the saved
 * zone. The original groups are not mutated.
 */
export function withPreservedValue(
	groups: readonly TimeZoneGroup[],
	value: string,
): readonly TimeZoneGroup[] {
	const trimmed = value.trim();
	if (trimmed === "") return groups;
	for (const group of groups) {
		if (group.values.includes(trimmed)) return groups;
	}
	const extra: TimeZoneGroup = { label: OTHER_GROUP, values: [trimmed] };
	// Merge with an existing "Other" group if one is already present,
	// otherwise append a fresh one.
	const next: TimeZoneGroup[] = [];
	let mergedIntoOther = false;
	for (const group of groups) {
		if (group.label === OTHER_GROUP) {
			mergedIntoOther = true;
			next.push({
				label: OTHER_GROUP,
				values: [...group.values, trimmed].sort((a, b) => a.localeCompare(b)),
			});
		} else {
			next.push(group);
		}
	}
	if (!mergedIntoOther) next.push(extra);
	return next;
}

/**
 * browserTimeZone returns the time zone the runtime believes the
 * user is in. An empty string is returned when `Intl.DateTimeFormat`
 * is missing or rejects the resolution.
 */
export function browserTimeZone(): string {
	try {
		return Intl.DateTimeFormat().resolvedOptions().timeZone ?? "";
	} catch {
		return "";
	}
}

interface IntlWithSupportedValues {
	supportedValuesOf?: (key: "timeZone") => string[];
}

function listSupportedZones(): string[] {
	const intl = Intl as unknown as IntlWithSupportedValues;
	if (typeof intl.supportedValuesOf !== "function") return [];
	try {
		const zones = intl.supportedValuesOf("timeZone");
		return Array.isArray(zones) ? zones.slice() : [];
	} catch {
		return [];
	}
}

function groupZones(zones: readonly string[]): readonly TimeZoneGroup[] {
	const buckets = new Map<string, string[]>();
	const others: string[] = [];
	for (const zone of zones) {
		const slash = zone.indexOf("/");
		if (slash === -1) {
			others.push(zone);
			continue;
		}
		const prefix = zone.slice(0, slash);
		const bucket = buckets.get(prefix);
		if (bucket === undefined) {
			buckets.set(prefix, [zone]);
		} else {
			bucket.push(zone);
		}
	}
	const groups: TimeZoneGroup[] = [];
	const sortedPrefixes = Array.from(buckets.keys()).sort((a, b) =>
		a.localeCompare(b),
	);
	for (const prefix of sortedPrefixes) {
		const values = (buckets.get(prefix) ?? []).slice().sort((a, b) =>
			a.localeCompare(b),
		);
		groups.push({ label: prefix, values });
	}
	if (others.length > 0) {
		groups.push({
			label: OTHER_GROUP,
			values: others.slice().sort((a, b) => a.localeCompare(b)),
		});
	}
	return groups;
}
