// Timezone-safe ISO-8601 week handling.
//
// Per the spec, weekly contributions must NOT break due to localized timezone
// offsets. We therefore compute everything in UTC and use the ISO-8601 standard
// (weeks start Monday; week 1 is the week containing the first Thursday).

/** Returns { week, year } for a given date using the ISO-8601 standard, in UTC. */
export function getIsoWeek(date: Date = new Date()): { week: number; year: number } {
  // Work on a UTC copy so the result is independent of the server/client TZ.
  const d = new Date(
    Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
  );
  // ISO weekday: Mon=1..Sun=7
  const dayNum = d.getUTCDay() || 7;
  // Shift to the Thursday of this week (Thursday determines the ISO year).
  d.setUTCDate(d.getUTCDate() + 4 - dayNum);
  const isoYear = d.getUTCFullYear();
  const yearStart = new Date(Date.UTC(isoYear, 0, 1));
  const week = Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
  return { week, year: isoYear };
}

/** Number of ISO weeks in a given ISO year (52 or 53). */
export function isoWeeksInYear(year: number): number {
  const p = (y: number) =>
    (y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400)) % 7;
  return p(year) === 4 || p(year - 1) === 3 ? 53 : 52;
}

/** Monday (UTC, 00:00) that starts the given ISO week. */
export function startOfIsoWeek(week: number, year: number): Date {
  const jan4 = new Date(Date.UTC(year, 0, 4));
  const jan4Day = jan4.getUTCDay() || 7;
  const week1Monday = new Date(jan4);
  week1Monday.setUTCDate(jan4.getUTCDate() - (jan4Day - 1));
  const monday = new Date(week1Monday);
  monday.setUTCDate(week1Monday.getUTCDate() + (week - 1) * 7);
  return monday;
}

/** Inclusive UTC date range [start, end] for an ISO week. */
export function isoWeekRange(week: number, year: number): { start: Date; end: Date } {
  const start = startOfIsoWeek(week, year);
  const end = new Date(start);
  end.setUTCDate(start.getUTCDate() + 6);
  end.setUTCHours(23, 59, 59, 999);
  return { start, end };
}

/** Human label e.g. "Week 24 · 9–15 Jun 2026". */
export function formatIsoWeek(week: number, year: number): string {
  const { start, end } = isoWeekRange(week, year);
  const fmt = (d: Date, withMonth = true) =>
    d.toLocaleDateString("en-GB", {
      day: "numeric",
      ...(withMonth ? { month: "short" } : {}),
      timeZone: "UTC",
    });
  const sameMonth = start.getUTCMonth() === end.getUTCMonth();
  const range = sameMonth
    ? `${fmt(start, false)}–${fmt(end)}`
    : `${fmt(start)} – ${fmt(end)}`;
  return `Week ${week} · ${range} ${year}`;
}

/** The current ISO week/year. */
export function currentIsoWeek() {
  return getIsoWeek(new Date());
}

/** List of { week, year, label } for the last `n` ISO weeks including current. */
export function recentIsoWeeks(n: number): { week: number; year: number; label: string }[] {
  const out: { week: number; year: number; label: string }[] = [];
  const now = new Date();
  for (let i = 0; i < n; i++) {
    const d = new Date(now);
    d.setUTCDate(now.getUTCDate() - i * 7);
    const { week, year } = getIsoWeek(d);
    out.push({ week, year, label: `W${week} ${year}` });
  }
  return out.reverse();
}
