% numodel-coach.dtx
%
% Docstrip source for numodel-coach.sty.
% Run
%   tex numodel-coach.ins
% to extract the derived file.  User-facing documentation lives
% in numodel-coach-manual.tex (a stand-alone LaTeX file).
%
% Copyright (C) 2026 Paul Zuurbier <mail@paulzuurbier.nl>
%
% 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
%
% This work has the LPPL maintenance status 'maintained'.
% The Current Maintainer of this work is Paul Zuurbier.
%
% This work consists of the files numodel-coach.dtx,
% numodel-coach.ins, the derived file numodel-coach.sty, and the
% companion Lua files numodel-coach.lua and numodel-coach-template.lua.
% \section{Implementation}
%
% numodel-coach writes a numodel model as a CMA Coach 7 modelling
% activity (.cma7) in text mode.  It reads the model only through
% numodel's Lua export API (numodel.get_model, numodel.plaintext) and
% the public variable \cs{g_numodel_current_prefix_tl}; the Coach file
% format lives entirely in numodel-coach.lua.
%
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesExplPackage{numodel-coach}{2026/10/08}{0.10.0}
  {Export numodel models to CMA Coach 7}

\sys_if_engine_luatex:F
  {
    \msg_new:nnn { numodel-coach } { luatex-only }
      { numodel-coach~requires~LuaLaTeX. }
    \msg_critical:nn { numodel-coach } { luatex-only }
  }

\RequirePackage{numodel}
\RequirePackage{embedfile}

\lua_load_module:n { numodel-coach }
%    \end{macrocode}
%
% \subsection{Options}
%
% \begin{description}
%   \item[dir] directory the files are written to, relative to the
%     working directory (default: empty, the working directory itself).
%     Created when it does not exist.
%   \item[attach] also embed the file in the PDF (default false).
%   \item[switch] let pupils switch between the text and the graphical
%     view in Coach (default false).  Coach derives the text view from
%     the graphical model, which numodel-coach leaves empty, so
%     switching loses the model.
% \end{description}
%
%    \begin{macrocode}
\tl_new:N   \g__numodelcoach_dir_tl
\bool_new:N \g__numodelcoach_attach_bool
\bool_new:N \g__numodelcoach_switch_bool
\tl_new:N   \l__numodelcoach_prefix_tl
\tl_new:N   \l__numodelcoach_dir_tl
\bool_new:N \l__numodelcoach_attach_bool
\bool_new:N \l__numodelcoach_switch_bool
\tl_new:N   \l__numodelcoach_name_tl
\tl_new:N   \l__numodelcoach_path_tl

\keys_define:nn { numodel-coach / setup }
  {
    dir    .tl_gset:N   = \g__numodelcoach_dir_tl,
    attach .bool_gset:N = \g__numodelcoach_attach_bool,
    switch .bool_gset:N = \g__numodelcoach_switch_bool,
  }
\keys_define:nn { numodel-coach / model }
  {
    prefix .tl_set:N   = \l__numodelcoach_prefix_tl,
    dir    .tl_set:N   = \l__numodelcoach_dir_tl,
    attach .bool_set:N = \l__numodelcoach_attach_bool,
    switch .bool_set:N = \l__numodelcoach_switch_bool,
  }

