/**
 * تقویم جلالی — تبدیل دقیق جلالی↔میلادی بدون وابستگی خارجی.
 * الگوریتم: jalaali (Khayyam-based astronomical approximation)، همان الگوریتم jalaali-js.
 */

export const jalaliMonthNames = [
  'فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور',
  'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند',
] as const;

export const jalaliWeekdayNames = [
  'شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه',
] as const;

export type JalaliParts = { jy: number; jm: number; jd: number };

export function persianDigits(value: string | number): string {
  return String(value).replace(/\d/g, (digit) => '۰۱۲۳۴۵۶۷۸۹'[Number(digit)]);
}

/* ───────────────────────── core algorithm ───────────────────────── */

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 ~~(a / b);
}

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

function jalCal(jy: number): { leap: number; gy: number; march: number } {
  const gy = jy + 621;
  let leapJ = -14;
  let jp = BREAKS[0];
  let jump = 0;

  for (let i = 1; i < BREAKS.length; 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;
  }

  let 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 (jump - n < 6) n = n - jump + div(jump + 4, 33) * 33;
  let leap = mod(mod(n + 1, 33) - 1, 4);
  if (leap === -1) leap = 4;

  return { leap, gy, march };
}

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 j2d(jy: number, jm: number, jd: number): number {
  const r = jalCal(jy);
  return g2d(r.gy, 3, r.march) + (jm - 1) * 31 - div(jm, 7) * (jm - 7) + jd - 1;
}

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

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

  return { jy, jm: 7 + div(k, 30), jd: mod(k, 30) + 1 };
}

/* ───────────────────────── public API ───────────────────────── */

export function toJalali(gy: number, gm: number, gd: number): JalaliParts {
  return d2j(g2d(gy, gm, gd));
}

export function toGregorian(jy: number, jm: number, jd: number): { gy: number; gm: number; gd: number } {
  return d2g(j2d(jy, jm, jd));
}

export function isLeapJalali(jy: number): boolean {
  return jalCal(jy).leap === 0;
}

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

export function dateToJalali(date: Date): JalaliParts {
  return toJalali(date.getFullYear(), date.getMonth() + 1, date.getDate());
}

/* ───────────── «الان» به وقت تهران (Asia/Tehran از IANA؛ بدون آفست دستی) ───────────── */

export type TehranNow = {
  gy: number; gm: number; gd: number;
  hour: number; minute: number;
  /** شنبه = 0 ... جمعه = 6 */
  weekday: number;
};

const INTL_WEEKDAY_INDEX: Record<string, number> = {
  Sat: 0, Sun: 1, Mon: 2, Tue: 3, Wed: 4, Thu: 5, Fri: 6,
};

export function tehranNow(): TehranNow {
  try {
    const parts = new Intl.DateTimeFormat('en-US', {
      timeZone: 'Asia/Tehran',
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      hourCycle: 'h23',
      weekday: 'short',
    }).formatToParts(new Date());

    const get = (type: string) => parts.find((part) => part.type === type)?.value ?? '';
    const weekday = INTL_WEEKDAY_INDEX[get('weekday')];

    return {
      gy: Number(get('year')),
      gm: Number(get('month')),
      gd: Number(get('day')),
      hour: Number(get('hour')),
      minute: Number(get('minute')),
      weekday: weekday ?? (new Date().getDay() + 1) % 7,
    };
  } catch {
    // مرورگر بدون پشتیبانی timeZone — ساعت محلی کاربر
    const d = new Date();
    return {
      gy: d.getFullYear(),
      gm: d.getMonth() + 1,
      gd: d.getDate(),
      hour: d.getHours(),
      minute: d.getMinutes(),
      weekday: (d.getDay() + 1) % 7,
    };
  }
}

/** امروز جلالی — بر اساس ساعت تهران، نه ساعت مرورگر کاربر. */
export function jalaliToday(): JalaliParts {
  const now = tehranNow();
  return toJalali(now.gy, now.gm, now.gd);
}

/** «پنجشنبه ۱۱ تیر ۱۴۰۵» — امروزِ تهران. */
export function todayJalaliLong(): string {
  const now = tehranNow();
  const j = toJalali(now.gy, now.gm, now.gd);
  return `${jalaliWeekdayNames[now.weekday]} ${persianDigits(j.jd)} ${jalaliMonthNames[j.jm - 1]} ${persianDigits(j.jy)}`;
}

/** «۱۸:۲۵» — ساعت فعلی تهران. */
export function tehranTimeHM(): string {
  const now = tehranNow();
  return formatPersianTimeHM(now.hour, now.minute);
}

