/**
 * German public holiday calculator.
 * Supports federal holidays + Bavaria (BY) specific holidays.
 *
 * Easter-based dates use the Gauss/Meeus algorithm.
 */

export interface PublicHoliday {
  date: string;       // ISO "YYYY-MM-DD"
  name: string;
  federal: boolean;   // true = all states; false = state-specific
  states?: string[];  // which state abbreviations observe this holiday
}

/**
 * Compute Easter Sunday date for a given year (Gregorian calendar).
 * Uses the Anonymous Gregorian algorithm.
 */
function computeEaster(year: number): Date {
  const a = year % 19;
  const b = Math.floor(year / 100);
  const c = year % 100;
  const d = Math.floor(b / 4);
  const e = b % 4;
  const f = Math.floor((b + 8) / 25);
  const g = Math.floor((b - f + 1) / 3);
  const h = (19 * a + b - d - g + 15) % 30;
  const i = Math.floor(c / 4);
  const k = c % 4;
  const l = (32 + 2 * e + 2 * i - h - k) % 7;
  const m = Math.floor((a + 11 * h + 22 * l) / 451);
  const month = Math.floor((h + l - 7 * m + 114) / 31); // 1-based
  const day = ((h + l - 7 * m + 114) % 31) + 1;
  return new Date(Date.UTC(year, month - 1, day));
}

function addDays(date: Date, days: number): Date {
  const d = new Date(date);
  d.setUTCDate(d.getUTCDate() + days);
  return d;
}

function fmt(date: Date): string {
  return date.toISOString().slice(0, 10);
}

function fixed(year: number, month: number, day: number): string {
  return fmt(new Date(Date.UTC(year, month - 1, day)));
}

/**
 * Return all public holidays for a given year and optional state.
 * When state is omitted, returns federal holidays only.
 * When state is provided (e.g. "BY"), returns federal + state-specific holidays.
 */
export function getPublicHolidays(year: number, state?: string): PublicHoliday[] {
  const easter = computeEaster(year);

  const holidays: PublicHoliday[] = [
    // Federal holidays (all states)
    { date: fixed(year, 1, 1),         name: "Neujahr",                        federal: true },
    { date: fixed(year, 5, 1),         name: "Tag der Arbeit",                 federal: true },
    { date: fixed(year, 10, 3),        name: "Tag der Deutschen Einheit",      federal: true },
    { date: fixed(year, 12, 25),       name: "1. Weihnachtstag",               federal: true },
    { date: fixed(year, 12, 26),       name: "2. Weihnachtstag",               federal: true },
    // Easter-based federal holidays
    { date: fmt(addDays(easter, -2)),  name: "Karfreitag",                     federal: true },
    { date: fmt(easter),               name: "Ostersonntag",                   federal: true },
    { date: fmt(addDays(easter, 1)),   name: "Ostermontag",                    federal: true },
    { date: fmt(addDays(easter, 39)),  name: "Christi Himmelfahrt",            federal: true },
    { date: fmt(addDays(easter, 49)),  name: "Pfingstsonntag",                 federal: true },
    { date: fmt(addDays(easter, 50)),  name: "Pfingstmontag",                  federal: true },

    // Bavaria-specific (BY)
    {
      date: fixed(year, 1, 6),
      name: "Heilige Drei Könige",
      federal: false,
      states: ["BY", "BW", "ST"],
    },
    {
      date: fmt(addDays(easter, 60)),
      name: "Fronleichnam",
      federal: false,
      states: ["BY", "BW", "HE", "NW", "RP", "SL"],
    },
    {
      date: fixed(year, 8, 15),
      name: "Mariä Himmelfahrt",
      federal: false,
      states: ["BY", "SL"],
    },
    {
      date: fixed(year, 11, 1),
      name: "Allerheiligen",
      federal: false,
      states: ["BY", "BW", "NW", "RP", "SL"],
    },

    // Other state-specific (not BY but included for completeness)
    {
      date: fixed(year, 10, 31),
      name: "Reformationstag",
      federal: false,
      states: ["BB", "HB", "HH", "MV", "NI", "SH", "SN", "ST", "TH"],
    },
    {
      date: fixed(year, 11, 18),
      name: "Buß- und Bettag",
      federal: false,
      states: ["SN"],
    },
  ];

  if (!state) {
    return holidays.filter((h) => h.federal);
  }

  return holidays.filter((h) => h.federal || h.states?.includes(state));
}

/**
 * Check if a given date (ISO string or Date) is a public holiday.
 */
export function isPublicHoliday(date: Date | string, state?: string): boolean {
  const d = typeof date === "string" ? date : date.toISOString().slice(0, 10);
  const year = parseInt(d.slice(0, 4), 10);
  return getPublicHolidays(year, state).some((h) => h.date === d);
}
