%% This is file `hadith.sty',
%%
%% بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ
%% Dedicated to the service of Allah ﷻ
%%
%% Copyright (C) 2025 by Nibras Abo Alzahab
%%
%% This work may be distributed and/or modified under the
%% conditions of the LaTeX Project Public License, either version 1.3c
%% of this license or (at your option) any later version.
%% The latest version of this license is in
%% https://www.latex-project.org/lppl.txt
%% and version 1.3c or later is part of all distributions of LaTeX
%% version 2005/12/01 or later.
%%
%% This work has the LPPL maintenance status `maintained'.
%%
%% Package: hadith
%% Version: 1.1.0
%% Date: 2025-01-15
%% Author: Nibras Abo Alzahab
%% Email: nibras.abo.alzahab@gmail.com
%% License: LPPL 1.3c
%%

\NeedsTeXFormat{LaTeX2e}
\def\hadithdate{2025/01/15}
\def\hadithversion{1.1.0}
\ProvidesPackage{hadith}[\hadithdate\space v\hadithversion
  An easy way to typeset Hadith from canonical collections^^J]

%% --- Requirements ---
\RequirePackage{ifxetex}
\RequirePackage{biditools}
\RequirePackage{xkeyval}
\RequirePackage{xparse}
\RequirePackage{xstring}

%% --- Package options ---
\newif\ifhd@bukhari
\newif\ifhd@muslim
\newif\ifhd@abudawud
\newif\ifhd@tirmidhi
\newif\ifhd@nasai
\newif\ifhd@ibnmajah
\newif\ifhd@malik
\newif\ifhd@ahmad
\newif\ifhd@darimi
\newif\ifhd@all
\newif\ifhd@sixbooks
\newif\ifhd@nonumber
\newif\ifhd@ornbraces
\newif\ifhd@compact
\newif\ifhd@showgrade
\newif\ifhd@showtrans

\DeclareOptionX{bukhari}{\hd@bukharitrue}
\DeclareOptionX{muslim}{\hd@muslimtrue}
\DeclareOptionX{abudawud}{\hd@abudawudtrue}
\DeclareOptionX{tirmidhi}{\hd@tirmidhitrue}
\DeclareOptionX{nasai}{\hd@nasaitrue}
\DeclareOptionX{ibnmajah}{\hd@ibnmajahtrue}
\DeclareOptionX{malik}{\hd@maliktrue}
\DeclareOptionX{ahmad}{\hd@ahmadtrue}
\DeclareOptionX{darimi}{\hd@darimitrue}
\DeclareOptionX{all}{\hd@alltrue}
\DeclareOptionX{sixbooks}{\hd@sixbookstrue}
\DeclareOptionX{nonumber}{\hd@nonumbertrue}
\DeclareOptionX{ornbraces}{\hd@ornbracestrue}
\DeclareOptionX{compact}{\hd@compacttrue}
\DeclareOptionX{showgrade}{\hd@showgradetrue}
\DeclareOptionX{showtrans}{\hd@showtranstrue}

%% Default: load six books
\ExecuteOptionsX{sixbooks,showgrade}
\ProcessOptionsX

%% --- Resolve all/sixbooks to individual flags ---
%% Note: Only 7 of the traditional Nine Books are available in the data source.
%% Musnad Ahmad and Sunan al-Darimi are not available in fawazahmed0/hadith-api.
\ifhd@all
  \hd@bukharitrue\hd@muslimtrue\hd@abudawudtrue
  \hd@tirmidhitrue\hd@nasaitrue\hd@ibnmajahtrue
  \hd@maliktrue
\fi
\ifhd@sixbooks
  \hd@bukharitrue\hd@muslimtrue\hd@abudawudtrue
  \hd@tirmidhitrue\hd@nasaitrue\hd@ibnmajahtrue
\fi

%% ============================================================
%% Internal csname separator: use | (not @) to avoid conflicts
%% with \makeatletter where @ is a letter and \hd@coll@ would
%% be parsed as a single control sequence.
%% ============================================================

