/* ============================================================
   jalali.js — Jalali (Persian Solar) calendar utilities
   ------------------------------------------------------------
   Pure, dependency-free conversion between Gregorian and Jalali.
   Algorithm: Borkovsky/Kayiani (astronomically accurate, used by
   the Solar Hijri calendar of Iran). Month/day output uses Persian
   (Farsi-Digit) numerals by default for display, Latin internally.
   ============================================================ */
(function (global) {
  'use strict';

  var JALALI_MONTHS = [
    'فروردین', 'اردیبهشت', 'خرداد',
    'تیر', 'مرداد', 'شهریور',
    'مهر', 'آبان', 'آذر',
    'دی', 'بهمن', 'اسفند'
  ];

  var WEEKDAY_NAMES = ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'];
  // JS getDay(): 0=Sun..6=Sat. Jalali week starts Saturday (5 in JS terms).
  var PERSIAN_WEEKDAY_ABBR = ['ش', 'ی', 'د', 'س', 'چ', 'پ', 'ج'];

  var PERSIAN_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];

  /* ---- Number formatting ---- */
  function toPersianDigits(input) {
    return String(input).replace(/\d/g, function (d) { return PERSIAN_DIGITS[+d]; });
  }
  function toLatinDigits(input) {
    return String(input).replace(/[۰-۹]/g, function (d) {
      return String(PERSIAN_DIGITS.indexOf(d));
    });
  }
  function pad2(n) { return (n < 10 ? '0' : '') + n; }

  /* =========================================================
     Core conversion — Jalali <-> Gregorian
     Based on the well-known jalaali-js algorithm.
     ========================================================= */
  function div(a, b) { return Math.floor(a / b); }

  function jalaliToGregorian(jy, jm, jd) {
    var gy = (jy <= 979) ? 621 : 1600;
    jy -= (jy <= 979) ? 0 : 979;
    var days = (365 * jy) + (div(jy, 33) * 8) + div((jy % 33) + 3, 4)
      + 78 + jd + div(jm - 1, 7) * 31 + ((jm - 1) % 7) * ((jm - 1 < 7) ? 31 : 30);
    gy += 400 * div(days, 146097);
    days %= 146097;
    if (days > 36524) {
      gy += 100 * div(--days, 36524);
      days %= 36524;
      if (days < 0) days = 36524;
    }
    gy += 4 * div(days, 1461);
    days %= 1461;
    if (days > 365) {
      gy += div(days - 1, 365);
      days = (days - 1) % 365;
    }
    var gd = days + 1;
    var sal_a = [0, 31, ((gy % 4 === 0 && gy % 100 !== 0) || (gy % 400 === 0)) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    var gm = 0;
    for (gm = 0; gm < 13 && gd > sal_a[gm]; gm++) gd -= sal_a[gm];
    return [gy, gm, gd];
  }

  function gregorianToJalali(gy, gm, gd) {
    var g_d_m = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
    var jy = (gy <= 1600) ? 0 : 979;
    gy -= (gy <= 1600) ? 621 : 1600;
    var gy2 = (gm > 2) ? (gy + 1) : gy;
    var days = (365 * gy) + (div((gy2 + 3), 4)) - (div((gy2 + 99), 100)) + (div((gy2 + 399), 400))
      - 80 + gd + g_d_m[gm - 1];
    jy += 33 * div(days, 12053);
    days %= 12053;
    jy += 4 * div(days, 1461);
    days %= 1461;
    if (days > 365) {
      jy += div((days - 1), 365);
      days = (days - 1) % 365;
    }
    var jm = (days < 186) ? 1 + div(days, 31) : 7 + div((days - 186), 30);
    var jd = 1 + ((days < 186) ? (days % 31) : ((days - 186) % 30));
    return [jy, jm, jd];
  }

  /* ---- Days in Jalali month ---- */
  function isLeapJalali(jy) {
    var a = jy % 33;
    return (a === 1 || a === 5 || a === 9 || a === 13 || a === 17 || a === 22 || a === 26 || a === 30);
  }
  function daysInJalaliMonth(jy, jm) {
    if (jm <= 6) return 31;
    if (jm <= 11) return 30;
    return isLeapJalali(jy) ? 30 : 29;
  }
  function daysInJalaliYear(jy) {
    return isLeapJalali(jy) ? 366 : 365;
  }

  /* =========================================================
     Date <-> object helpers
     ========================================================= */

  // Gregorian Date -> {jy, jm, jd, weekday (0=Sat..6=Fri)}
  function fromGregorian(date) {
    var j = gregorianToJalali(date.getFullYear(), date.getMonth() + 1, date.getDate());
    var jsDay = date.getDay(); // 0=Sun..6=Sat
    var pWeekday = (jsDay + 1) % 7; // 0=Sat..6=Fri
    return { jy: j[0], jm: j[1], jd: j[2], weekday: pWeekday };
  }

  // Jalali {jy, jm, jd} -> JS Date (local midnight)
  function toGregorianDate(jy, jm, jd) {
    var g = jalaliToGregorian(jy, jm, jd);
    return new Date(g[0], g[1] - 1, g[2]);
  }

  // Weekday index (0=Sat..6=Fri) for a Jalali date
  function weekdayOf(jy, jm, jd) {
    var d = toGregorianDate(jy, jm, jd);
    return (d.getDay() + 1) % 7;
  }

  /* =========================================================
     Formatting
     ========================================================= */

  // Format Jalali date: default "۱۴۰۳/۰۳/۱۲"
  //   'long' -> "۱۲ خرداد ۱۴۰۳"
  //   'full' -> "یکشنبه ۱۲ خرداد ۱۴۰۳"
  function format(obj, style) {
    var y = toPersianDigits(obj.jy);
    var m = toPersianDigits(pad2(obj.jm));
    var d = toPersianDigits(pad2(obj.jd));
    if (style === 'long') {
      return toPersianDigits(obj.jd) + ' ' + JALALI_MONTHS[obj.jm - 1] + ' ' + y;
    }
    if (style === 'full') {
      return WEEKDAY_NAMES[obj.weekday] + ' ' + toPersianDigits(obj.jd) + ' ' + JALALI_MONTHS[obj.jm - 1] + ' ' + y;
    }
    if (style === 'short') {
      return toPersianDigits(pad2(obj.jm)) + '/' + toPersianDigits(pad2(obj.jd));
    }
    return y + '/' + m + '/' + d;
  }

  // Format a JS Date (Gregorian) into Jalali string
  function formatDate(date, style) {
    if (!date) return '';
    var d = (date instanceof Date) ? date : new Date(date);
    if (isNaN(d.getTime())) return '';
    return format(fromGregorian(d), style);
  }

  // Current Jalali date object
  function today() {
    return fromGregorian(new Date());
  }

  /* =========================================================
     Parsing
     ========================================================= */
  // Parse "1403/03/12" or "۱۴۰۳/۰۳/۱۲" -> {jy,jm,jd} | null
  function parse(str) {
    if (!str) return null;
    var latin = toLatinDigits(str).trim();
    var m = latin.match(/^(\d{1,4})[\/\-\.](\d{1,2})[\/\-\.](\d{1,2})$/);
    if (!m) return null;
    var jy = +m[1], jm = +m[2], jd = +m[3];
    if (jm < 1 || jm > 12 || jd < 1 || jd > 31) return null;
    if (jd > daysInJalaliMonth(jy, jm)) return null;
    return { jy: jy, jm: jm, jd: jd };
  }

  // Convert a parsed Jalali object to an ISO-ish "1403/03/12" latin string (for storage)
  function toLatinString(obj) {
    if (!obj) return '';
    return obj.jy + '/' + pad2(obj.jm) + '/' + pad2(obj.jd);
  }

  global.Jalali = {
    MONTHS: JALALI_MONTHS,
    WEEKDAYS: WEEKDAY_NAMES,
    WEEKDAY_ABBR: PERSIAN_WEEKDAY_ABBR,
    toPersianDigits: toPersianDigits,
    toLatinDigits: toLatinDigits,
    pad2: pad2,
    jalaliToGregorian: jalaliToGregorian,
    gregorianToJalali: gregorianToJalali,
    isLeap: isLeapJalali,
    daysInMonth: daysInJalaliMonth,
    daysInYear: daysInJalaliYear,
    fromGregorian: fromGregorian,
    toGregorianDate: toGregorianDate,
    weekdayOf: weekdayOf,
    format: format,
    formatDate: formatDate,
    today: today,
    parse: parse,
    toLatinString: toLatinString
  };

})(window);
