\ProvidesExplPackage {luatagging} {2026-09-20} {0.1a}
  { LuaLaTeX code for PDF tagging }

\RequirePackage{iftex}
\RequireLuaTeX

\RequirePackage{luatagging-lua3keyval}
\lua_load_module:n { luatagging }

\tl_new:N \l__luatag_commands_tl
\tl_new:N \l__luatag_parent_tl
\bool_new:N \l__luatag_artifact_bool
\bool_new:N \l__luatag_firstkid_bool

\hook_gput_code:nnn{package/tagpdf/after}{luatagging}{
  \cs_set_protected:Npn \tag_struct_begin:n %#1 key-val
    {
      \__tag_check_if_active_struct:TF
        {
          \StructBeginKV
        } {
          \use_none:n
        }
    }

  % Note: This removes adding a reference to the StructTreeRoot, but does not disable writing the StructTreeRoot from
  % tagpdf. Ideally that would change in the longer term
  \hook_gremove_code:nn {shipout/lastpage} {tagpdf}% We probably want to readd the open action handling.

  \msg_new:nnn {luatag} {begin-pop-parameter-not-empty} {\string\tag_mc_begin_pop:n~is~only~supported~with~empty~argument.}
  \msg_new:nnn {luatag} {unsupported} {Luatagging~does~not~support~#1.}
  \cs_new:Npn \__luatag_unsupported:n {
    \msg_error:nnn {luatag} {unsupported}
  }


  \cs_set_protected:Npn \tag_struct_end:
    {
      \__tag_check_if_active_struct:T
        {
          \StructEnd
       }
    }


  \cs_set_protected:Npn \tag_mc_begin:n {
    \__tag_check_if_active_mc:TF {
      \BeginMc
    } {
      \use_none:n
    }
  }

  \cs_set_protected:Nn \tag_mc_end: {
    \__tag_check_if_active_mc:T {
      \ActivateMcArtifact
    }
  }

  \cs_set_protected:Nn \tag_mc_begin_pop:n {
    \__tag_check_if_active_mc:T {
      \tl_if_empty:nF {#1} {
        \msg_error:nn {luatag} {begin-pop-parameter-not-empty}
      }
      \PopMc
    }
  }

  \cs_set_protected:Nn \tag_mc_end_push: {
    \__tag_check_if_active_mc:T {
      \PushMc
    }
  }
  \ActivateMcArtifact

  \cs_set_eq:NN \__tag_attr_new_entry:nn \RegisterAttribute
  \cs_set_eq:NN \__tag_attr_new_used_entry:nn \RegisterAttribute

  \cs_undefine:N \__tag_role_check_parent_child:nnN
  \cs_undefine:N \__tag_role_check_parent_child:nnnnN

  \cs_set_protected:Nn \__tag_role_NS_new:nnn {
    \errmessage{Unsupported~\noexpand \__tag_role_NS_new:nnn \unexpanded{{#1}{#2}{#3}}}
  }

  \cs_set_protected:Nn \__tag_role_alloctag:nnn {
    \errmessage{Unsupported~\noexpand \__tag_role_alloctag:nnn \unexpanded{{#1}{#2}{#3}}}
  }

  \cs_set_protected:Nn \__tag_role_add_tag:nn {
    \errmessage{Unsupported~\noexpand \__tag_role_add_tag:nn \unexpanded{{#1}{#2}}}
  }

  \cs_set_protected:Nn \__tag_role_get:nnNN {
    \errmessage{Unsupported~\noexpand \__tag_role_get:nnNN \unexpanded{{#1}{#2}#3#4}}
  }

  \cs_set_protected:Nn \__tag_role_add_tag:nnnn {
    \AddTagNS{#1}{#2}{#3}{#4}
  }

  % FIXME: Real implementation
  \prg_gset_conditional:Npnn \tag_check_child:nn #1 #2 {TF, T, F} {
    \prg_return_true:
  }

  \socket_new_plug:nnn { tag/struct/tag }{ lua-latex-tags }
   {
     \prop_get:NeNTF \g__tag_role_tags_NS_prop {#1} \l__tag_tmp_unused_tl
      {
        \seq_set_split:Nne \l__tag_tmpa_seq { / }
          {#1/\l__tag_tmp_unused_tl}
      }
      {
        \seq_set_split:Nne \l__tag_tmpa_seq { / }
          {#1/}
      }
     \tl_gset:Ne \g__tag_struct_tag_tl   { \seq_item:Nn\l__tag_tmpa_seq {1} }
     \tl_gset:Ne \g__tag_struct_tag_NS_tl{ \seq_item:Nn\l__tag_tmpa_seq {2} }
     % \@@_check_structure_tag:N \g__tag_struct_tag_tl
   }

  \socket_assign_plug:nn { tag/struct/tag } { lua-latex-tags }

  \cs_gset:Npn \tag_struct_recordtarget: {
      \SetStructProperty {Dest} {\@currentHref}
  }

  % Could be supported, but is currently only used in already replaced code.
  \cs_gset:Npn \__tag_get_data_mc_tag: {
    \get_mc_tag_is_not_supported
  }
  % Would be awkward to support and is not used anywhere.
  \cs_gset:Npn \__tag_get_data_mc_counter: {
    \get_mc_counter_is_not_supported
  }
  % We don't assign IDs, so this would be tricky. It's also not used.
  \cs_gset:Npn \__tag_get_data_struct_id: {
    \get_struct_id_is_not_supported
  }
  % Sadly used rather often.
  \cs_gset:Npn \__tag_get_data_struct_num: {
    \GetStructNum
  }
  \cs_set_eq:NN \g_@@_struct_stack_current_tl \GetStructNum

  \cs_gset:Npn \__tag_get_data_struct_tag: {
    \GetCurrentStructTag
  }

  \cs_gset:Npn \__tag_get_data_struct_C:n #1 {
    \GetStructClassClist \int_eval:n {#1} ~
  }

  % The way the Lua interaction works there's really no
  % reason to differentiate `\tag_get:nN` and `\tag_get:n`,
  % so we just implement one in terms of the other.
  \cs_set_protected:Npn \tag_get:nN #1#2 {
    \tl_set:Nx #2 { \tag_get:n { #1 } }
  }
  % For \tag_get:nnN there is no \tag_get:nn variant, so we define it.
  \cs_new:Npn \tag_get:nn #1 #2 {
    \cs_if_exist_use:cTF { __tag_get_data_#2:n } { { #1 } } {
      \__luatag_unsupported:n {\tag_get:nnN{#1}{?}{?}}
    }
  }
  \cs_set_protected:Npn \tag_get:nnN #1#2#3 {
    \tl_set:Nx #3 { \tag_get:nn { #1 } { #2 } }
  }

  \cs_set_protected:Npn \tag_struct_gput:nnn #1 #2 #3 {
    \tl_if_eq:nnTF { \tag_get:n { struct_num } } { #1 } {
      \SetStructProperty
    } {
      \SetStructPropertyForStructNum #1
    }
    \str_case:nnF { #2 } {
      { ref_dest_parent } { { Ref-dest-parent } { #3 } }
      { ref_dest } { { Ref-dest } { #3 } }
      { ref_num } { { Ref-num } { #3 } }
      { attribute } { { Attribute-inline }{ #3 } }
      { tag } { { Tag }{ \tl_tail:n { #3 } } } % \tl_tail:n to strip a leading /
      { C } { { Attribute-class-list }{ #3 } }
    } {
      {}{} \__luatag_unsupported:n {\tag_struct_gput:nnn{?}{#2}{#3}}
    }
  }

  \prg_set_conditional:Npnn \tag_if_tag_known:n #1 {TF, T, F} {
    \IfTagKnown {#1}
  }

  \cs_set:Npn \tag_struct_parent_int: {
    \AssignAndGetStructParent
  }

  \cs_set_protected:Npn \tag_struct_use_num:n #1 {
    \UseStructByNum \tex_numexpr:D #1 \scan_stop:
  }

  \cs_set:Npn \tag_struct_insert_annot:nn {
    \RecordObjectForStructParent
  }

  \cs_set_protected:Npn \tag_mc_reset_box:N {
    \ResetStructureAttributeToCurrentValueForBox
  }

  \prg_set_conditional:Npnn \tag_if_struct_exist:n #1 {TF, T, F} {
    \IfStructNumDefined #1 ~
  }

  \keys_define:nn { __tag / setup } {
    role/user-NS .code:n = {
      \pdf_version_compare:NnF < {2.0} {
        \SetUserNamespace {https://www.latex-project.org/ns/local/#1}
      }
    }
  }

  \hook_gput_code:nnn{begindocument}{luatagging}{
    \lua_now:n {
      luatexbase.remove_from_callback('pre_shaping_filter', 'tagpdf.rewrite-softhyphen')
      luatexbase.remove_from_callback('post_shaping_filter', 'tagpdf.rewrite-softhyphen')
    }
  }
  \hook_gset_rule:nnnn{begindocument}{luatagging}{voids}{tagpdf/mc}

  \hook_gput_code:nnn{begindocument/before}{luatagging}{
    \bool_lazy_and:nnT
      { \g__tag_active_struct_dest_bool }
      { \g__tag_active_struct_bool }
      {
        \tl_set:Nn \l_pdf_current_structure_destination_tl
          { luatagging_current_structure }
        \pdf_activate_structure_destination:
      }
  }
  \hook_gset_rule:nnnn{begindocument/before}{luatagging}{voids}{tagpdf}
  \__pdf_object_record:nN {luatagging_current_structure} \c_one_int
  \cs_set_protected:Npn \c__pdf_object_luatagging_current_structure_int {
    \GetCurrentStructureReference
  }
}

\hook_gput_code:nnn{package/latex-lab-testphase-marginpar/after}{luatagging}{
  \socket_new_plug:nnn { tagsupport/marginpar/begin }{ lua-latex-tags }
    {
      \if_mode_horizontal:
        \tag_mc_end:
        \tag_struct_begin:e{tag=\UseStructureName{marginnote},stash}%
        \AttachToMarginparParent
      \else:
        \tag_struct_begin:n{tag=\UseStructureName{marginnote}}%
      \fi:
    }

  \socket_assign_plug:nn { tagsupport/marginpar/begin } { lua-latex-tags }
}