%% --- Internal command to define hadith text ---
%% \hadithdefcmd{collection}{number_key}{text}
%% Defines \csname hadithtext|collection|number_key\endcsname
%% Public name (no @) so it works in .def files loaded during document body
\def\hadithdefcmd#1#2#3{%
  \@namedef{hadithtext|#1|#2}{#3}%
}

%% --- Grade and translation markers (public names for .def files) ---
\def\hadithgrade#1{\def\hd@currentgrade{#1}}
\def\hadithtrans#1{\def\hd@currenttrans{#1}}
\def\hd@currentgrade{}
\def\hd@currenttrans{}

%% --- Hadith number formatting (public name for .def files) ---
\ifhd@ornbraces
  \def\hadithno#1{\,\textarabic{﴿#1﴾}}
\else
  \def\hadithno#1{\,\textarabic{[#1]}}
\fi
\ifhd@nonumber
  \def\hadithno#1{}
\fi

%% --- Collection name storage ---
\def\hadithcollectionname#1#2{%
  \@namedef{hd|name|#1}{#2}%
}

%% --- Section metadata storage ---
\def\hadithsectionname#1#2#3{%
  \@namedef{hd|section|#1|#2}{#3}%
}
\def\hadithsectionrange#1#2#3#4{%
  \@namedef{hd|sectionfirst|#1|#2}{#3}%
  \@namedef{hd|sectionlast|#1|#2}{#4}%
}

%% --- Chapter file index ---
%% \hadithchapterfile{collection}{book_num}{filename}{first}{last}
\def\hadithchapterfile#1#2#3#4#5{%
  \@namedef{hd|chapfile|#1|#2}{#3}%
  \@namedef{hd|chapfirst|#1|#2}{#4}%
  \@namedef{hd|chaplast|#1|#2}{#5}%
}

%% --- Track which chapter files have been loaded ---
\def\hd@markloaded#1#2{%
  \@namedef{hd|loaded|#1|#2}{1}%
}

%% --- Sanitize hadith number for \csname (replace . with p) ---
\def\hd@sanitize#1{%
  \StrSubstitute{#1}{.}{p}[\hd@temp]%
}

%% --- Find and load chapter for a given hadith number ---
%% \hd@searchchapters{hadith_num} -> loads the right chapter
%% Uses \hd@findcoll and \hd@key set by caller
\def\hd@searchchapters#1{%
  \def\hd@findnum{#1}%
  \hd@search@try{0}%
}
\def\hd@search@try#1{%
  \ifnum#1>200\relax
    % safety limit, stop
  \else
    \@ifundefined{hd|chapfirst|\hd@findcoll|#1}%
      {% chapter not defined, try next
        \expandafter\hd@search@try\expandafter{\the\numexpr#1+1\relax}%
      }%
      {%
        \@tempcnta=\@nameuse{hd|chapfirst|\hd@findcoll|#1}\relax
        \@tempcntb=\@nameuse{hd|chaplast|\hd@findcoll|#1}\relax
        \ifnum\hd@findnum<\@tempcnta\relax
          % before this chapter's range, try next
          \expandafter\hd@search@try\expandafter{\the\numexpr#1+1\relax}%
        \else
          \ifnum\hd@findnum>\@tempcntb\relax
            % after this chapter's range, try next
            \expandafter\hd@search@try\expandafter{\the\numexpr#1+1\relax}%
          \else
            % in range — load chapter and check if hadith is defined
            \hd@loadchapter{\hd@findcoll}{#1}%
            \@ifundefined{hadithtext|\hd@findcoll|\hd@key}%
              {% not in this chapter despite being in range, try next
                \expandafter\hd@search@try\expandafter{\the\numexpr#1+1\relax}%
              }%
              {}% found, stop
          \fi
        \fi
      }%
  \fi
}

%% --- On-demand chapter loading ---
%% \hd@loadchapter{collection}{book_num}
%% Chapter files are in subdirectory: {collection}/hadithtext-{collection}-ch{NN}.def
\def\hd@loadchapter#1#2{%
  \@ifundefined{hd|loaded|#1|#2}%
    {%
      \@ifundefined{hd|chapfile|#1|#2}%
        {\PackageWarning{hadith}{Chapter #2 not found in collection #1}}%
        {%
          \edef\hd@chapfilepath{#1/\@nameuse{hd|chapfile|#1|#2}}%
          \InputIfFileExists{\hd@chapfilepath}{}{%
            \InputIfFileExists{\@nameuse{hd|chapfile|#1|#2}}{}{%
              \PackageWarning{hadith}{File \@nameuse{hd|chapfile|#1|#2} not found}}%
          }%
          \hd@markloaded{#1}{#2}%
        }%
    }%
    {}% already loaded
}

%% --- Load metadata (index) files for requested collections ---
%% Only loads the index, not the full text
\ifhd@bukhari
  \InputIfFileExists{hadithmeta-bukhari.def}{}{%
    \PackageWarning{hadith}{hadithmeta-bukhari.def not found.%
      \MessageBreak Install the hadith package data files.}}
\fi
\ifhd@muslim
  \InputIfFileExists{hadithmeta-muslim.def}{}{%
    \PackageWarning{hadith}{hadithmeta-muslim.def not found.}}
\fi
\ifhd@abudawud
  \InputIfFileExists{hadithmeta-abudawud.def}{}{%
    \PackageWarning{hadith}{hadithmeta-abudawud.def not found.}}
\fi
\ifhd@tirmidhi
  \InputIfFileExists{hadithmeta-tirmidhi.def}{}{%
    \PackageWarning{hadith}{hadithmeta-tirmidhi.def not found.}}
\fi
\ifhd@nasai
  \InputIfFileExists{hadithmeta-nasai.def}{}{%
    \PackageWarning{hadith}{hadithmeta-nasai.def not found.}}
\fi
\ifhd@ibnmajah
  \InputIfFileExists{hadithmeta-ibnmajah.def}{}{%
    \PackageWarning{hadith}{hadithmeta-ibnmajah.def not found.}}
\fi
\ifhd@malik
  \InputIfFileExists{hadithmeta-malik.def}{}{%
    \PackageWarning{hadith}{hadithmeta-malik.def not found.}}
\fi
\ifhd@ahmad
  \InputIfFileExists{hadithmeta-ahmad.def}{}{%
    \PackageWarning{hadith}{hadithmeta-ahmad.def not found.}}
\fi
\ifhd@darimi
  \InputIfFileExists{hadithmeta-darimi.def}{}{%
    \PackageWarning{hadith}{hadithmeta-darimi.def not found.}}
\fi

%% --- Core: typeset a single hadith ---
%% \hadith[collection][number]
%% Starred version adds \par after
\DeclareDocumentCommand{\hadith}{ s o o }{%
  \IfNoValueTF{#2}%
    {\PackageError{hadith}{Collection name required}\MessageBreak
     Usage: \string\hadith[collection][number]}%
    {%
      \IfNoValueTF{#3}%
        {\PackageError{hadith}{Hadith number required}\MessageBreak
         Usage: \string\hadith[#2][number]}%
        {%
          \hd@typeset{#2}{#3}%
          \IfBooleanT{#1}{\par}%
        }%
    }%
}

%% --- Internal: lowercase collection name into \hd@coll ---
\def\hd@setcoll#1{%
  \lowercase{\def\hd@coll{#1}}%
}

%% --- Internal: typeset the hadith text (with on-demand loading) ---
\def\hd@typeset#1#2{%
  % Lowercase the collection name
  \hd@setcoll{#1}%
  % Sanitize hadith number
  \hd@sanitize{#2}%
  \let\hd@key\hd@temp
  % Check if hadith is already defined
  \@ifundefined{hadithtext|\hd@coll|\hd@key}%
    {%
      % Not loaded — search chapters and load on demand
      \let\hd@findcoll\hd@coll
      \hd@searchchapters{#2}%
      % Check if found now
      \@ifundefined{hadithtext|\hd@coll|\hd@key}%
        {\PackageError{hadith}{Hadith #2 not found in collection #1}%
          {The collection '#1' may not be loaded, or hadith #2 does not exist.}}%
        {\hd@render{\hd@coll}{\hd@key}{#2}}%
    }%
    {%
      % Already loaded
      \hd@render{\hd@coll}{\hd@key}{#2}%
    }%
}

%% --- Internal: render the hadith text ---
\def\hd@render#1#2#3{%
  \begingroup
  \def\hd@currentgrade{}%
  \def\hd@currenttrans{}%
  \setRTL
  \@nameuse{hadithtext|#1|#2}%
  \endgroup
  % Show grade if available and option is set
  \ifhd@showgrade
    \ifx\hd@currentgrade\empty\else
      \par\noindent\textit{\small\textenglish{Grade: \hd@currentgrade}}%
    \fi
  \fi
  % Show translation if available and option is set
  \ifhd@showtrans
    \ifx\hd@currenttrans\empty\else
      \par\noindent\textenglish{\hd@currenttrans}%
    \fi
  \fi
}

%% --- Typeset a range of hadiths ---
%% \hadithrange[collection][start-end]
\DeclareDocumentCommand{\hadithrange}{ o o }{%
  \IfNoValueTF{#1}%
    {\PackageError{hadith}{Collection name required}}%
    {%
      \IfNoValueTF{#2}%
        {\PackageError{hadith}{Range required (e.g., 1-10)}}%
        {\hd@typesetrange{#1}{#2}}%
    }%
}

\def\hd@typesetrange#1#2{%
  \StrBefore{#2}{-}[\hd@start]
  \StrBehind{#2}{-}[\hd@end]
  \ifx\hd@start\empty
    \hd@typeset{#1}{#2}%
  \else
    \hd@rangefrom{#1}{\hd@start}{\hd@end}%
  \fi
}

\newcounter{hd@count}
\def\hd@rangefrom#1#2#3{%
  \setcounter{hd@count}{#2}%
  \loop
    \hd@typeset{#1}{\arabic{hd@count}}\par
    \stepcounter{hd@count}%
  \ifnum\value{hd@count}<#3\relax\repeat
}

%% --- Shortcut commands per collection ---
\newcommand{\hadithbukhari}[1]{\hadith[bukhari][#1]}
\newcommand{\hadithmuslim}[1]{\hadith[muslim][#1]}
\newcommand{\hadithabudawud}[1]{\hadith[abudawud][#1]}
\newcommand{\hadithtirmidhi}[1]{\hadith[tirmidhi][#1]}
\newcommand{\hadithnasai}[1]{\hadith[nasai][#1]}
\newcommand{\hadithibnmajah}[1]{\hadith[ibnmajah][#1]}
\newcommand{\hadithmalik}[1]{\hadith[malik][#1]}
\newcommand{\hadithahmad}[1]{\hadith[ahmad][#1]}
\newcommand{\hadithdarimi}[1]{\hadith[darimi][#1]}

%% --- Citation command (inline) ---
\DeclareDocumentCommand{\hadithcite}{ o o }{%
  \IfNoValueTF{#1}%
    {\textit{[Hadith]}}%
    {%
      \hd@setcoll{#1}%
      \IfNoValueTF{#2}%
        {\textit{[\@nameuse{hd|name|\hd@coll}]}}%
        {\textit{[\@nameuse{hd|name|\hd@coll}, \##2]}}%
    }%
}

%% --- Get collection name ---
\newcommand{\hadithname}[1]{%
  \hd@setcoll{#1}%
  \@nameuse{hd|name|\hd@coll}%
}

%% --- Get section name ---
\newcommand{\hadithsection}[2]{%
  \hd@setcoll{#1}%
  \@nameuse{hd|section|\hd@coll|#2}%
}

%% --- End of package ---
\endinput