\NewDocumentCommand \coachsetup { m }
  { \keys_set:nn { numodel-coach / setup } {#1} }
%    \end{macrocode}
%
% \subsection{\cs{coachmodel}}
%
% \cs{coachmodel}\oarg{options}\marg{name} writes the model with the
% current prefix (or \texttt{prefix=}) to \meta{dir}/\meta{name}.cma7.
% Anything that cannot be exported exactly is reported as a warning.
% The iteration count is numodel's \texttt{maxiter}.
%
%    \begin{macrocode}
\msg_new:nnn { numodel-coach } { no-model }
  { No~numodel~model~with~prefix~'#1'.~
    Declare~it~with~\token_to_str:N \newmodelprefix\space first. }
\msg_new:nnn { numodel-coach } { export }
  { Model~'#1'~(#2):~#3 }
\msg_new:nnn { numodel-coach } { written }
  { Wrote~Coach~activity~'#1'. }

% Called from Lua (numodelcoach.tex_write) with plain-letter names, so
% they can be used without expl3 catcodes; the arguments arrive as
% catcode-12 strings.
\cs_new_protected:Npn \numodelcoachwarn #1#2#3
  { \msg_warning:nnnnn { numodel-coach } { export } {#1} {#2} {#3} }
\cs_new_protected:Npn \numodelcoachwritten #1
  { \msg_info:nnn { numodel-coach } { written } {#1} }
\cs_new_protected:Npn \numodelcoachnomodel #1
  { \msg_error:nnn { numodel-coach } { no-model } {#1} }

%    \end{macrocode}
%
% \subsection{Instruction text}
%
% The environment \texttt{coachinstruction} defines the instruction text
% of a model (current prefix or \texttt{prefix=}); it typesets nothing.
% \cs{coachinstructiontext} shows it in the PDF, wherever and as often
% as wanted, and \cs{coachmodel} writes it to Coach's instruction window
% (converted to HTML by numodelcoach.instruction_html).  One source, two
% outputs.  The body is kept twice: as tokens for the PDF, and
% detokenized for the conversion.  One instruction per model: a second
% definition replaces the first with a warning, as \cs{mvar} does.
%
%    \begin{macrocode}
\msg_new:nnn { numodel-coach } { instruction-redefined }
  { The~instruction~text~of~model~'#1'~is~being~redefined. }
\msg_new:nnn { numodel-coach } { no-instruction }
  { Model~'#1'~has~no~instruction~text~(coachinstruction). }

\keys_define:nn { numodel-coach / instruction }
  { prefix .tl_set:N = \l__numodelcoach_prefix_tl }

\NewDocumentEnvironment { coachinstruction } { O{} +b }
  {
    \tl_set_eq:NN \l__numodelcoach_prefix_tl \g_numodel_current_prefix_tl
    \keys_set:nn { numodel-coach / instruction } {#1}
    \tl_if_exist:cTF
      { g__numodelcoach_instruction_ \l__numodelcoach_prefix_tl _tl }
      {
        \msg_warning:nne { numodel-coach } { instruction-redefined }
          { \l__numodelcoach_prefix_tl }
      }
      { \tl_new:c { g__numodelcoach_instruction_ \l__numodelcoach_prefix_tl _tl } }
    \tl_gset:cn
      { g__numodelcoach_instruction_ \l__numodelcoach_prefix_tl _tl } {#2}
    \lua_now:e
      {
        numodelcoach.set_instruction(
          "\lua_escape:e { \l__numodelcoach_prefix_tl }",
          "\lua_escape:e { \detokenize {#2} }")
      }
  }
  { }

\NewDocumentCommand \coachinstructiontext { O{} }
  {
    \tl_set_eq:NN \l__numodelcoach_prefix_tl \g_numodel_current_prefix_tl
    \keys_set:nn { numodel-coach / instruction } {#1}
    \tl_if_exist:cTF
      { g__numodelcoach_instruction_ \l__numodelcoach_prefix_tl _tl }
      { \tl_use:c { g__numodelcoach_instruction_ \l__numodelcoach_prefix_tl _tl } }
      {
        \msg_warning:nne { numodel-coach } { no-instruction }
          { \l__numodelcoach_prefix_tl }
      }
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\NewDocumentCommand \coachmodel { O{} m }
  {
    \tl_set_eq:NN \l__numodelcoach_prefix_tl \g_numodel_current_prefix_tl
    \tl_set_eq:NN \l__numodelcoach_dir_tl \g__numodelcoach_dir_tl
    \bool_set_eq:NN \l__numodelcoach_attach_bool \g__numodelcoach_attach_bool
    \bool_set_eq:NN \l__numodelcoach_switch_bool \g__numodelcoach_switch_bool
    \keys_set:nn { numodel-coach / model } {#1}
    \__numodelcoach_write:n {#2}
  }

\cs_new_protected:Npn \__numodelcoach_write:n #1
  {
    \tl_set:Nn \l__numodelcoach_name_tl {#1}
    \str_if_in:nnF {#1} { . }
      { \tl_put_right:Nn \l__numodelcoach_name_tl { .cma7 } }
    \tl_set:Ne \l__numodelcoach_path_tl
      {
        \tl_if_empty:NF \l__numodelcoach_dir_tl
          { \tl_use:N \l__numodelcoach_dir_tl / }
        \tl_use:N \l__numodelcoach_name_tl
      }
    \lua_now:e
      {
        numodelcoach.tex_write(
          "\lua_escape:e { \l__numodelcoach_prefix_tl }",
          "\lua_escape:e { \l__numodelcoach_path_tl }",
          \bool_if:NTF \l__numodelcoach_switch_bool { true } { false })
      }
    \bool_if:NT \l__numodelcoach_attach_bool
      {
        \use:e
          {
            \exp_not:N \embedfile
              [ filespec = { \tl_use:N \l__numodelcoach_name_tl } ,
                mimetype = application/octet-stream ,
                desc = { Coach~7~model~\tl_to_str:n {#1} } ]
              { \tl_use:N \l__numodelcoach_path_tl }
          }
      }
  }
%    \end{macrocode}
