/**
 * Jalali (Persian) calendar — Gregorian `Date` values stay the app’s internal model.
 * Conversion logic ported from jalaali-js (MIT).
 */

export type PersianDateParts = {
  year: number;
  month: number;
  day: number;
};

const BREAKS = [
  -61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210, 1635, 2060, 2097, 2192, 2262,
  2324, 2394, 2456, 3178,
];

function div(a: number, b: number): number {
  return Math.trunc(a / b);
}

function mod(a: number, b: number): number {
  return a - Math.trunc(a / b) * b;
}

function g2d(gy: number, gm: number, gd: number): number {
  let d =
    div((gy + div(gm - 8, 6) + 100100) * 1461, 4) +
    div(153 * mod(gm + 9, 12) + 2, 5) +
    gd -
    34840408;
  d = d - div(div(gy + 100100 + div(gm - 8, 6), 100) * 3, 4) + 752;
  return d;
}

function d2g(jdn: number): { gy: number; gm: number; gd: number } {
  let j = 4 * jdn + 139361631;
  j = j + div(div(4 * jdn + 183187720, 146097) * 3, 4) * 4 - 3908;
  const i = div(mod(j, 1461), 4) * 5 + 308;
  const gd = div(mod(i, 153), 5) + 1;
  const gm = mod(div(i, 153), 12) + 1;
  const gy = div(j, 1461) - 100100 + div(8 - gm, 6);
  return { gy, gm, gd };
}

function jalCal(jy: number, withoutLeap: boolean): { leap?: number; gy: number; march: number } {
  const bl = BREAKS.length;
  let gy = jy + 621;
  let leapJ = -14;
  let jp = BREAKS[0];
  let jump = 0;
  let leap = 0;
  let n = 0;

  if (jy < jp || jy >= BREAKS[bl - 1]) {
    throw new Error(`Invalid Jalaali year ${jy}`);
  }

  for (let i = 1; i < bl; i += 1) {
    const jm = BREAKS[i];
    jump = jm - jp;
    if (jy < jm) break;
    leapJ = leapJ + div(jump, 33) * 8 + div(mod(jump, 33), 4);
    jp = jm;
  }
  n = jy - jp;
  leapJ = leapJ + div(n, 33) * 8 + div(mod(n, 33) + 3, 4);
  if (mod(jump, 33) === 4 && jump - n === 4) leapJ += 1;

  const leapG = div(gy, 4) - div((div(gy, 100) + 1) * 3, 4) - 150;
  const march = 20 + leapJ - leapG;

  if (withoutLeap) return { gy, march };

  if (jump - n < 6) n = n - jump + div(jump + 4, 33) * 33;
  leap = mod(mod(n + 1, 33) - 1, 4);
  if (leap === -1) leap = 4;
  return { leap, gy, march };
}

function j2d(jy: number, jm: number, jd: number): number {
  const r = jalCal(jy, true);
  return g2d(r.gy, 3, r.march) + (jm - 1) * 31 - div(jm, 7) * (jm - 7) + jd - 1;
}

function d2j(jdn: number): { jy: number; jm: number; jd: number } {
  const { gy } = d2g(jdn);
  let jy = gy - 621;
  const r = jalCal(jy, false);
  const jdn1f = g2d(gy, 3, r.march);
  let k = jdn - jdn1f;
  let jm: number;
  let jd: number;

  if (k >= 0) {
    if (k <= 185) {
      jm = 1 + div(k, 31);
      jd = mod(k, 31) + 1;
      return { jy, jm, jd };
    }
    k -= 186;
  } else {
    jy -= 1;
    k += 179;
    if (r.leap === 1) k += 1;
  }
  jm = 7 + div(k, 30);
  jd = mod(k, 30) + 1;
  return { jy, jm, jd };
}

export function gregorianToJalali(gy: number, gm: number, gd: number): [number, number, number] {
  const { jy, jm, jd } = d2j(g2d(gy, gm, gd));
  return [jy, jm, jd];
}

export function jalaliToGregorian(jy: number, jm: number, jd: number): [number, number, number] {
  const { gy, gm, gd } = d2g(j2d(jy, jm, jd));
  return [gy, gm, gd];
}

