const formatters = new Map<string, Intl.DateTimeFormat>();

/** Only chooses a date label for the first request; API normalizes the week.
 * Cutoff policy matches API WorkWeek: first fold occurrence, gap forward.
 * Shift inputs deliberately use the stricter localToInstant policy instead.
 */
export function currentWeekLabelDate(settings: { timezone: string; work_week_start_day: string; work_week_start_time: string }, now = new Date().toISOString()): string {
  const local = instantToLocal(now, settings.timezone);
  const date = local.slice(0, 10);
  const day = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][new Date(`${date}T12:00:00Z`).getUTCDay()];
  if (day !== settings.work_week_start_day) return date;
  const cutoffLocal = `${date}T${settings.work_week_start_time.slice(0, 5)}`;
  const candidates = candidateInstants(cutoffLocal, settings.timezone);
  const exact = candidates.filter(c => instantToLocal(c, settings.timezone) === cutoffLocal).sort();
  const forward = candidates.filter(c => instantToLocal(c, settings.timezone) > cutoffLocal).sort();
  const cutoff = exact[0] ?? forward[0];
  return new Date(now).getTime() < new Date(cutoff).getTime() ? addDays(date, -1) : date;
}
function formatter(timezone: string) {
  let value = formatters.get(timezone);
  if (!value) {
    value = new Intl.DateTimeFormat('en-CA', { timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23' });
    formatters.set(timezone, value);
  }
  return value;
}
export function instantToLocal(instant: string, timezone: string): string {
  const parts = Object.fromEntries(formatter(timezone).formatToParts(new Date(instant)).map(p => [p.type, p.value]));
  return `${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}`;
}
/** Reject gaps AND folds: native datetime-local has no offset disambiguator. */
export function localToInstant(local: string, timezone: string): string {
  if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(local)) throw new Error('Enter a valid date and time.');
  const naive = new Date(`${local}:00Z`);
  if (!Number.isFinite(naive.getTime()) || naive.toISOString().slice(0, 16) !== local) throw new Error('Enter a valid date and time.');
  const matches = candidateInstants(local, timezone).filter(candidate => instantToLocal(candidate, timezone) === local);
  if (!matches.length) throw new Error(`This time does not exist in ${timezone} because of a clock change. Choose another time.`);
  if (matches.length > 1) throw new Error(`This time is ambiguous in ${timezone} because clocks repeat. Choose an unambiguous time.`);
  return matches[0];
}
function candidateInstants(local: string, timezone: string): string[] {
  const naive = new Date(`${local}:00Z`);
  const offsets = new Set<number>();
  // Sample either side of any nearby transition, then validate every candidate.
  for (let hours = -36; hours <= 36; hours += 6) {
    const sample = new Date(naive.getTime() + hours * 3600000);
    offsets.add(new Date(`${instantToLocal(sample.toISOString(), timezone)}:00Z`).getTime() - sample.getTime());
  }
  return [...offsets].map(offset => new Date(naive.getTime() - offset).toISOString());
}
/** Calendar-only arithmetic: deliberately not elapsed 24-hour store days. */
export function addDays(date: string, days: number): string {
  const value = new Date(`${date}T12:00:00Z`);
  value.setUTCDate(value.getUTCDate() + days);
  return value.toISOString().slice(0, 10);
}
