function extractDateParts(date: Date, timeZone: string) {
  const formatter = new Intl.DateTimeFormat("en-CA", {
    timeZone,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  });

  const parts = formatter.formatToParts(date);
  const year = Number(parts.find((part) => part.type === "year")?.value);
  const month = Number(parts.find((part) => part.type === "month")?.value);
  const day = Number(parts.find((part) => part.type === "day")?.value);

  return { year, month, day };
}

export function getDateStringInTimeZone(date: Date, timeZone: string): string {
  const { year, month, day } = extractDateParts(date, timeZone);
  return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
}

export function getShiftedDateStringInTimeZone(
  daysOffset: number,
  timeZone: string,
  baseDate: Date = new Date(),
): string {
  const { year, month, day } = extractDateParts(baseDate, timeZone);
  const shifted = new Date(Date.UTC(year, month - 1, day));
  shifted.setUTCDate(shifted.getUTCDate() + daysOffset);
  return shifted.toISOString().split("T")[0];
}

function getTimeZoneOffsetMs(date: Date, timeZone: string): number {
  const formatter = new Intl.DateTimeFormat("en-US", {
    timeZone,
    timeZoneName: "shortOffset",
  });

  const offsetLabel =
    formatter.formatToParts(date).find((part) => part.type === "timeZoneName")
      ?.value || "GMT+0";

  const match = offsetLabel.match(/GMT([+-])(\d{1,2})(?::?(\d{2}))?/);
  if (!match) return 0;

  const sign = match[1] === "-" ? -1 : 1;
  const hours = Number(match[2] || "0");
  const minutes = Number(match[3] || "0");

  return sign * (hours * 60 + minutes) * 60 * 1000;
}

export function getDayBoundsForTimeZone(
  dateString: string,
  timeZone: string,
): { startMs: number; endMs: number } {
  const [year, month, day] = dateString.split("-").map(Number);
  const startGuess = new Date(Date.UTC(year, month - 1, day, 0, 0, 0));
  const nextDayGuess = new Date(Date.UTC(year, month - 1, day + 1, 0, 0, 0));

  const startOffsetMs = getTimeZoneOffsetMs(startGuess, timeZone);
  const nextDayOffsetMs = getTimeZoneOffsetMs(nextDayGuess, timeZone);

  const startMs = Date.UTC(year, month - 1, day, 0, 0, 0) - startOffsetMs;
  const nextDayStartMs =
    Date.UTC(year, month - 1, day + 1, 0, 0, 0) - nextDayOffsetMs;

  return {
    startMs,
    endMs: nextDayStartMs - 1,
  };
}

export function getDateOnlyValueForTimeZone(
  timeZone: string,
  date: Date = new Date(),
): Date {
  return new Date(`${getDateStringInTimeZone(date, timeZone)}T00:00:00.000Z`);
}