export function isJalaliLeapYear(jy: number): boolean {
  const r = jalCal(jy, false);
  return r.leap === 0;
}

export function jalaliDaysInMonth(jy: number, jm: number): number {
  if (jm <= 6) return 31;
  if (jm <= 11) return 30;
  return isJalaliLeapYear(jy) ? 30 : 29;
}

export function getLocalPersianParts(date: Date): PersianDateParts {
  const [year, month, day] = gregorianToJalali(
    date.getFullYear(),
    date.getMonth() + 1,
    date.getDate(),
  );
  return { year, month, day };
}

export function persianPartsToLocalDate(jy: number, jm: number, jd: number): Date {
  const [gy, gm, gd] = jalaliToGregorian(jy, jm, jd);
  return new Date(gy, gm - 1, gd, 0, 0, 0, 0);
}

export function persianYearRange(anchorYear: number, past = 10, future = 2): number[] {
  const years: number[] = [];
  for (let y = anchorYear - past; y <= anchorYear + future; y += 1) {
    years.push(y);
  }
  return years;
}

const persianMonthFormatter = new Intl.DateTimeFormat('fa-IR-u-ca-persian', {
  month: 'long',
  numberingSystem: 'arabext',
});

/** Jalali month name (Farvardin, …) for picker labels. */
export function formatPersianMonthLabel(jy: number, jm: number): string {
  const [gy, gm, gd] = jalaliToGregorian(jy, jm, 15);
  return persianMonthFormatter.format(new Date(gy, gm - 1, gd));
}

const ARABEXT_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'] as const;

export function toLatinDigits(value: string): string {
  return value.replace(/[۰-۹]/g, (ch) => {
    const index = ARABEXT_DIGITS.indexOf(ch as (typeof ARABEXT_DIGITS)[number]);
    return index >= 0 ? String(index) : ch;
  });
}

export function toArabextDigits(value: string): string {
  return value.replace(/\d/g, (d) => ARABEXT_DIGITS[Number(d)] ?? d);
}

/** Parse typed Jalali `YYYY/MM/DD` (Latin or Persian digits) → `YYYY-MM-DD` or null. */
export function parsePersianDateInputText(raw: string): string | null {
  const normalized = toLatinDigits(raw.trim()).replace(/-/g, '/');
  const match = /^(\d{4})\/(\d{1,2})\/(\d{1,2})$/.exec(normalized);
  if (!match) return null;

  const jy = Number(match[1]);
  const jm = Number(match[2]);
  const jd = Number(match[3]);
  if (jm < 1 || jm > 12 || jd < 1 || jd > jalaliDaysInMonth(jy, jm)) return null;

  const date = persianPartsToLocalDate(jy, jm, jd);
  const gy = date.getFullYear();
  const gm = String(date.getMonth() + 1).padStart(2, '0');
  const gd = String(date.getDate()).padStart(2, '0');
  return `${gy}-${gm}-${gd}`;
}

/** `YYYY-MM-DD` → typed Jalali field text e.g. ۱۴۰۴/۰۴/۲۲ */
export function formatIsoAsPersianDateInput(iso: string): string {
  if (!iso) return '';
  const [gy, gm, gd] = iso.split('-').map(Number);
  if (!gy || !gm || !gd) return '';
  const { year, month, day } = getLocalPersianParts(new Date(gy, gm - 1, gd, 0, 0, 0, 0));
  const pad2 = (n: number) => toArabextDigits(String(n).padStart(2, '0'));
  return `${toArabextDigits(String(year))}/${pad2(month)}/${pad2(day)}`;
}

/** Force `YYYY/MM/DD` shape while typing (Persian digits in output). */
export function maskJalaliDateTyping(raw: string): string {
  const digits = toLatinDigits(raw).replace(/\D/g, '').slice(0, 8);
  const segments: string[] = [];
  if (digits.length > 0) segments.push(digits.slice(0, Math.min(4, digits.length)));
  if (digits.length > 4) segments.push(digits.slice(4, Math.min(6, digits.length)));
  if (digits.length > 6) segments.push(digits.slice(6, 8));
  return toArabextDigits(segments.join('/'));
}
