export const DEFAULT_TIME_ZONE = "Asia/Shanghai";
export const TIME_ZONE_STORAGE_KEY = "hyapp-admin.display-time-zone";
export const TIME_ZONE_OPTIONS = [
  { label: "中国时间", value: "Asia/Shanghai" },
  { label: "UTC", value: "UTC" }
];

let activeTimeZone = DEFAULT_TIME_ZONE;

const formatterCache = new Map();

export function normalizeTimeZone(value) {
  return TIME_ZONE_OPTIONS.some((option) => option.value === value) ? value : DEFAULT_TIME_ZONE;
}

export function getActiveTimeZone() {
  return activeTimeZone;
}

export function setActiveTimeZone(value) {
  activeTimeZone = normalizeTimeZone(value);
  return activeTimeZone;
}

export function getTimeZoneLabel(value) {
  return TIME_ZONE_OPTIONS.find((option) => option.value === value)?.label || value || "-";
}

export function readStoredTimeZone(storage = globalThis.localStorage) {
  try {
    return normalizeTimeZone(storage?.getItem(TIME_ZONE_STORAGE_KEY));
  } catch {
    return DEFAULT_TIME_ZONE;
  }
}

export function writeStoredTimeZone(value, storage = globalThis.localStorage) {
  const normalized = normalizeTimeZone(value);
  try {
    storage?.setItem(TIME_ZONE_STORAGE_KEY, normalized);
  } catch {
    // 仅影响偏好持久化，不能阻断本次页面内切换。
  }
  return normalized;
}

export function toEpochMillis(value) {
  if (value === null || value === undefined || value === "") {
    return null;
  }
  if (value instanceof Date) {
    return finitePositiveMillis(value.getTime());
  }
  if (typeof value === "number") {
    return finitePositiveMillis(value);
  }
  if (typeof value !== "string") {
    return null;
  }

  const text = value.trim();
  if (!text) {
    return null;
  }
  if (/^\d+$/.test(text)) {
    return finitePositiveMillis(Number(text));
  }
  if (!/(?:Z|[+-]\d{2}:?\d{2})$/i.test(text)) {
    return null;
  }
  return finitePositiveMillis(Date.parse(text));
}

export function formatTime(value = Date.now(), timeZone = activeTimeZone) {
  const parts = dateTimeParts(value, timeZone);
  return parts ? `${parts.hour}:${parts.minute}:${parts.second}` : "-";
}

export function formatDateTime(value, timeZone = activeTimeZone) {
  const parts = dateTimeParts(value, timeZone);
  return parts ? `${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute}:${parts.second}` : "-";
}

export function formatMillis(value, timeZone = activeTimeZone) {
  return formatDateTime(value, timeZone);
}

export function startOfDayMillis(value = Date.now(), timeZone = activeTimeZone) {
  const normalizedTimeZone = normalizeTimeZone(timeZone);
  const targetParts = dateTimeParts(value, normalizedTimeZone);
  if (!targetParts) {
    return null;
  }

  return localDateStartMillis(targetParts, normalizedTimeZone);
}

export function nextDayStartMillis(value = Date.now(), timeZone = activeTimeZone) {
  const normalizedTimeZone = normalizeTimeZone(timeZone);
  const targetParts = dateTimeParts(value, normalizedTimeZone);
  if (!targetParts) {
    return null;
  }

  // 先按日历推进一天，再把该日期的本地零点换算成 epoch；不能直接加 24 小时，
  // 否则未来开放夏令时时区后会在切换日提前或延后一小时刷新 Dashboard。
  const nextCalendarDate = new Date(
    Date.UTC(Number(targetParts.year), Number(targetParts.month) - 1, Number(targetParts.day) + 1)
  );
  return localDateStartMillis(
    {
      day: String(nextCalendarDate.getUTCDate()).padStart(2, "0"),
      month: String(nextCalendarDate.getUTCMonth() + 1).padStart(2, "0"),
      year: String(nextCalendarDate.getUTCFullYear())
    },
    normalizedTimeZone
  );
}

function localDateStartMillis(targetParts, timeZone) {
  const targetUtc = Date.UTC(Number(targetParts.year), Number(targetParts.month) - 1, Number(targetParts.day));
  let candidate = targetUtc;

  // IANA 时区的 UTC 偏移可能随日期变化；迭代校正能在不引入时区库的前提下得到目标日期的本地零点。
  for (let attempt = 0; attempt < 3; attempt += 1) {
    const candidateParts = dateTimeParts(candidate, timeZone);
    if (!candidateParts) {
      return null;
    }
    const representedUtc = Date.UTC(
      Number(candidateParts.year),
      Number(candidateParts.month) - 1,
      Number(candidateParts.day),
      Number(candidateParts.hour),
      Number(candidateParts.minute),
      Number(candidateParts.second)
    );
    const corrected = targetUtc - (representedUtc - candidate);
    if (corrected === candidate) {
      return corrected;
    }
    candidate = corrected;
  }

  return candidate;
}

function finitePositiveMillis(value) {
  return Number.isFinite(value) && value > 0 ? value : null;
}

function dateTimeParts(value, timeZone) {
  const epochMillis = toEpochMillis(value);
  if (!epochMillis) {
    return null;
  }

  const partMap = Object.fromEntries(
    getFormatter(normalizeTimeZone(timeZone))
      .formatToParts(epochMillis)
      .filter((part) => part.type !== "literal")
      .map((part) => [part.type, part.value])
  );
  return {
    day: partMap.day,
    hour: partMap.hour,
    minute: partMap.minute,
    month: partMap.month,
    second: partMap.second,
    year: partMap.year
  };
}

function getFormatter(timeZone) {
  if (!formatterCache.has(timeZone)) {
    formatterCache.set(
      timeZone,
      new Intl.DateTimeFormat("en-CA", {
        day: "2-digit",
        hour: "2-digit",
        hourCycle: "h23",
        minute: "2-digit",
        month: "2-digit",
        second: "2-digit",
        timeZone,
        year: "numeric"
      })
    );
  }
  return formatterCache.get(timeZone);
}
