(function (window) {
    'use strict';

    var ARABIC_DIGITS = '٠١٢٣٤٥٦٧٨٩';
    var PERSIAN_DIGITS = '۰۱۲۳۴۵۶۷۸۹';

    function intdiv(a, b) {
        return Math.floor(a / b);
    }

    function pad2(n) {
        return n < 10 ? '0' + n : String(n);
    }

    function toArabicDigits(str) {
        if (typeof window.zertiLocalizeDigits === 'function') {
            return window.zertiLocalizeDigits(str);
        }
        return String(str).replace(/[0-9]/g, function (d) {
            return ARABIC_DIGITS.charAt(parseInt(d, 10));
        });
    }

    function toLatinDigits(str) {
        if (typeof window.zertiToLatinDigits === 'function') {
            return window.zertiToLatinDigits(str);
        }
        return String(str)
            .replace(/[۰-۹]/g, function (d) {
                return String(PERSIAN_DIGITS.indexOf(d));
            })
            .replace(/[٠-٩]/g, function (d) {
                return String(ARABIC_DIGITS.indexOf(d));
            });
    }

    /** Same algorithm as zerti_gregorian_to_julian_day() in PHP. */
    function gregorianToJulianDay(y, m, d) {
        var a = intdiv(14 - m, 12);
        y = y + 4800 - a;
        m = m + 12 * a - 3;
        return d + intdiv(153 * m + 2, 5) + 365 * y + intdiv(y, 4) - intdiv(y, 100) + intdiv(y, 400) - 32045;
    }

    /** Same algorithm as zerti_julian_day_to_gregorian() in PHP. */
    function julianDayToGregorian(jdn) {
        var a = jdn + 32044;
        var b = intdiv(4 * a + 3, 146097);
        var c = a - intdiv(146097 * b, 4);
        var d = intdiv(4 * c + 3, 1461);
        var e = c - intdiv(1461 * d, 4);
        var m = intdiv(5 * e + 2, 153);
        var day = e - intdiv(153 * m + 2, 5) + 1;
        var month = m + 3 - 12 * intdiv(m, 10);
        var year = 100 * b + d - 4800 + intdiv(m, 10);
        return [year, month, day];
    }

    /** Same algorithm as zerti_gregorian_to_hijri() in PHP. */
    function gregorianToHijri(gy, gm, gd) {
        var jd = gregorianToJulianDay(gy, gm, gd);
        var l = jd - 1948440 + 10632;
        var n = intdiv(l - 1, 10631);
        l = l - 10631 * n + 354;
        var j =
            intdiv(10985 - l, 5316) * intdiv(50 * l, 17719) +
            intdiv(l, 5670) * intdiv(43 * l, 15238);
        l =
            l -
            intdiv(30 - j, 15) * intdiv(17719 * j, 50) -
            intdiv(j, 16) * intdiv(15238 * j, 43) +
            29;
        var hm = intdiv(24 * l, 709);
        var hd = l - intdiv(709 * hm, 24);
        var hy = 30 * n + j - 30;
        return [hy, hm, hd];
    }

    /** Same algorithm as zerti_hijri_to_gregorian() in PHP. */
    function hijriToGregorian(hy, hm, hd) {
        var jd = intdiv(11 * hy + 3, 30) + 354 * hy + 30 * hm - intdiv(hm - 1, 2) + hd + 1948440 - 385;
        return julianDayToGregorian(jd);
    }

    function isHijriLeap(hy) {
        return (11 * hy + 14) % 30 < 11;
    }

    function hijriMonthLength(hy, hm) {
        if (hm === 12) {
            return isHijriLeap(hy) ? 30 : 29;
        }
        return hm % 2 === 1 ? 30 : 29;
    }

    function formatHijriParts(hy, hm, hd, arabicDigits) {
        var out = pad2(hy) + '/' + pad2(hm) + '/' + pad2(hd);
        return arabicDigits ? toArabicDigits(out) : out;
    }

    function parseHijriString(raw) {
        var latin = toLatinDigits(String(raw || '').trim());
        if (latin === '') {
            return null;
        }

        var gregorian = latin.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
        if (gregorian) {
            return gregorianToHijri(parseInt(gregorian[1], 10), parseInt(gregorian[2], 10), parseInt(gregorian[3], 10));
        }

        var hijri = latin.match(/^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})/);
        if (!hijri) {
            return null;
        }

        return [parseInt(hijri[1], 10), parseInt(hijri[2], 10), parseInt(hijri[3], 10)];
    }

    function gregorianYmdToHijriDisplay(ymd, arabicDigits) {
        var m = toLatinDigits(String(ymd || '')).match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
        if (!m) {
            return '';
        }
        var parts = gregorianToHijri(parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10));
        return formatHijriParts(parts[0], parts[1], parts[2], arabicDigits !== false);
    }

    function hijriWeekday(hy, hm, hd) {
        var g = hijriToGregorian(hy, hm, hd);
        return new Date(g[0], g[1] - 1, g[2]).getDay();
    }

    function todayHijriParts(timeZone) {
        var now = new Date();
        var fmt = new Intl.DateTimeFormat('en-GB', {
            timeZone: timeZone || undefined,
            year: 'numeric',
            month: 'numeric',
            day: 'numeric',
        });
        var map = {};
        fmt.formatToParts(now).forEach(function (part) {
            if (part.type !== 'literal') {
                map[part.type] = part.value;
            }
        });
        return gregorianToHijri(parseInt(map.year, 10), parseInt(map.month, 10), parseInt(map.day, 10));
    }

    window.zertiHijriDate = {
        toArabicDigits: toArabicDigits,
        toLatinDigits: toLatinDigits,
        gregorianToHijri: gregorianToHijri,
        hijriToGregorian: hijriToGregorian,
        hijriMonthLength: hijriMonthLength,
        formatHijriParts: formatHijriParts,
        parseHijriString: parseHijriString,
        gregorianYmdToHijriDisplay: gregorianYmdToHijriDisplay,
        hijriWeekday: hijriWeekday,
        todayHijriParts: todayHijriParts,
    };
})(window);
