/**
 * The arithmetic layer behind `JalaliDatePicker`/`JalaliDateField`. Two small, pure "calendar engines"
 * (Jalaali for `fa`, plain Gregorian for `en`) share one shape so the picker's grid/navigation logic is
 * calendar-agnostic; both always emit/accept **ISO Gregorian** `YYYY-MM-DD` — the wire never sees Jalaali.
 *
 * Arithmetic decision: `Intl` (`fa-IR-u-ca-persian`, already used by `utils/date.ts`) is enough to
 * *display* a Shamsi date, but has no reverse direction — there is no way to ask Intl "what Gregorian
 * date is Jalaali 1404/5/15", which is exactly what a date *picker* (as opposed to a date *label*) needs
 * for month navigation and emitting the selected day. `jalaali-js` (MIT, zero runtime deps, ~9KB
 * unminified / low single-digit KB gzipped — see the phase report) supplies that reverse conversion
 * (`toGregorian`) plus month-length/leap-year math, so it's used here instead of hand-rolling the
 * Borkowski algorithm. Intl is still used for locale-correct month/weekday *names* — jalaali-js only
 * supplies numbers.
 */
import { toJalaali, toGregorian, jalaaliMonthLength } from 'jalaali-js';

export interface CalendarCursor {
  year: number;
  month: number; // 1-based
}

export interface CalendarEngine {
  today(): CalendarCursor & { day: number };
  /** Parses a wire ISO (Gregorian) date into this calendar's year/month/day. */
  fromIso(iso: string): CalendarCursor & { day: number };
  /** Emits the wire ISO (Gregorian) date for a day in this calendar. */
  toIso(year: number, month: number, day: number): string;
  daysInMonth(year: number, month: number): number;
  addMonths(cursor: CalendarCursor, delta: number): CalendarCursor;
  /** 0-based offset of day 1 within the week grid (this engine's week-start convention). */
  firstWeekdayOffset(year: number, month: number): number;
  monthYearLabel(cursor: CalendarCursor, locale: string): string;
  /** 7 short weekday labels, in this engine's week-start order. */
  weekdayLabels(locale: string): string[];
}

function pad2(n: number): string {
  return String(n).padStart(2, '0');
}

function isoOf(y: number, m: number, d: number): string {
  return `${y}-${pad2(m)}-${pad2(d)}`;
}

function parseIso(iso: string): { y: number; m: number; d: number } {
  const [y, m, d] = iso.split('-').map(Number);
  return { y, m, d };
}

/** Weekday of a Gregorian y/m/d as a UTC-anchored computation — immune to the runtime's local timezone. */
function utcWeekday(y: number, m: number, d: number): number {
  return new Date(Date.UTC(y, m - 1, d)).getUTCDay(); // 0 = Sunday … 6 = Saturday
}

function addMonthsGeneric(cursor: CalendarCursor, delta: number): CalendarCursor {
  const total = cursor.year * 12 + (cursor.month - 1) + delta;
  const year = Math.floor(total / 12);
  const month = ((total % 12) + 12) % 12;
  return { year, month: month + 1 };
}

export const jalaliEngine: CalendarEngine = {
  today() {
    const { jy, jm, jd } = toJalaali(new Date());
    return { year: jy, month: jm, day: jd };
  },
  fromIso(iso) {
    const { y, m, d } = parseIso(iso);
    const { jy, jm, jd } = toJalaali(y, m, d);
    return { year: jy, month: jm, day: jd };
  },
  toIso(year, month, day) {
    const { gy, gm, gd } = toGregorian(year, month, day);
    return isoOf(gy, gm, gd);
  },
  daysInMonth(year, month) {
    return jalaaliMonthLength(year, month);
  },
  addMonths: addMonthsGeneric,
  firstWeekdayOffset(year, month) {
    const { gy, gm, gd } = toGregorian(year, month, 1);
    // Jalaali weeks start Saturday; utcWeekday is Sunday-based, so shift by one.
    return (utcWeekday(gy, gm, gd) + 1) % 7;
  },
  monthYearLabel(cursor, locale) {
    const { gy, gm, gd } = toGregorian(cursor.year, cursor.month, 1);
    return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(new Date(gy, gm - 1, gd));
  },
  weekdayLabels(locale) {
    // A known Saturday (2024-01-06 is a Saturday) anchors the fa week-start order.
    const anchor = new Date(2024, 0, 6);
    return Array.from({ length: 7 }, (_, i) => {
      const d = new Date(anchor);
      d.setDate(anchor.getDate() + i);
      return new Intl.DateTimeFormat(locale, { weekday: 'short' }).format(d);
    });
  },
};

export const gregorianEngine: CalendarEngine = {
  today() {
    const now = new Date();
    return { year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() };
  },
  fromIso(iso) {
    const { y, m, d } = parseIso(iso);
    return { year: y, month: m, day: d };
  },
  toIso: isoOf,
  daysInMonth(year, month) {
    return new Date(year, month, 0).getDate();
  },
  addMonths: addMonthsGeneric,
  firstWeekdayOffset(year, month) {
    return utcWeekday(year, month, 1); // Gregorian week starts Sunday — no shift needed.
  },
  monthYearLabel(cursor, locale) {
    return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(
      new Date(cursor.year, cursor.month - 1, 1),
    );
  },
  weekdayLabels(locale) {
    // A known Sunday (2024-01-07) anchors the en week-start order.
    const anchor = new Date(2024, 0, 7);
    return Array.from({ length: 7 }, (_, i) => {
      const d = new Date(anchor);
      d.setDate(anchor.getDate() + i);
      return new Intl.DateTimeFormat(locale, { weekday: 'short' }).format(d);
    });
  },
};

/** Selects the display calendar for the app locale — `fa` gets Jalaali, everything else Gregorian. */
export function engineForLocale(locale: string): CalendarEngine {
  return locale === 'fa' ? jalaliEngine : gregorianEngine;
}

/** Adds whole days to an ISO (Gregorian) date via UTC arithmetic — DST-safe, calendar-agnostic. */
export function addDaysIso(iso: string, days: number): string {
  const { y, m, d } = parseIso(iso);
  const next = new Date(Date.UTC(y, m - 1, d) + days * 86_400_000);
  return isoOf(next.getUTCFullYear(), next.getUTCMonth() + 1, next.getUTCDate());
}

/** Today's date as a wire ISO string, from the local wall clock. */
export function todayIso(): string {
  const now = new Date();
  return isoOf(now.getFullYear(), now.getMonth() + 1, now.getDate());
}
