use experimental :rakuast;
use RakuDoc::Render;
use RakuDoc::PromiseStrings;
use MIME::Base64;

#| Prefer embedded binary for file: images; otherwise keep the original URI (http/https).
sub html-image-src(%prm --> Str) {
    if (%prm<contents> ~~ Blob:D) && %prm<contents>.elems {
        return 'data:' ~ (%prm<content-type> // 'application/octet-stream')
            ~ ';base64,' ~ MIME::Base64.encode(%prm<contents>, :oneline);
    }
    ~(%prm<uri> // '')
}

class HTML::Processor is RakuDoc::Processor {
    ## A set of methods to generate anchors / targets
    ## Output formats, eg. MarkDown and HTML, have different
    ## criteria. HTML rendering of Raku documentation has its
    ## own legacy algorithms
    ## So, different methods are used for
    ## - names (blocks) to be included in ToC, which needs to target the block
    ## - footnotes
    ## - indexed text, where Raku HTML has anchors that depend on context and meta
    ## - external links to other documents, which do not have to be unique

    multi method escape( Str:D $s ) {
        # will not double escape
        $s.trans(qw｢ &lt; &gt; &amp; & " > < ｣ => qw｢ &lt; &gt; &amp; &amp; &quot; &gt; &lt;｣)
    }
    #| Stringify if not string
    multi method escape( $s ) { self.escape( $s ?? $s.Str !! '' ) }
    #| mangle an id to make sure it will be a valid id in the output
    method mangle( $s ) {
        self.escape( $s ).subst(/ \s /, '_', :g)
    }
    #| name-id takes an ast
    #| returns a unique Str to be used as an anchor / target
    #| Used by any name (block) that is placed in the ToC
    #| Also used for the main anchor in the text for a footnote
    #| Not called if an :id is specified in the source
    #| This method should be sub-classed by Renderers for different outputs
    #| renderers can use method is-target-unique to test for uniqueness
    method name-id($ast --> Str) {
        my $target = self.mangle($ast.Str.trim);
        return self.register-target($target) if $.is-target-unique($target);
        $target ~= '_0';
        ++$target until $.is-target-unique($target);
        self.register-target($target);
    }

    #| Like name-id, index-id returns a unique Str to be used as a target
    #| Target should be unique
    #| Should be sub-classed by Renderers
    method index-id(:$contents) {
        my $target = 'index-entry-' ~ self.mangle($contents.Str.trim);
        return self.register-target($target) if $.is-target-unique($target);
        my @rejects = $target, ;
        # if plain target is rejected, then start adding a suffix
        $target ~= '_0';
        $target += 1 while $target ~~ any(@rejects);
        self.register-target($target);
    }

    #| Like name-id, local-heading returns a Str to be used as a target
    #| A local-heading is assumed to exist because specified by document author
    #| Should be sub-classed by Renderers
    method local-heading($ast) {
        self.mangle($ast.Str.trim)
    }
}

class RakuDoc::To::HTML {
    has HTML::Processor $.rdp .=new(:output-format<html>);

    submethod TWEAK {
        $!rdp.add-templates( self.html-templates, :source<RakuDoc::To::HTML> );
        my $css;
        if 'resources/css/vanilla.css'.IO ~~ :e & :f { # use local value if available
            $css = 'resources/css/vanilla.css'.IO.slurp;
        }
        else {
            $css = %?RESOURCES<css/vanilla.css>.slurp(:close)
        }
        $!rdp.add-data('css', $css);
        $!rdp.debug( %*ENV<RAKURENDEROPTS>.list ) if %*ENV<RAKURENDEROPTS>:exists
    }
    method render($ast) {
        my $fn = $*PROGRAM;
        my %source-data = %(
            name     => ~$fn,
            modified => $fn.modified,
            path     => $fn.dirname,
        );
        my $r2html = self.new;
        my $rdp := $r2html.rdp;
        if %*ENV<MORE_HTML>:exists {
            exit note( "｢{%*ENV<MORE_HTML>}｣ is not a file" ) unless %*ENV<MORE_HTML>.IO ~~ :e & :f;
            try {
                $rdp.add-templates( EVALFILE( %*ENV<MORE_HTML> ), :source<User-supplied-HTML> );
                CATCH {
                    default { exit note "Could not utilise ｢{%*ENV<MORE_HTML>}｣ " ~ .message }
                }
            }
        }
        if %*ENV<ALT_CSS>:exists {
            exit note( "｢{%*ENV<ALT_CSS>}｣ is not a file" ) unless %*ENV<ALT_CSS>.IO ~~ :e & :f;
            $rdp.add-data('css', %*ENV<ALT_CSS>.IO.slurp);
        }
        $rdp.render( $ast, :%source-data  )
    }

    # no post processing needed
    method postprocess( $final ) { $final };

    method html-templates {
        my constant BASIS-ON = '<span class="basis">';
        my constant BASIS-OFF = '</span>';
        my constant IMPORTANT-ON = '<span class="important">';
        my constant IMPORTANT-OFF = '</span>';
        my constant UNUSUAL-ON = '<span class="unusual">';
        my constant UNUSUAL-OFF = '</span>';
        my constant WEIGHTY-ON = '<span class="weighty">';
        my constant WEIGHTY-OFF = '</span>';
        my constant CODE-ON = '<span class="code">';
        my constant CODE-OFF = '</span>';
        my constant OVERSTRIKE-ON = '<span class="overstrike">';
        my constant OVERSTRIKE-OFF = '</span>';
        my constant HIGH-ON = '<span class="high">';
        my constant HIGH-OFF = '</span>';
        my constant JUNIOR-ON = '<span class="junior">';
        my constant JUNIOR-OFF = '</span>';
        my constant REPLACE-ON = '<span class="replace">';
        my constant REPLACE-OFF = '</span>';
        my constant KEYBOARD-ON = '<span class="keyboard">';
        my constant KEYBOARD-OFF = '</span>';
        my constant TERMINAL-ON = '<span class="terminal">';
        my constant TERMINAL-OFF = '</span>';
        my constant FOOTNOTE-ON = '<span class="footnote">';
        my constant FOOTNOTE-OFF = '</span>';
        my constant DEVEL-TEXT-ON = '<span class="developer-text">';
        my constant DEVEL-TEXT-OFF = '</span>';
        my constant DEVEL-VERSION-ON = '<span class="developer-version">';
        my constant DEVEL-VERSION-OFF = "</span>";
        my constant DEVEL-NOTE-ON = '<span class="developer-note">';
        my constant DEVEL-NOTE-OFF = "</span>";
        my constant DEFN-TEXT-ON = '<div class="defn-text">';
        my constant DEFN-TEXT-OFF = '</div>';
        my constant DEFN-TERM-ON = '<div class="defn-term">';
        my constant DEFN-TERM-OFF = '</div>';
        my constant BAD-MARK-ON = '<span class="bad-markdown">';
        my constant BAD-MARK-OFF = '</span>';
        my @bullets = <<\x2022 \x25b9 \x2023 \x2043 \x2219>> ;
        # we do not expect there to be recursively embedded spans, nor for spans to be broken accross strings
        my regex spantab { '<span' ~ '</span>'  .+ };
        %(
            #| special key to name template set
            _name => -> %, $ { 'markdown templates' },
            #| escape contents of code block, which may contain <span ...> & </span> tabs, not to be escaped
            escape-code => -> %prm, $tmpl {
                my $cont = (%prm<contents> // '').Str.trim-trailing;
                if $cont ~~ / <spantab> / {
                    ( $cont ~~ / ^ [ .*? <spantab> ]+ .*? $ / )
                    .chunks
                    .map({
                        .key eq '~' ??
                            $tmpl.globals.escape.( .value )
                            !! .value
                    })
                    .join
                }
                else { 
                $tmpl.globals.escape.( $cont ) }
                # else { 
                #     # do not avoid double escaping
                #     $cont.trans(qw｢  & " > < ｣ => qw｢ &amp; &quot; &gt; &lt;｣)
                # }
            },
            #| renders =code blocks
            code => -> %prm, $tmpl {
                my $del = %prm<delta> // '';
                my $caption = %prm<caption>;
                $caption = [~] %prm<numeration>.grep( *.so ).map( {
                    '<span class="enumeration-' ~ .field-type ~ '">' ~ $_ ~ '</span>'
                } ) if %prm<numeration>;
                PStr.new: qq:to/CODE/
                    { '<div class="delta">' ~ $del if $del }
                    <div class="raku-code">
                    <pre class="rakudoc">{ $tmpl<escape-code> }</pre>
                    <div class="code-caption">$caption\</div>
                    </div>
                    { '</div>' if $del }
                CODE
            },
            #| renders =input block
            input => -> %prm, $tmpl {
                %prm<html-tags> = True;
                my $del = %prm<delta> // '';
                my $caption = %prm<caption>;
                $caption = [~] %prm<numeration>.grep( *.so ).map( {
                    '<span class="enumeration-' ~ .field-type ~ '">' ~ $_ ~ '</span>'
                } ) if %prm<numeration>;
                PStr.new: qq:to/CODE/
                    { '<div class="delta">' ~ $del if $del }
                    <div class="raku-input">
                    <pre class="rakudoc">{ $tmpl<escape-code> }</pre>
                    <div class="input-caption">$caption\</div>
                    </div>
                    { '</div>' if $del }
                CODE
            },
            #| renders =output block
            output => -> %prm, $tmpl {
                %prm<html-tags> = True;
                my $del = %prm<delta> // '';
                my $caption = %prm<caption>;
                $caption = [~] %prm<numeration>.grep( *.so ).map( {
                    '<span class="enumeration-' ~ .field-type ~ '">' ~ $_ ~ '</span>'
                } ) if %prm<numeration>;
                PStr.new: qq:to/CODE/
                    { '<div class="delta">' ~ $del if $del }
                    <div class="raku-output">
                    <pre class="rakudoc">{ $tmpl<escape-code> }</pre>
                    <div class="output-caption">$caption\</div>
                    </div>
                    { '</div>' if $del }
                CODE
            },
            #| renders =comment block
            comment => -> %prm, $tmpl { '' },
            #| renders =formula block
            formula => -> %prm, $tmpl {
                # beware premature stringification!
                my $del = %prm<delta> // '';
                my $caption = %prm<caption>;
                $caption = [~] %prm<numeration>.grep( *.so ).map( {
                    '<span class="enumeration-' ~ .field-type ~ '">' ~ $_ ~ '</span>'
                } ) if %prm<numeration>;
                my $targ := %prm<target>;
                my $id-target = %prm<id>:exists &&
                        %prm<id>
                        ?? qq[[\n<div class="id-target" id="{ $tmpl.globals.escape.(%prm<id>) }"></div>]]
                        !! '';
                PStr.new: ('<div class="delta">' ~ $del if $del) ~
                    $id-target ~
                    qq[<div class="formula" id="{ $tmpl.globals.escape.($targ) }">] ~
                    ( %prm<alt> ?? %prm<alt> !! %prm<formula> ) ~
                    q[<div class="formula-caption">] ~
                    $caption ~
                    q[</div>
                    </div>] ~
                    ( '</div>' if $del )
            },
            #| renders =head block
            head => -> %prm, $tmpl {
                my $del = %prm<delta> // '';
                my $classes = ( %prm<classes> // "heading" ) ~ ( 'delta' if $del ) ;
                my $h = 'h' ~ (%prm<level> // '1');
                my $contents = %prm<contents>.Str.split(/ \< ~ \> <-[>]>+? /).join.trim;
                $contents = [~] %prm<numeration>.grep( *.so ).map( {
                    '<span class="enumeration-' ~ .field-type ~ '">' ~ $_ ~ '</span>'
                } ) if %prm<numeration>;
                my $targ = %prm<target> // '';
                my $esc-cap = $tmpl.globals.escape.( $contents );
                $esc-cap = '' if ($contents eq $targ or $esc-cap eq $targ);
                my $id-target = %prm<id>:exists && %prm<id>
                    ?? qq[[\n<div class="id-target" id="{ $tmpl.globals.escape.(%prm<id>) }"></div>]]
                    !! '';
                PStr.new:
                    $id-target ~
                    ( $esc-cap ?? qq[[\n<div class="id-target" id="$esc-cap"></div>]] !! '') ~
                    qq[[<$h id="$targ" class="$classes {'delta' if $del}">]] ~
                    ($del if $del) ~
                    ($contents ?? (
                    qq[[<a href="#" title="go to top of document">]] ~
                    $contents ~
                    qq[[</a><a class="raku-anchor" title="direct link" href="#{$esc-cap.so ?? $esc-cap !! $targ}">§\</a>]] ~
                    qq[[</$h>\n]]
                    ) !! '')
            },
            delta => -> %prm, $tmpl {
                DEVEL-VERSION-ON ~ %prm<versions> ~
                ( %prm<note> ??
                       DEVEL-NOTE-ON ~ %prm<note> ~ DEVEL-NOTE-OFF
                    !! ''
                ) ~
                DEVEL-VERSION-OFF ~
                "\n\n"
            },#| renders =defn block
            defn => -> %prm, $tmpl {
                my Bool $extended-defn = ?(%prm<extension>:exists and %prm<extension>);
                my PStr $rv .= new: DEFN-TERM-ON ~
                    ( %prm<numeration> ??
                        [~] %prm<numeration>.grep( *.so ).map( {
                        '<span class="enumeration-' ~ .field-type ~ '">' ~ $_ ~ '</span>'
                            } )
                    !! %prm<term> ) ~
                    DEFN-TERM-OFF ~ "\n" ~
                    DEFN-TEXT-ON;
                $rv ~= %prm<contents>;
                if $extended-defn {
                    for %prm<extension>.list -> $extra-p {
                        $rv ~= '<p class="extended-defn">';
                        $rv ~= $extra-p;
                        $rv ~= '</p>'
                    }
                }
                $rv ~= DEFN-TEXT-OFF ~ "\n\n";
                $rv
            },
            #| renders =numdefn block
            #| special template to render a defn list data structure
            defn-list => -> %prm, $tmpl { "\n" ~ [~] %prm<defn-list> },
            #| renders =item block
            item => -> %prm, $tmpl {
                my PStr $rv .= new;
                my Bool $extended-item = ?(%prm<extension>:exists and %prm<extension>);
                if %prm<numeration> {
                    # numeration is a sequence marked with field-type.
                    # Filter out D and put the rest in the bullet
                    my $bullet = '';
                    my $text = '';
                    %prm<numeration>.grep( *.so ).map( {
                        if .field-type eq 'D' { $text = $_ }
                        else { $bullet ~= $_ }
                    } );
                    $rv ~= qq| \<li class="numitem{ $extended-item ?? ' extended-item' !! '' }" data-bullet="$bullet" { ' id="' ~ $_ ~ '" ' with %prm<id>}>|;
                    $rv ~= $text;
                }
                else {
                    my $n = %prm<level> - 1;
                    $n = @bullets.elems - 1 if $n >= @bullets.elems;
                    my $bullet = %prm<bullet> // @bullets[$n];
                    $rv ~= qq[<li class="item{ $extended-item ?? ' extended-item' !! '' }" data-bullet="$bullet" style ="--level:$n;" { ' id="' ~ $_ ~ '" ' with %prm<id>} >];
                    $rv ~= %prm<contents>
                }
                if $extended-item {
                    for %prm<extension>.list -> $extra-p {
                        $rv ~= '<p class="extended-item">';
                        $rv ~= $extra-p;
                        $rv ~= '</p>'
                    }
                }
                $rv ~= "</li>\n";
                $rv
            },
            #| special template to render an item list data structure
            item-list => -> %prm, $tmpl {
                "\n<ul class=\"item-list\">" ~ ([~] %prm<item-list>) ~ "</ul>\n"
            },
            #| renders =nested block
            nested => -> %prm, $tmpl {
                PStr.new:
                    '<div class="nested"' ~
                    (%prm<target> ?? ' id="' ~ %prm<target> ~ '"' !! '') ~
                    '>' ~
                        ( %prm<numeration>
                        ?? [~] %prm<numeration>.grep( *.so ).map( {
                            '<span class="enumeration-' ~ .field-type ~ '">' ~ $_ ~ '</span>'
                        } )
                        !! %prm<contents> ) ~
                    "</div>\n"
            },
            #| renders =para block
            para => -> %prm, $tmpl {
                my $del = %prm<delta> // '';
                my $number = '';
                my PStr $text = %prm<contents>;
                my Bool $extended-para = ?(%prm<extension>:exists and %prm<extension>);
                %prm<numeration>.grep( *.so ).map( {
                    if .field-type eq 'D' { $text .= new: $_ }
                    else { $number ~= $_ }
                } );
                my PStr $rv .= new: ('<div class="delta">' ~ $del if $del) ~
                    "\n" ~
                    ( $extended-para ?? '<div class="extended-para"' !! '<p' ) ~
                    (' id="' ~ %prm<id> ~ '"' if %prm<id>) ~
                    (' class="' ~ %prm<in-type> ~ '"' if %prm<in-type>) ~
                    '>' ~
                    (qq[<span class="numpara">$number\</span>] if $number);
                $rv ~= $text;
                if $extended-para {
                    for %prm<extension>.list -> $extra-p {
                        $rv ~= '<p class="extended-para">';
                        $rv ~= $extra-p;
                        $rv ~= '</p>'
                    }
                }
                $rv ~= $extended-para ?? "</div>\n" !! "</p>\n";
                $rv ~= '</div>' if $del;
                $rv
            },
            #| renders =place block
            place => -> %prm, $tmpl {
                # Filter undefined head options (e.g. level) so the head template
                # does not warn about uninitialized values.
                my %head = %prm<id target delta numeration level>:p;
                my $rv = $tmpl('head', %(
                    :contents(%prm<caption> // ''),
                    |%head.grep({ .value.defined })
                ));
                given %prm<content-type> // '' {
                    when .contains('text') {
                        $rv ~= %prm<contents> // ''
                    }
                    when .contains('image') {
                        my $alt = '';
                        $alt = qq[ alt="$_" title="$_"] with %prm<alt>;
                        my $src = html-image-src(%prm);
                        my $caption-html = '';
                        %prm<caption> and ($caption-html = qq[<div>{ %prm<caption> }</div>]);
                        $rv ~= qq[<div class="rakudoc-image-placement">
                            <img src="$src"$alt>$caption-html\</div> ]
                    }
                    default {
                        $rv ~= qq[<div class="rakudoc-placement-error"><p>Placement of {%prm<content-type> // 'unknown'} is not yet implemented or requires internet connection</p></div> ]
                    }
                }
                $rv ~= "\n\n";
            },
            #| renders =rakudoc block
            rakudoc => -> %prm, $tmpl { %prm<contents> ~ "\n" }, #pass through without change
            #| renders =section block
            section => -> %prm, $tmpl {
                qq[<div class="rakudoc-section { 'delta' if %prm<delta>}">] ~
                %prm<delta> ~
                ( %prm<numeration>
                    ?? [~] %prm<numeration>.grep( *.so ).map( {
                        '<span class="enumeration-' ~ .field-type ~ '">' ~ $_ ~ '</span>'
                        } )
                    !! %prm<contents>
                ) ~
                "\n</div>\n"
            },
            #| renders =SEMANTIC block, if not otherwise given
            semantic => -> %prm, $tmpl {
                my $head = $tmpl('head', %(
                    :contents( %prm<caption> ),
                    |(%prm<id target delta numeration level>:p)));
                ( $head unless %prm<hidden> ) ~
                %prm<contents> ~ "\n\n"
            },
            #| renders =pod block
            pod => -> %prm, $tmpl { %prm<contents> },
            #| renders =table block
            table => -> %prm, $tmpl {
                my $classes = ( %prm<classes> // "table" );
                my $del = %prm<delta> // '';
                my $align-table = %prm<alignment>:exists && %prm<alignment> ?? ' align-' ~ %prm<alignment> !! '';
                my $caption =
                    %prm<numeration>
                    ?? [~] %prm<numeration>.grep( *.so ).map( {
                        '<span class="enumeration-' ~ .field-type ~ '">' ~ $_ ~ '</span>'
                    } )
                    !! %prm<caption> ;
                my $rv = PStr.new: ('<div class="delta">' ~ $del if $del);
                $rv ~= qq[<div id="{%prm<target> // 'default-table'}" class="rakudoc-table$align-table">\n];
                $rv ~= '<table class="' ~ $classes ~ '">';
                if %prm<procedural> {
                    $rv ~= '<tbody class="procedural">';
                    for %prm<grid>.list -> @row {
                        $rv ~= "\n<tr class=\"procedural\">";
                        for @row -> $cell {
                            next if $cell<no-cell>;
                            my $content;
                            $content ~= ' colspan="' ~ $cell<span>[0] ~'"' if $cell<span>:exists and $cell<span>[0] != 1;
                            $content ~= ' rowspan="' ~ $cell<span>[1] ~'"' if $cell<span>:exists and $cell<span>[1] != 1;
                            $content ~= ' class="';
                            with $cell<align> { for .list {
                                $content ~= "procedural-cell-$_ "
                            } }
                            $content ~= 'procedural-cell-label' if $cell<label>;
                            with $cell<data> { $content ~= '">' ~ $cell<data> }
                            else { $content ~= '">' }
                            if $cell<header> {
                                $rv ~= "<th$content\</th>"
                            }
                            else {
                                $rv ~= "<td$content\</td>"
                            }
                        }
                        $rv ~= "</tr>"
                    }
                    $rv ~= "\n</tbody>"
                }
                else {
                    $rv ~= "\t<thead>\n\t\t<tr><th>" ~ $_>>.trim.join( '</th><th>') ~ "</th></tr>\n\t</thead>"
                        with %prm<headers>.head;
                    $rv ~= "\t<tbody>\n\t\t";
                    $rv ~= [~] %prm<rows>.map({ '<tr><td>' ~ .map(*.trim).join('</td><td>') ~ "</td></tr>\n\t\t" });
                    $rv ~= "\t</tbody>\n";
                }
                $rv ~= "</table>\n";
                $rv ~= qq[<div class="table-caption rakudoc">$caption\</div>] if $caption;
                $rv ~= '</div>';
                $rv ~= '</div>' if $del;
                $rv
            },
            #| renders any unknown block minimally
            unknown => -> %prm, $tmpl {
                my $contents = qq[UNKNOWN { %prm<type> }];
                my $head = $tmpl('head', %( :$contents, |(%prm<id level target caption delta>:p)) );
                PStr.new: $head ~ $tmpl.globals.escape.(%prm<contents>)
                        .subst(/ \h\h /, '&nbsp;&nbsp;', :g)
                        .subst(/ \v /, '<br>', :g) ~
                         "\n\n"
            },
            #| Version numbers should appear on the same line as the heading
            VERSION => -> %prm, $tmpl {
                my $contents = ( %prm<caption> || 'VERSION' ).Str ~ '&nbsp' x 4 ~ %prm<contents>.Str;
                my $head = $tmpl('head', %( :$contents, |(%prm< level id target delta numeration >:p) ) );
                if %prm<hidden> { qq| <div class="rakudoc-version">$contents\</div> | }
                else { $head }
            },
            #| renders a single item in the toc
            toc-item => -> %prm, $tmpl {
                my $n = %prm<toc-entry><level> > 3 ?? 3 !! (%prm<toc-entry><level> - 1);
                my $pref = qq[<div class="toc-item" style="--level:$n;" data-bullet="{ $n ?? @bullets[$n] !! '' }">];
                PStr.new: qq[$pref\<a href="#{ %prm<toc-entry><target> }">{%prm<toc-entry><caption>.split(/ \< ~ \> <-[>]>+? /).join}</a></div>\n]
            },
            #| special template to render the toc list
            toc => -> %prm, $tmpl {
                if %prm<toc-list>:exists && %prm<toc-list>.elems {
                    PStr.new: qq[<div class="toc">\n] ~
                    ( "<h2 class=\"toc-caption\">%prm<caption>\</h2>" if  %prm<caption> ) ~
                    ([~] %prm<toc-list>) ~
                    "</div>\n"
                }
                else {
                    PStr.new: ''
                }
            },
            #| special template to render the citationns list
            citations => -> %prm, $tmpl {
                my $rv = PStr.new: qq[<div class="citations">\n];
                if %prm<citation-items>:exists && %prm<citation-items>.elems {
                    $rv ~= "\n<ul class=\"citations-list\">\n" ~
                        ([~] %prm<citation-items>) ~ "\n</ul>\n"
                }
                else {
                    $rv ~=  %prm<contents>
                }
                $rv ~= "</div>\n";
                $rv
            },
            #| renders a single item in the index
            index-item => -> %prm, $tmpl {
            # expecting a level, and entry name, whether its in a heading, and
            # a list (possibly empty) of hashes with information for link(s)
            # by the time of rendering an index list, all PStr/PCell should have been expanded
                my $n = %prm<level>;
                my $rv =  qq[<div class="index-section" data-index-level="$n" style="--level:$n">\n] ~
                        '<span class="index-entry">' ~ %prm<entry> ~ '</span>';
                %prm<refs>.list
#                    .grep( .<is-in-heading>.not ) # do not render if in heading
                    .map({
                        $rv ~= qq[<a class="index-ref" href="#{ .<target> }">{
                            $tmpl('escape-code', %( :contents( .<place> ) ))
                            }</a>]
                    });
                $rv ~= "\n</div>\n";
            },
            #| special template to render the index data structure
            index => -> %prm, $tmpl {
                my @inds = %prm<index-list>.grep({ .isa(Str) || .isa(PStr) });
                if @inds.elems {
                    PStr.new: '<div class="index">' ~ "\n" ~
                    ( "<h2 class=\"index-caption\">%prm<caption>\</h2>" if  %prm<caption> ) ~
                    ([~] @inds ) ~ "\n</div>\n"
                }
                else { 'No indexed items' }
            },
            #| special template to render the footnotes data structure
            footnotes => -> %prm, $tmpl {
                if %prm<footnotes>.elems {
                    PStr.new: qq:to/FOOTNOTES/
                        <div class="footnotes">
                        <h2 class="footnote-caption">Footnotes</h2>
                        { [~] %prm<footnotes>.map({
                            PStr.new:
                                '<div class="footnote">' ~ .<fnNumber> ~
                                '<a class="footnote-anchor" id="' ~ .<fnTarget> ~
                                '" href="#' ~ .<retTarget> ~
                                '"> |^| </a>' ~
                                 ~ .<contents>.Str ~
                                 ~ '</div>'
                        }) }
                        </div>
                    FOOTNOTES
                }
                else { '' }
            },
            #| special template to render the warnings data structure
            warnings => -> %prm, $tmpl {
                if %prm<warnings>.elems {
                    qq:to/WARNINGS/
                        <div class="warnings">
                        <h2 class="warnings-caption">Warnings</h2>
                            <ol>
                            { [~] %prm<warnings>.map({
                                '<li>' ~ $tmpl.globals.escape.( $_ ).subst( / \v /,'<br>',:g ) ~ "</li>\n"
                                })
                            }
                            </ol>
                        </div>
                    WARNINGS
                }
                else { '' }
            },
            ## Markup codes with only display (format codes), no meta data allowed
            ## meta data via Config is allowed
            #| B< DISPLAY-TEXT >
            #| Basis/focus of sentence (typically rendered bold)
            markup-B => -> %prm, $ {
                BASIS-ON ~ %prm<contents> ~ BASIS-OFF
            },
            #| C< DISPLAY-TEXT >
            #| Code (typically rendered fixed-width)
            markup-C => -> %prm, $tmpl { CODE-ON ~ $tmpl<escape-code> ~ CODE-OFF },
            #| H< DISPLAY-TEXT >
            #| High text (typically rendered superscript)
            markup-H => -> %prm, $tmpl { HIGH-ON ~ %prm<contents> ~ HIGH-OFF },
            #| I< DISPLAY-TEXT >
            #| Important (typically rendered in italics)
            markup-I => -> %prm, $tmpl { IMPORTANT-ON ~ %prm<contents> ~ IMPORTANT-OFF },
            #| J< DISPLAY-TEXT >
            #| Junior text (typically rendered subscript)
            markup-J => -> %prm, $tmpl { JUNIOR-ON ~ %prm<contents> ~ JUNIOR-OFF },
            #| K< DISPLAY-TEXT >
            #| Keyboard input (typically rendered fixed-width)
            markup-K => -> %prm, $tmpl { KEYBOARD-ON ~ %prm<contents> ~ KEYBOARD-OFF },
            #| N< DISPLAY-TEXT >
            #| Note (text not rendered inline, but visible in some way: footnote, sidenote, pop-up, etc.))
            markup-N => -> %prm, $tmpl { 
                PStr.new:
                '<a class="footnote-anchor" id="' ~
                    %prm<retTarget> ~ '" href="#' ~ %prm<fnTarget> ~
                    '" title="' ~ %prm<contents> ~
                    '">' ~
                    FOOTNOTE-ON ~ '[ ' ~ %prm<fnNumber> ~ ' ]' ~ FOOTNOTE-OFF ~
                    '</a>'
            },
            #| O< DISPLAY-TEXT >
            #| Overstrike or strikethrough
            markup-O => -> %prm, $tmpl { OVERSTRIKE-ON ~ %prm<contents> ~ OVERSTRIKE-OFF },
            #| R< DISPLAY-TEXT >
            #| Replaceable component or metasyntax
            markup-R => -> %prm, $tmpl { REPLACE-ON ~ %prm<contents> ~ REPLACE-OFF },
            #| S< DISPLAY-TEXT >
            #| Space characters to be preserved
            markup-S => -> %prm, $tmpl {
                $tmpl.globals.escape.(%prm<contents>)
                    .subst(/ \h\h /, '&nbsp;&nbsp;', :g)
                    .subst(/ \v /, '<br>', :g)
            },
            #| T< DISPLAY-TEXT >
            #| Terminal output (typically rendered fixed-width)
            markup-T => -> %prm, $tmpl { TERMINAL-ON ~ %prm<contents> ~ TERMINAL-OFF },
            #| U< DISPLAY-TEXT >
            #| Unusual (typically rendered with underlining)
            markup-U => -> %prm, $tmpl { UNUSUAL-ON ~ %prm<contents> ~ UNUSUAL-OFF },
            #| V< DISPLAY-TEXT >
            #| Verbatim (internal markup instructions ignored)
            markup-V => -> %prm, $tmpl {
                $tmpl<escape-code>
                    .subst(/ \h\h /, '&nbsp;&nbsp;', :g)
                    .subst(/ \v /, '<br>', :g)
            },
            #| W< DISPLAY-TEXT >
            #| Weighty (typically rendered with small caps)
            markup-W => -> %prm, $tmpl { WEIGHTY-ON ~ %prm<contents> ~ WEIGHTY-OFF },
            #| Q< CITATION LIST >
            #| Make into a superscript
            markup-Q => -> %prm, $tmpl { HIGH-ON ~ %prm<contents> ~ HIGH-OFF },

            ##| Markup codes, optional display and meta data

            #| A< DISPLAY-TEXT |  METADATA = ALIAS-NAME >
            #| Alias to be replaced by contents of specified V<=alias> directive
            markup-A => -> %prm, $tmpl {
                my $c = %prm<contents>;
                $c ~~ Positional
                    ??
                    [~] $c.grep( *.so ).map( {
                        '<span class="enumeration-' ~ .field-type ~ '">' ~ $_ ~ '</span>'
                    } )
                    !! $c
            },
            #| E< DISPLAY-TEXT |  METADATA = HTML/UNICODE-ENTITIES >
            #| Entity (HTML or Unicode) description ( E<entity1;entity2; multi,glyph;...> )
            markup-E => -> %prm, $tmpl { %prm<contents> },
            #| F< DISPLAY-TEXT |  METADATA = LATEX-FORM >
            #| Formula inline content ( F<ALT|LaTex notation> )
            markup-F => -> %prm, $tmpl { CODE-ON ~ ( %prm<alt> ?? %prm<alt> !! %prm<formula> ) ~ CODE-OFF },
            #| L< DISPLAY-TEXT |  METADATA = TARGET-URI >
            #| Link ( L<display text|destination URI> )
            markup-L => -> %prm, $tmpl {
                my $target = %prm<target>.trim.subst(/ '.*' /, ".%prm<output-format>", :g);
                my $text = %prm<link-label>;
                given %prm<type> {
                    when 'local' {
                        if %prm<extra> {
                            qq[<a href="{$target}#%prm<extra>">$text\</a>]
                        }
                        else {
                            qq[<a href="{$target}">$text\</a>]
                        }
                    }
                    when 'internal' {
                        qq[<a href="#{$target}">$text\</a>]
                    }
                    default {
                        qq[<a href="$target">$text\</a>]
                    }
                }
            },
            #| P< DISPLAY-TEXT |  METADATA = REPLACEMENT-URI >
            #| Placement link
            markup-P => -> %prm, $tmpl {
                given %prm<schema> {
                    when 'defn' {
                        "\n\n&#x2997;  " ~
                        DEFN-TEXT-ON ~ %prm<defn-expansion> ~ DEFN-TEXT-OFF ~
                        "\n&#x2998;"
                    }
                    default {
                        given %prm<content-type> // '' {
                            when .contains('text') {
                                %prm<contents> // ''
                            }
                            when .contains('image') {
                                my $alt = '';
                                $alt = qq[ alt="$_" title="$_"] with (%prm<alt> // %prm<fallback>);
                                my $src = html-image-src(%prm);
                                qq[<img src="$src"$alt>]
                            }
                            default {
                                qq[<p>Placement of {%prm<content-type> // 'unknown'} is not yet implemented or requires internet connection</p> ]
                            }
                        }
                    }
                }
            },

            ##| Markup codes, mandatory display and meta data
            #| D< DISPLAY-TEXT |  METADATA = SYNONYMS >
            #| Definition inline ( D<term being defined|synonym1; synonym2> )
            markup-D => -> %prm, $tmpl {  BASIS-ON ~ %prm<contents> ~ BASIS-OFF },
            #| Δ< DISPLAY-TEXT |  METADATA = VERSION-ETC >
            #| Delta note ( Δ<visible text|version; Notification text> )
            markup-Δ => -> %prm, $tmpl {
                DEVEL-TEXT-ON ~ %prm<contents> ~
                DEVEL-VERSION-ON ~ %prm<versions> ~
                (%prm<note> ?? DEVEL-NOTE-ON ~ %prm<note> ~ DEVEL-NOTE-OFF !! '') ~
                DEVEL-VERSION-OFF ~ DEVEL-TEXT-OFF
            },
            #| M< DISPLAY-TEXT |  METADATA = WHATEVER >
            #| Markup extra ( M<display text|functionality;param,sub-type;...>)
            markup-M => -> %prm, $tmpl { CODE-ON ~ %prm<contents> ~ CODE-OFF },
            #| X< DISPLAY-TEXT |  METADATA = INDEX-ENTRY >
            #| Index entry ( X<display text|entry,subentry;...>)
            markup-X => -> %prm, $tmpl {
                my $contents = (%prm<contents> // '');
                if %prm<is-in-heading> {
#                    my $indexedheader = %prm<meta>.elems ?? %prm<meta>[0].join(';') !! $contents;
#                    '<span class="indexed-header" id="' ~ %prm<target> ~ '"' ~
#                    ( $indexedheader ?? (' data-indexedheader="' ~ $indexedheader ~ '">') !! '>' ) ~
                    $contents
#                    '</span>'
                }
                else {
                    my $index-text;
                    $index-text = %prm<meta>.Str
                        if %prm<meta>;
                    '<span class="indexed" id="' ~ %prm<target> ~ '"' ~
                    ($index-text && $contents ?? (' data-index-text="' ~ $index-text ~ '">') !! '>') ~
                    $contents ~
                    '</span>'
                }
            },
            #| Unknown markup, render minimally
            markup-bad => -> %prm, $tmpl { BAD-MARK-ON ~ $tmpl.globals.escape.(%prm<contents>) ~ BAD-MARK-OFF },
            #| special template to encapsulate all the output to save to a file
            #| These sub-templates should allow sub-classes of RakuDoc::To::HTML
            #| to provide replacement templates on a more granular basis
            final => -> %prm, $tmpl {
                qq:to/PAGE/
                <!DOCTYPE html>
                <html { $tmpl<html-root> } class="rakudoc-singlepage">
                    <head>
                    <meta charset="UTF-8" />
                    { $tmpl<head-block> }
                    </head>
                    <body>
                    { $tmpl<top-of-page> }
                    { $tmpl<main-content> }
                    { $tmpl<footer> }
                </body>
                </html>
                PAGE
            },
            ## sections of the final document
            #| root section, what is placed in the html tab
            html-root => -> %prm, $tmpl {
                qq[lang="{%prm<document-options><lang>}"]
            },
            #| head-block, what goes in the head tab
            head-block => -> %prm, $tmpl {
                qq:to/HEAD/
                    <title>{%prm<title>}</title>
                    {$tmpl.globals.data<css>:exists ??
                       '<style>' ~ $tmpl.globals.data<css> ~ '</style>'
                    !! ''
                    }
                HEAD
            },
            #| the first section of body, including navigation
            top-of-page => -> %prm, $tmpl {
                my $rv = '';
                if %prm<title-target>:exists and %prm<title-target> ne '' {
                    $rv ~= qq[<div id="{
                        $tmpl.globals.escape.( %prm<title-target> )
                    }"></div>]
                }
                $rv ~= '<h1 class="title">' ~ %prm<title> ~ "</h1>\n\n" ~
                (%prm<subtitle> ?? ( "\t" ~ %prm<subtitle> ~ "\n\n" ) !! '') ~
                ( %prm<rendered-toc> if %prm<rendered-toc> && %prm<document-options><auto-toc> )
            },
            #| the main section of body
            main-content => -> %prm, $tmpl {
                %prm<body>.Str ~
                %prm<footnotes>.Str ~ "\n" ~
                ( %prm<rendered-index> if %prm<rendered-index> && %prm<document-options><auto-index>)~
                ( %prm<rendered-citations> if %prm<rendered-citations> && %prm<document-options><auto-citations>)
            },
            #| the last section of body
            footer => -> %prm, $tmpl {
                qq:to/FOOTER/;
                \n<div class="footer">
                    Rendered from<span class="footer-field">&nbsp;{%prm<source-data><path>}/{%prm<source-data><name>}</span>
                <span class="footer-field">{sprintf( "&nbsp;at %02d:%02d UTC on %s", .hour, .minute, .yyyy-mm-dd) with %prm<modified>.DateTime }</span>
                <span class="footer-line">Source last modified {(sprintf( "at %02d:%02d UTC on %s", .hour, .minute, .yyyy-mm-dd) with %prm<source-data><modified>.DateTime)}</span>
                { qq[<div class="warnings">{ %prm<warnings> }\</div>] if %prm<warnings> }
                </div>
                FOOTER
            },
        ); # END OF TEMPLATES (this comment is to simplify documentation generation)
    }
}
