/**
 * Event date/time serialization for the write path (BUG A fix).
 *
 * The bug: EventForm previously sent a NAIVE local wall-clock string
 * (`${date}T${time}:00`, e.g. "2026-06-07T09:00:00") with NO timezone offset.
 * The API outbox worker then did `new Date(thatString)`, which Node parses in
 * the SERVER container's local timezone (UTC in Docker). So "09:00" Toronto
 * became 09:00 UTC, and buildVeventString serialized it as `090000Z`, which
 * displays back as 05:00 EDT — a 4-hour error.
 *
 * The fix: serialize timed events to an UNAMBIGUOUS instant here in the browser,
 * where the operator's timezone IS known. `new Date(date + 'T' + time)` is parsed
 * in the browser's local zone (the operator's wall clock), and `.toISOString()`
 * converts that instant to a correct UTC `...Z` string. The worker's `new Date()`
 * then parses an unambiguous UTC instant regardless of container timezone, and
 * the round-trip preserves the operator's wall-clock time.
 *
 * All-day events carry no time component and remain `YYYY-MM-DD` DATE strings
 * (D-13) — they are timezone-independent by contract and must NOT be converted.
 */

/**
 * Serialize the start/end of an event for the create/update payload.
 *
 * @param allDay    when true, returns the date strings unchanged (DATE contract, D-13)
 * @param startDate 'YYYY-MM-DD'
 * @param startTime 'HH:MM' (ignored when allDay)
 * @param endDate   'YYYY-MM-DD'
 * @param endTime   'HH:MM' (ignored when allDay)
 * @returns { start, end } — for timed events, ISO-8601 UTC instants ('...Z');
 *          for all-day events, the raw 'YYYY-MM-DD' date strings.
 */
export function serializeEventDateTime(
  allDay: boolean,
  startDate: string,
  startTime: string,
  endDate: string,
  endTime: string,
): { start: string; end: string } {
  if (allDay) {
    // DATE contract (D-13): no time component, timezone-independent.
    return { start: startDate, end: endDate };
  }

  // Timed: build the instant from local wall-clock parts (browser is in the
  // operator's zone) and serialize to a UTC instant so the wire value is
  // unambiguous and container-timezone-independent.
  return {
    start: localWallClockToUtcIso(startDate, startTime),
    end: localWallClockToUtcIso(endDate, endTime),
  };
}

/**
 * Convert a local wall-clock date+time to a UTC ISO-8601 instant.
 * `new Date('YYYY-MM-DDTHH:MM:00')` (no offset) is parsed in the browser's
 * local timezone per ECMAScript, giving the correct instant for the operator.
 */
export function localWallClockToUtcIso(date: string, time: string): string {
  return new Date(`${date}T${time}:00`).toISOString();
}

// ─── Private date helpers (local accessor pattern — WR-05 constraint) ────────
// NEVER use toISOString().slice(0,10) — that returns the UTC date, not the
// local date. Always use getFullYear/getMonth/getDate/getHours/getMinutes.

function pad2(n: number): string {
  return n < 10 ? `0${n}` : `${n}`;
}

/**
 * Format a Date as 'YYYY-MM-DD' using LOCAL calendar accessors.
 * WR-04: exported as the single source for local-date formatting so calendarStore
 * (initialCalendarRange/todayIso) can drop its banned toISOString().slice(0,10).
 */
export function localDateISO(d: Date): string {
  return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
}

/** Format a Date as 'HH:MM' using LOCAL clock accessors. */
function localTimeHHMM(d: Date): string {
  return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
}

/**
 * Return the number of whole calendar days between two 'YYYY-MM-DD' strings.
 * Positive when end > start, negative when end < start.
 * Uses midnight-local Date arithmetic to stay in the local calendar.
 */
function dateDiffDays(startDate: string, endDate: string): number {
  const startMs = new Date(`${startDate}T00:00:00`).getTime();
  const endMs = new Date(`${endDate}T00:00:00`).getTime();
  return Math.round((endMs - startMs) / (24 * 60 * 60 * 1000));
}

/**
 * Add a whole number of days to a 'YYYY-MM-DD' string, returning a new
 * 'YYYY-MM-DD' string. Uses local midnight to avoid DST-boundary issues.
 */
function addDaysISO(dateStr: string, days: number): string {
  const d = new Date(`${dateStr}T00:00:00`);
  d.setDate(d.getDate() + days);
  return localDateISO(d);
}

// ─── D-04: Duration-preservation helpers ─────────────────────────────────────

/**
 * Compute a new timed event end when the start changes, preserving duration.
 *
 * D-04 contract:
 *   - newEnd = newStart + (oldEnd − oldStart)
 *   - Floor: if oldEnd ≤ oldStart (stale/invalid), newEnd = newStart + 1h
 *
 * Returns { endDate: 'YYYY-MM-DD', endTime: 'HH:MM' } using LOCAL accessors
 * (WR-05: never UTC-sliced).
 */
export function computeNewTimedEnd(
  newStartDate: string,
  newStartTime: string,
  oldStartDate: string,
  oldStartTime: string,
  oldEndDate: string,
  oldEndTime: string,
): { endDate: string; endTime: string } {
  const oldStartMs = new Date(`${oldStartDate}T${oldStartTime}:00`).getTime();
  const oldEndMs = new Date(`${oldEndDate}T${oldEndTime}:00`).getTime();
  const deltaMs = oldEndMs > oldStartMs ? oldEndMs - oldStartMs : 60 * 60 * 1000; // 1h floor
  const newEndDate = new Date(new Date(`${newStartDate}T${newStartTime}:00`).getTime() + deltaMs);
  return {
    endDate: localDateISO(newEndDate),
    endTime: localTimeHHMM(newEndDate),
  };
}

/**
 * Compute a new all-day event end when the start date changes, preserving
 * the inclusive day-span.
 *
 * D-04 contract:
 *   - span = max(0, oldEnd − oldStart) days  (Math.max floors negative spans to 0)
 *   - newEnd = newStart + span
 *
 * Returns 'YYYY-MM-DD' (inclusive end date).
 */
export function computeNewAllDayEnd(
  newStartDate: string,
  oldStartDate: string,
  oldEndDate: string,
): string {
  const span = Math.max(0, dateDiffDays(oldStartDate, oldEndDate));
  return addDaysISO(newStartDate, span);
}
