export const BUDAPEST_TIMEZONE = 'Europe/Budapest';

const DAY_MS = 24 * 60 * 60 * 1000;
const ISO_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;

export interface BirthdayDateParts {
  year: number;
  month: number;
  day: number;
}

const toMiddayDate = (parts: BirthdayDateParts) =>
  new Date(parts.year, parts.month - 1, parts.day, 12, 0, 0, 0);

const pad2 = (value: number) => String(value).padStart(2, '0');

export const isLeapYear = (year: number) =>
  year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);

export const getDaysInYear = (year: number) => (isLeapYear(year) ? 366 : 365);

export const wrapDayIndex = (index: number, totalDays: number) =>
  totalDays > 0 ? ((index % totalDays) + totalDays) % totalDays : 0;

export const isValidDateParts = ({ year, month, day }: BirthdayDateParts) => {
  if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {
    return false;
  }
  if (month < 1 || month > 12 || day < 1 || day > 31) {
    return false;
  }

  const date = toMiddayDate({ year, month, day });
  return date.getFullYear() === year && date.getMonth() + 1 === month && date.getDate() === day;
};

export const toIsoDate = ({ year, month, day }: BirthdayDateParts) =>
  `${year}-${pad2(month)}-${pad2(day)}`;

export const parseIsoDate = (value: string): BirthdayDateParts | null => {
  const match = value.match(ISO_DATE_PATTERN);
  if (!match) return null;

  const parts: BirthdayDateParts = {
    year: Number(match[1]),
    month: Number(match[2]),
    day: Number(match[3]),
  };

  return isValidDateParts(parts) ? parts : null;
};

export const getBudapestDateParts = (date: Date = new Date()): BirthdayDateParts => {
  const formatter = new Intl.DateTimeFormat('en-CA', {
    timeZone: BUDAPEST_TIMEZONE,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  });

  const formattedParts = formatter.formatToParts(date);
  const year = Number(formattedParts.find((part) => part.type === 'year')?.value ?? '0');
  const month = Number(formattedParts.find((part) => part.type === 'month')?.value ?? '0');
  const day = Number(formattedParts.find((part) => part.type === 'day')?.value ?? '0');

  const fallbackDate = new Date();
  const fallback: BirthdayDateParts = {
    year: fallbackDate.getFullYear(),
    month: fallbackDate.getMonth() + 1,
    day: fallbackDate.getDate(),
  };

  if (!isValidDateParts({ year, month, day })) {
    return fallback;
  }

  return { year, month, day };
};

export const getDatePartsFromDayIndex = (year: number, dayIndex: number): BirthdayDateParts => {
  const total = getDaysInYear(year);
  const wrapped = wrapDayIndex(dayIndex, total);
  const date = new Date(year, 0, wrapped + 1, 12, 0, 0, 0);

  return {
    year: date.getFullYear(),
    month: date.getMonth() + 1,
    day: date.getDate(),
  };
};

export const getDayIndexInYear = (parts: BirthdayDateParts) => {
  const target = toMiddayDate(parts);
  const start = new Date(parts.year, 0, 1, 12, 0, 0, 0);
  return Math.floor((target.getTime() - start.getTime()) / DAY_MS);
};