export function formatPersianTimeHM(hour: number, minute: number): string {
  const suffix = hour < 12 ? 'ق.ظ' : 'ب.ظ';
  return `${persianDigits(`${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`)} ${suffix}`;
}

export function formatPersianTime(value: string | null | undefined): string {
  if (!value) return '—';
  const match = String(value).match(/(\d{1,2}):(\d{2})/);
  if (!match) return persianDigits(value);
  return formatPersianTimeHM(Number(match[1]), Number(match[2]));
}

export function formatPersianTimeRange(value: string | null | undefined): string {
  if (!value) return '—';
  return String(value)
    .split(/\s*[-–]\s*/)
    .map((part) => formatPersianTime(part))
    .join(' - ');
}

/** شنبه = 0 ... جمعه = 6 */
export function jalaliWeekdayIndex(date: Date): number {
  return (date.getDay() + 1) % 7;
}

export function jalaliToDate(jy: number, jm: number, jd: number): Date {
  const g = toGregorian(jy, jm, jd);
  return new Date(g.gy, g.gm - 1, g.gd);
}

/** خروجی میلادی «YYYY-MM-DD» برای ارسال به سرور. */
export function jalaliToIso(jy: number, jm: number, jd: number): string {
  const g = toGregorian(jy, jm, jd);
  return `${g.gy}-${String(g.gm).padStart(2, '0')}-${String(g.gd).padStart(2, '0')}`;
}

/**
 * پارس امن رشته تاریخ سرور («2026-07-02»، «2026-07-02 13:45:00»، ISO با T).
 * دستی پارس می‌شود تا شیفت منطقه زمانی، روز را جابجا نکند.
 */
export function parseServerDate(value: string | Date | null | undefined): Date | null {
  if (!value) return null;
  if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;

  const m = String(value).match(/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?/);
  if (!m) {
    const fallback = new Date(value);
    return Number.isNaN(fallback.getTime()) ? null : fallback;
  }
  return new Date(
    Number(m[1]), Number(m[2]) - 1, Number(m[3]),
    Number(m[4] ?? 0), Number(m[5] ?? 0), Number(m[6] ?? 0),
  );
}

export function isoToJalaliParts(value: string | Date | null | undefined): JalaliParts | null {
  const date = parseServerDate(value);
  return date ? dateToJalali(date) : null;
}

/** «۱۱ تیر ۱۴۰۵» */
export function formatJalali(value: string | Date | null | undefined): string {
  const p = isoToJalaliParts(value);
  if (!p) return '—';
  return `${persianDigits(p.jd)} ${jalaliMonthNames[p.jm - 1]} ${persianDigits(p.jy)}`;
}

/** «چهارشنبه ۱۱ تیر ۱۴۰۵» */
export function formatJalaliLong(value: string | Date | null | undefined): string {
  const date = parseServerDate(value);
  if (!date) return '—';
  return `${jalaliWeekdayNames[jalaliWeekdayIndex(date)]} ${formatJalali(date)}`;
}

/** «۱۴۰۵/۰۴/۱۱» */
export function formatJalaliNumeric(value: string | Date | null | undefined): string {
  const p = isoToJalaliParts(value);
  if (!p) return '';
  return persianDigits(`${p.jy}/${String(p.jm).padStart(2, '0')}/${String(p.jd).padStart(2, '0')}`);
}

/** «۱۱ تیر ۱۴۰۵ — ساعت ۱۳:۴۵» */
export function formatJalaliDateTime(value: string | Date | null | undefined): string {
  const date = parseServerDate(value);
  if (!date) return '—';
  return `${formatJalali(date)} — ساعت ${formatPersianTimeHM(date.getHours(), date.getMinutes())}`;
}

/** زمان نسبی فارسی: «همین حالا»، «۵ دقیقه پیش»، «دیروز»... */
export function timeAgoFa(value: string | Date | null | undefined): string {
  const date = parseServerDate(value);
  if (!date) return '—';

  const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
  if (seconds < 45) return 'همین حالا';
  if (seconds < 3600) return `${persianDigits(Math.max(1, Math.floor(seconds / 60)))} دقیقه پیش`;
  if (seconds < 86400) return `${persianDigits(Math.floor(seconds / 3600))} ساعت پیش`;
  if (seconds < 172800) return 'دیروز';
  if (seconds < 2592000) return `${persianDigits(Math.floor(seconds / 86400))} روز پیش`;
  return formatJalali(date);
}
