% This file is a documentation support file for the unipersian package.
%
% It provides the date-conversion functionality required by shamsi
% in the documentation of unipersian. It is not part of the unipersian
% package itself.
%
% This file is currently distributed only as part of the source
% of the unipersian documentation and is not intended as an
% independently released package.

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{jalali-engine}[2026/09/08 Documentation support for the unipersian package]

\RequirePackage{expl3}

\ExplSyntaxOn

% ------------------------------------------------
% Global result variables (Public for Interface)
% ------------------------------------------------
\int_new:N \g_jd_gy_int
\int_new:N \g_jd_gm_int
\int_new:N \g_jd_gd_int

\int_new:N \g_jd_jy_int
\int_new:N \g_jd_jm_int
\int_new:N \g_jd_jd_int

\int_new:N \g_jd_wd_int

% ------------------------------------------------
% Local working variables
% ------------------------------------------------
\int_new:N \l_jd_days_int
\int_new:N \l_jd_leap_int
\int_new:N \l_jd_month_len_int
\int_new:N \l_jd_wd_int
\int_new:N \l_jd_tmp_int

\int_new:N \l_jd_y_int
\int_new:N \l_jd_m_int
\int_new:N \l_jd_d_int

% ------------------------------------------------
% Sakamoto month code
% ------------------------------------------------
\cs_new:Npn \jd_sakamoto_month_code:n #1
{
    \int_case:nn {#1}
    {
        {1}{0} {2}{3} {3}{2} {4}{5} {5}{0} {6}{3}
        {7}{5} {8}{1} {9}{4} {10}{6} {11}{2} {12}{4}
    }
}

% ------------------------------------------------
% Gregorian leap-year test
% ------------------------------------------------
\cs_new:Npn \jd_is_gregorian_leap:n #1
{
    \bool_if:nTF
    {
        ( \int_compare_p:nNn { \int_mod:nn {#1} {4} } = {0}
        && ! \int_compare_p:nNn { \int_mod:nn {#1} {100} } = {0} )
        || \int_compare_p:nNn { \int_mod:nn {#1} {400} } = {0}
    } {1} {0}
}

% ------------------------------------------------
% Weekday calculation (Result in \l_jd_wd_int)
% ------------------------------------------------
\cs_new_protected:Npn \jd_calculate_weekday_logic:nnn #1#2#3
{
    \int_set:Nn \l_jd_tmp_int {#1}
    \int_compare:nT { #2 < 3 } { \int_decr:N \l_jd_tmp_int }
    \int_set:Nn \l_jd_wd_int
    {
        \int_mod:nn { \l_jd_tmp_int + \int_div_truncate:nn { \l_jd_tmp_int } {4}
            - \int_div_truncate:nn { \l_jd_tmp_int } {100} + \int_div_truncate:nn { \l_jd_tmp_int } {400}
            + \jd_sakamoto_month_code:n {#2} + #3 } {7}
    }
}

% ------------------------------------------------
% Core: Gregorian -> Jalali
% ------------------------------------------------
\cs_new_protected:Npn \jd_convert_gregorian_to_jalali_core:nnn #1#2#3
{
    \group_begin:
    \int_set:Nn \l_jd_tmp_int { #1 - 1600 }
    \int_set:Nn \l_jd_days_int { 365 * \l_jd_tmp_int + \int_div_truncate:nn { \l_jd_tmp_int + 3 } {4} - \int_div_truncate:nn { \l_jd_tmp_int + 99 } {100} + \int_div_truncate:nn { \l_jd_tmp_int + 399 } {400} }

    \int_case:nn {#2} { {1}{ \int_add:Nn \l_jd_days_int {0} } {2}{ \int_add:Nn \l_jd_days_int {31} } {3}{ \int_add:Nn \l_jd_days_int {59} } {4}{ \int_add:Nn \l_jd_days_int {90} } {5}{ \int_add:Nn \l_jd_days_int {120} } {6}{ \int_add:Nn \l_jd_days_int {151} } {7}{ \int_add:Nn \l_jd_days_int {181} } {8}{ \int_add:Nn \l_jd_days_int {212} } {9}{ \int_add:Nn \l_jd_days_int {243} } {10}{ \int_add:Nn \l_jd_days_int {273} } {11}{ \int_add:Nn \l_jd_days_int {304} } {12}{ \int_add:Nn \l_jd_days_int {334} } }
    \int_compare:nT { #2 > 2 } { \int_add:Nn \l_jd_days_int { \jd_is_gregorian_leap:n {#1} } }
    \int_add:Nn \l_jd_days_int { #3 - 1 }

    \int_set:Nn \l_jd_days_int { \l_jd_days_int - 79 }

    \int_set:Nn \l_jd_tmp_int { \int_div_truncate:nn { \l_jd_days_int } {12053} }
    \int_set:Nn \l_jd_days_int { \int_mod:nn { \l_jd_days_int } {12053} }
    \int_set:Nn \l_jd_y_int { 979 + 33 * \l_jd_tmp_int + 4 * \int_div_truncate:nn { \l_jd_days_int } {1461} }
    \int_set:Nn \l_jd_days_int { \int_mod:nn { \l_jd_days_int } {1461} }

    \int_compare:nT { \l_jd_days_int > 365 }
    {
        \int_add:Nn \l_jd_y_int { \int_div_truncate:nn { \l_jd_days_int - 1 } {365} }
        \int_set:Nn \l_jd_days_int { \int_mod:nn { \l_jd_days_int - 1 } {365} }
    }

    \int_compare:nTF { \l_jd_days_int < 186 }
    { \int_set:Nn \l_jd_m_int { 1 + \int_div_truncate:nn { \l_jd_days_int } {31} } \int_set:Nn \l_jd_d_int { 1 + \int_mod:nn { \l_jd_days_int } {31} } }
    { \int_set:Nn \l_jd_m_int { 7 + \int_div_truncate:nn { \l_jd_days_int - 186 } {30} } \int_set:Nn \l_jd_d_int { 1 + \int_mod:nn { \l_jd_days_int - 186 } {30} } }

    \int_gset:Nn \g_jd_gy_int {#1} \int_gset:Nn \g_jd_gm_int {#2} \int_gset:Nn \g_jd_gd_int {#3}
    \int_gset:Nn \g_jd_jy_int { \l_jd_y_int } \int_gset:Nn \g_jd_jm_int { \l_jd_m_int } \int_gset:Nn \g_jd_jd_int { \l_jd_d_int }

    \jd_calculate_weekday_logic:nnn {#1}{#2}{#3}
    \int_gset_eq:NN \g_jd_wd_int \l_jd_wd_int

    \group_end:
}

% ------------------------------------------------
% Core: Jalali -> Gregorian
% ------------------------------------------------
\cs_new_protected:Npn \jd_convert_jalali_to_gregorian_core:nnn #1#2#3
{
    \group_begin:
    \int_set:Nn \l_jd_tmp_int { #1 - 979 }
    \int_set:Nn \l_jd_days_int { 365 * \l_jd_tmp_int + \int_div_truncate:nn { \l_jd_tmp_int } {33} * 8 + \int_div_truncate:nn { \int_mod:nn { \l_jd_tmp_int } {33} + 3 } {4} }
    \int_compare:nTF { #2 < 7 } { \int_add:Nn \l_jd_days_int { ( #2 - 1 ) * 31 } } { \int_add:Nn \l_jd_days_int { ( #2 - 1 ) * 30 + 6 } }
    \int_add:Nn \l_jd_days_int { #3 + 78 }

    \int_set:Nn \l_jd_y_int { 1600 + 400 * \int_div_truncate:nn { \l_jd_days_int } {146097} }
    \int_set:Nn \l_jd_days_int { \int_mod:nn { \l_jd_days_int } {146097} }
    \int_compare:nT { \l_jd_days_int > 36524 }
    { \int_sub:Nn \l_jd_days_int { 1 } \int_add:Nn \l_jd_y_int { 100 * \int_div_truncate:nn { \l_jd_days_int } {36524} } \int_set:Nn \l_jd_days_int { \int_mod:nn { \l_jd_days_int } {36524} } \int_compare:nT { \l_jd_days_int >= 365 } { \int_add:Nn \l_jd_days_int { 1 } } }

    \int_add:Nn \l_jd_y_int { 4 * \int_div_truncate:nn { \l_jd_days_int } {1461} }
    \int_set:Nn \l_jd_days_int { \int_mod:nn { \l_jd_days_int } {1461} }
    \int_compare:nT { \l_jd_days_int > 365 } { \int_add:Nn \l_jd_y_int { \int_div_truncate:nn { \l_jd_days_int - 1 } {365} } \int_set:Nn \l_jd_days_int { \int_mod:nn { \l_jd_days_int - 1 } {365} } }

    \int_set:Nn \l_jd_d_int { \l_jd_days_int + 1 }
    \int_set:Nn \l_jd_m_int { 1 }
    \int_set:Nn \l_jd_leap_int { \jd_is_gregorian_leap:n { \l_jd_y_int } }

    \int_step_inline:nn {12}
    {
        \int_set:Nn \l_jd_month_len_int
        {
            \int_case:nn {##1}
            { {1}{31} {2}{ \int_eval:n { 28 + \l_jd_leap_int } } {3}{31} {4}{30} {5}{31} {6}{30} {7}{31} {8}{31} {9}{30} {10}{31} {11}{30} {12}{31} }
        }
        \int_compare:nT { \l_jd_d_int > \l_jd_month_len_int }
        {
            \int_sub:Nn \l_jd_d_int { \l_jd_month_len_int }
            \int_incr:N \l_jd_m_int
        }
    }

    \int_gset:Nn \g_jd_gy_int { \l_jd_y_int } \int_gset:Nn \g_jd_gm_int { \l_jd_m_int } \int_gset:Nn \g_jd_gd_int { \l_jd_d_int }
    \int_gset:Nn \g_jd_jy_int {#1} \int_gset:Nn \g_jd_jm_int {#2} \int_gset:Nn \g_jd_jd_int {#3}

    \jd_calculate_weekday_logic:nnn { \l_jd_y_int } { \l_jd_m_int } { \l_jd_d_int }
    \int_gset_eq:NN \g_jd_wd_int \l_jd_wd_int

    \group_end:
}

\ExplSyntaxOff
