import type { Season } from "@app/shared-types";

/**
 * All kalenderaritmetik i denna modul sker i UTC (Date.UTC/getUTC*).
 * Motorn ska ge identiskt resultat oavsett serverns tidszon – och UTC har
 * ingen sommartid, så dygnsmatte med millisekunder är alltid exakt.
 */

/** Meteorologisk årstid för norra halvklotet (SE-marknad). */
export function seasonForDate(date: Date): Season {
  const m = date.getUTCMonth() + 1;
  if (m >= 3 && m <= 5) return "spring";
  if (m >= 6 && m <= 8) return "summer";
  if (m >= 9 && m <= 11) return "autumn";
  return "winter";
}

/** Midsommarafton: fredagen mellan 19 och 25 juni. */
export function midsummerEve(year: number): Date {
  for (let day = 19; day <= 25; day++) {
    const d = new Date(Date.UTC(year, 5, day));
    if (d.getUTCDay() === 5) return d;
  }
  return new Date(Date.UTC(year, 5, 24));
}

/** Påskdagen enligt anonym gregoriansk algoritm. */
export function easterSunday(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);
  const day = ((h + l - 7 * m + 114) % 31) + 1;
  return new Date(Date.UTC(year, month - 1, day));
}

/** Första advent: fjärde söndagen före juldagen. */
export function firstAdvent(year: number): Date {
  const christmas = new Date(Date.UTC(year, 11, 25));
  const dayOfWeek = christmas.getUTCDay();
  return new Date(Date.UTC(year, 11, 25 - (dayOfWeek === 0 ? 7 : dayOfWeek) - 21));
}

export interface SeasonEventRuleInput {
  dateRule:
    | { kind: "fixed"; monthDay: string }
    | { kind: "range"; startMonthDay: string; endMonthDay: string }
    | { kind: "computed"; algorithm: "midsummer" | "easter" | "advent" | "custom" };
  leadDays: number;
}

/** Är eventet aktivt (inom leadDays före, till och med eventdagen/perioden)? */
export function isEventActive(rule: SeasonEventRuleInput, today: Date): boolean {
  const year = today.getUTCFullYear();
  const t = strip(today).getTime();
  const DAY = 86_400_000;

  const activeAround = (target: Date, tailDays = 1): boolean => {
    const start = strip(target).getTime() - rule.leadDays * DAY;
    const end = strip(target).getTime() + tailDays * DAY;
    return t >= start && t <= end;
  };

  switch (rule.dateRule.kind) {
    case "fixed": {
      const [mm, dd] = rule.dateRule.monthDay.split("-").map(Number);
      return activeAround(new Date(Date.UTC(year, (mm ?? 1) - 1, dd ?? 1)));
    }
    case "range": {
      const [sm, sd] = rule.dateRule.startMonthDay.split("-").map(Number);
      const [em, ed] = rule.dateRule.endMonthDay.split("-").map(Number);
      const start = Date.UTC(year, (sm ?? 1) - 1, sd ?? 1) - rule.leadDays * DAY;
      let end = Date.UTC(year, (em ?? 1) - 1, ed ?? 1);
      if (end < start) end = Date.UTC(year + 1, (em ?? 1) - 1, ed ?? 1);
      return t >= start && t <= end;
    }
    case "computed": {
      switch (rule.dateRule.algorithm) {
        case "midsummer":
          return activeAround(midsummerEve(year), 2);
        case "easter":
          return activeAround(easterSunday(year), 1);
        case "advent":
          return activeAround(firstAdvent(year), 28);
        case "custom":
          return false;
      }
    }
  }
}

/** UTC-dygnets start – oberoende av serverns tidszon. */
function strip(d: Date): Date {
  return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
}
