// Site-timezone datetime helpers.
//
// Notification start/end times are stored as UTC and must be shown + entered in
// the site's configured IANA timezone (settings key site_timezone), not the
// viewer's browser zone, so every site and remote admin agrees on the wall
// clock. These convert between a UTC instant and a 'YYYY-MM-DDTHH:mm' string in
// a given zone (the value a datetime-local input holds).

const DEFAULT_TZ = 'America/New_York'

function pad(n) {
  return String(n).padStart(2, '0')
}

// Wall-clock parts of a UTC instant AS SEEN in tz. Uses Intl so it is correct
// across DST without shipping a tz table.
function zonedParts(date, tz) {
  const dtf = new Intl.DateTimeFormat('en-US', {
    timeZone: tz,
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', second: '2-digit',
    hourCycle: 'h23',
  })
  const map = {}
  for (const p of dtf.formatToParts(date)) {
    if (p.type !== 'literal') map[p.type] = p.value
  }
  return {
    year: Number(map.year), month: Number(map.month), day: Number(map.day),
    hour: Number(map.hour), minute: Number(map.minute), second: Number(map.second),
  }
}

// tz offset (ms) at the given instant: (wall-clock-as-UTC) - (real UTC).
function tzOffsetMs(date, tz) {
  const p = zonedParts(date, tz)
  const asUtc = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second)
  return asUtc - date.getTime()
}

// UTC instant (or ISO string) -> 'YYYY-MM-DDTHH:mm' wall clock in tz, for a
// datetime-local input. Empty string on no/invalid input.
export function zonedInputFromUtc(value, tz = DEFAULT_TZ) {
  if (!value) return ''
  const date = value instanceof Date ? value : new Date(value)
  if (isNaN(date.getTime())) return ''
  const p = zonedParts(date, tz)
  return `${p.year}-${pad(p.month)}-${pad(p.day)}T${pad(p.hour)}:${pad(p.minute)}`
}

// 'YYYY-MM-DDTHH:mm' wall clock in tz -> UTC ISO string. Interprets the entered
// time as tz-local (DST-aware) and returns the real instant. Empty -> null.
export function utcFromZonedInput(localStr, tz = DEFAULT_TZ) {
  if (!localStr) return null
  const [datePart, timePart = '00:00'] = localStr.split('T')
  const [y, mo, d] = datePart.split('-').map(Number)
  const [h, mi] = timePart.split(':').map(Number)
  if ([y, mo, d, h, mi].some(Number.isNaN)) return null
  // Guess the instant as if the wall clock were UTC, then subtract the zone
  // offset AT that instant. One correction is exact except in the ~1h/yr DST
  // fold, where either side is a fair reading.
  const guess = Date.UTC(y, mo - 1, d, h, mi)
  const offset = tzOffsetMs(new Date(guess), tz)
  return new Date(guess - offset).toISOString()
}

// UTC instant (or ISO) -> human string in tz, e.g. "Jul 30, 2026, 2:34 PM".
export function formatInZone(value, tz = DEFAULT_TZ, opts = {}) {
  if (!value) return ''
  const date = value instanceof Date ? value : new Date(value)
  if (isNaN(date.getTime())) return ''
  return new Intl.DateTimeFormat('en-US', {
    timeZone: tz,
    year: 'numeric', month: 'short', day: 'numeric',
    hour: 'numeric', minute: '2-digit',
    ...opts,
  }).format(date)
}

export { DEFAULT_TZ }
