export type RPClaimDateTimeValueType = "date" | "datetime";

export const FALLBACK_TIME_ZONE = "UTC";

export function getBrowserTimeZone(): string {
  return Intl.DateTimeFormat().resolvedOptions().timeZone || FALLBACK_TIME_ZONE;
}

export function getSupportedTimeZones(currentTimeZone = getBrowserTimeZone()) {
  const supported =
    typeof Intl.supportedValuesOf === "function"
      ? Intl.supportedValuesOf("timeZone")
      : [];
  return Array.from(
    new Set([currentTimeZone, FALLBACK_TIME_ZONE, ...supported]),
  );
}

function getTimeZoneOffsetMs(date: Date, timeZone: string) {
  const parts = new Intl.DateTimeFormat("en-US", {
    timeZone,
    hour12: false,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  }).formatToParts(date);
  const values = Object.fromEntries(
    parts
      .filter((part) => part.type !== "literal")
      .map((part) => [part.type, part.value]),
  );
  const hour = values.hour === "24" ? "00" : values.hour;
  const asUTC = Date.UTC(
    Number(values.year),
    Number(values.month) - 1,
    Number(values.day),
    Number(hour),
    Number(values.minute),
    Number(values.second),
  );
  return asUTC - date.getTime();
}

function zonedDateTimeToUnixSeconds(
  year: number,
  month: number,
  day: number,
  hour: number,
  minute: number,
  timeZone: string,
) {
  const utcGuess = Date.UTC(year, month - 1, day, hour, minute, 0);
  let instant = utcGuess - getTimeZoneOffsetMs(new Date(utcGuess), timeZone);
  const corrected = utcGuess - getTimeZoneOffsetMs(new Date(instant), timeZone);
  if (corrected !== instant) {
    instant = corrected;
  }
  return Math.trunc(instant / 1000);
}

export function dateTimeInputToUnixSeconds(
  value: string,
  valueType: RPClaimDateTimeValueType,
  timeZone: string,
): number | null {
  const trimmed = value.trim();
  const match =
    valueType === "date"
      ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(trimmed)
      : /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/.exec(trimmed);
  if (!match) return null;

  const year = Number(match[1]);
  const month = Number(match[2]);
  const day = Number(match[3]);
  const hour = valueType === "datetime" ? Number(match[4]) : 0;
  const minute = valueType === "datetime" ? Number(match[5]) : 0;
  const unixSeconds = zonedDateTimeToUnixSeconds(
    year,
    month,
    day,
    hour,
    minute,
    timeZone || FALLBACK_TIME_ZONE,
  );
  return Number.isFinite(unixSeconds) ? unixSeconds : null;
}

export function unixSecondsToDateTimeInput(
  value: number,
  valueType: RPClaimDateTimeValueType,
  timeZone: string,
) {
  const date = new Date(value * 1000);
  if (Number.isNaN(date.getTime())) return "";
  const parts = new Intl.DateTimeFormat("en-CA", {
    timeZone: timeZone || FALLBACK_TIME_ZONE,
    hour12: false,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
  }).formatToParts(date);
  const values = Object.fromEntries(
    parts
      .filter((part) => part.type !== "literal")
      .map((part) => [part.type, part.value]),
  );
  const hour = values.hour === "24" ? "00" : values.hour;
  const dateText = `${values.year}-${values.month}-${values.day}`;
  if (valueType === "date") return dateText;
  return `${dateText}T${hour}:${values.minute}`;
}

export function claimDateTimeValueToInputString(
  value: unknown,
  fallback: string,
  valueType: RPClaimDateTimeValueType,
  timeZone: string,
) {
  if (typeof value === "number" && Number.isFinite(value)) {
    return unixSecondsToDateTimeInput(value, valueType, timeZone);
  }
  if (typeof value === "string" && /^-?\d+$/.test(value.trim())) {
    return unixSecondsToDateTimeInput(
      Number(value.trim()),
      valueType,
      timeZone,
    );
  }
  const text = typeof value === "string" ? value : fallback;
  return valueType === "date" ? text.slice(0, 10) : text.slice(0, 16);
}
