# vim: filetype=perl:softtabstop=4:tabstop=4

# This file is part of LilyPond, the GNU music typesetter.
#
# Copyright (C) 2023--2026 Jonas Hahnfeld <hahnjo@hahnjo.de>
#
# LilyPond is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# LilyPond is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.

# This file contains LilyPond's customization for the HTML output by texi2any.
# While rewritten from scratch, parts of this file are based on the previous
# lilypond-texi2html.init originally written by Reinhold Kainhofer and modified
# by many others over the years.

package Texinfo::Config;

use strict;

use Encode qw(decode);

# Function to get translated functions. See comment in lilypond-lang.init for
# a better setup.
our $LILYPOND_DOC;
defined($LILYPOND_DOC) or die('did you load lilypond-lang.init');
sub lilypond_gdt {
    my ($language, $msgid) = @_;

    if ($language ne 'en' and defined($LILYPOND_DOC->{$language}->{$msgid})) {
        return $LILYPOND_DOC->{$language}->{$msgid};
    } else {
        return $msgid;
    }
}

our $texinfo_version = $Texinfo::Common::VERSION;
if ($texinfo_version lt '7.0') {
    # Create an alias with the function name used since Texinfo 7.0, to simplify
    # the compatibility code.
    *texinfo_set_from_init_file = \&set_from_init_file;
}

my $BODY_ELEMENT_ATTRIBUTES_name;
if ($texinfo_version lt '7.1.90') {
    *convert_to_identifier = \&Texinfo::Convert::NodeNameNormalization::normalize_node;
    $BODY_ELEMENT_ATTRIBUTES_name = 'BODYTEXT';
} else {
    *convert_to_identifier = \&Texinfo::Convert::NodeNameNormalization::convert_to_identifier;
    $BODY_ELEMENT_ATTRIBUTES_name = 'BODY_ELEMENT_ATTRIBUTES';
}

# Compatibility function for links to Top node and navigation buttons.
sub lilypond_from_element_direction {
    my ($converter, $direction, $type) = @_;
    if ($texinfo_version lt '7.0') {
        my $element = $converter->{'current_element'};
        return $converter->_element_direction($element, $direction, $type);
    } else {
        return $converter->from_element_direction($direction, $type);
    }
}

# The manual name, i.e. learning, notation, web, etc.
our $manual_name;

### Set customization variables.
# Output the Table of Contents into a separate file. We will output the tocframe
# ourselves in lilypond_format_end_file.
texinfo_set_from_init_file('CONTENTS_OUTPUT_LOCATION', 'separate_element');

# Older Texinfo versions don't emit auto-generated @menu blocks.  It is
# necessary to explicitly enable this feature.
if ($texinfo_version lt '7.0') {
    texinfo_set_from_init_file('TREE_TRANSFORMATIONS',
                               'complete_tree_nodes_menus');
}

# This gives us Texinfo menus as defined in the source.
texinfo_set_from_init_file('FORMAT_MENU', 'menu');

# We always want both header and footer navigation panels in split HTML
# output.
texinfo_set_from_init_file('WORDS_IN_PAGE', 0);

# Remove colon after index (before Texinfo 7.1) and menu entries (Texinfo 6.8
# and later).
texinfo_set_from_init_file('INDEX_ENTRY_COLON', '');
texinfo_set_from_init_file('MENU_ENTRY_COLON', '');

# Use (translated) section names in the indices, the menus, and in the <title>
# for split output.
texinfo_set_from_init_file('USE_NODES', '0');
texinfo_set_from_init_file('NODE_NAME_IN_INDEX', '0');
texinfo_set_from_init_file('NODE_NAME_IN_MENU', '0');
texinfo_set_from_init_file('SECTION_NAME_IN_TITLE', '1');

# Hide the title heading, which is the default since Texinfo 7.0.
if ($texinfo_version lt '7.0') {
    texinfo_set_from_init_file('SHOW_TITLE', '0');
}


### Register init handlers.
sub lilypond_handler_structure {
    my $converter = shift;

    if ($texinfo_version lt '7.0') {
        $manual_name = $converter->{'document_name'};
    } else {
        $manual_name = $converter->get_info('document_name');
    }
    $manual_name =~ s/([^-]*)-.*/$1/;

    # Add <link>s to the CSS files, depending on the document name.
    my @css_refs = (
        'css/lilypond-manuals.css',
        'css/lilypond-highlighting.css',
    );
    if ($manual_name eq 'web') {
        @css_refs = (
            'css/lilypond-website.css',
        );
    }
    # Use force_conf to override previous CSS_REFS set via the command line
    # (which we don't do).
    $converter->force_conf('CSS_REFS', \@css_refs);

    if ($texinfo_version lt '7.0') {
        return 1;
    } else {
        return 0;
    }
}
sub lilypond_handler_init {
    my $converter = shift;

    # Make current manual name available as class in the <body> tag for CSS.
    my $bodytext = $converter->get_conf($BODY_ELEMENT_ATTRIBUTES_name);
    $bodytext .= " class=\"$manual_name\"";
    $converter->set_conf($BODY_ELEMENT_ATTRIBUTES_name, $bodytext);

    if ($texinfo_version lt '7.0') {
        return 1;
    } else {
        return 0;
    }
}
texinfo_register_handler('structure', \&lilypond_handler_structure);
texinfo_register_handler('init', \&lilypond_handler_init);


### Lower-case the node file names and the label target names.
sub lilypond_node_file_name {
    my ($converter, $element, $file_name) = @_;
    return lc($file_name);
}

sub lilypond_label_target_name {
    my ($converter, $normalized, $node_contents, $default_target) = @_;
    if (!defined($default_target)) {
        # In Texinfo 7.0, label_target_name received only three arguments.
        $default_target = $node_contents;
    }
    if (!defined($default_target)) {
        # Before Texinfo 7.0, label_target_name received only two arguments.
        $default_target = $normalized;
    }
    return lc($default_target);
}
if ($texinfo_version lt '7.0') {
    use vars qw($node_file_name $node_target_name);
    $node_file_name = \&lilypond_node_file_name;
    $node_target_name = \&lilypond_label_target_name;
} else {
    texinfo_register_file_id_setting_function('node_file_name',
                                              \&lilypond_node_file_name);
    texinfo_register_file_id_setting_function('label_target_name',
                                              \&lilypond_label_target_name);
}

sub lilypond_redirection_file_names {
    my ($converter, $target_element, $filename, $node_redirection_filename,
        $reference_redirection_files) = @_;
    # NOTE the extension is lower-cased too
    my $added_redirection_file = lc($node_redirection_filename);
    return @$reference_redirection_files
      if ($added_redirection_file eq $filename);
    foreach my $redirection_file (@$reference_redirection_files) {
        if ($added_redirection_file eq $redirection_file) {
            return @$reference_redirection_files;
        }
    }
    return @$reference_redirection_files, $added_redirection_file;
}

if ($texinfo_version ge '7.2.92') {
    texinfo_register_file_id_setting_function('redirection_file_names',
                                              \&lilypond_redirection_file_names);
}


### Format references, either internal or external.
sub lilypond_format_ref {
    my ($converter, $command_name, $element, $args, $content) = @_;

    my $name;
    if ($args->[2] and defined($args->[2]->{'normal'}) and
        $args->[2]->{'normal'} ne '') {
        $name = $args->[2]->{'normal'};
    } elsif ($args->[1] and defined($args->[1]->{'normal'}) and
             $args->[1]->{'normal'} ne '') {
        $name = $args->[1]->{'normal'};
    }

    my $file = '';
    if ($args->[3]) {
        if ($texinfo_version lt '7.1') {
            if (defined($args->[3]->{'monospacetext'}) and
                $args->[3]->{'monospacetext'} ne '') {
                $file = $args->[3]->{'monospacetext'};
            }
        } else {
            if (defined($args->[3]->{'filenametext'}) and
                $args->[3]->{'filenametext'} ne '') {
                $file = $args->[3]->{'filenametext'};
            }
        }
    }

    my $book = '';
    if ($args->[4] and defined($args->[4]->{'normal'})) {
        $book = $args->[4]->{'normal'}
    }

    # Delegate internal references to the default conversion.
    if ($file eq '') {
        if ($texinfo_version lt '7.0') {
            return &{$converter->default_commands_conversion($command_name)}(
                $converter, $command_name, $element, $args, $content);
        } else {
            return &{$converter->default_command_conversion($command_name)}(
                $converter, $command_name, $element, $args, $content);
        }
    }

    # Expect that we only use @ref for external references.
    if ($command_name ne 'ref') {
        my $msg = "used '$command_name' to link to external manual '$file'";
        if ($texinfo_version lt '7.2.90') {
            $converter->line_error($converter, $msg, $element->{'source_info'});
        } else {
            $converter->converter_line_error($msg, $element->{'source_info'});
        }
    }

    my $node_content;
    my $node_file;
    if ($texinfo_version lt '7.1') {
        if ($element->{'extra'} and $element->{'extra'}->{'node_argument'} and
            $element->{'extra'}->{'node_argument'}->{'node_content'}) {
            $node_content = {'contents'
                   => $element->{'extra'}->{'node_argument'}->{'node_content'}};
        }
    } else {
        my $node_arg;
        if ($texinfo_version lt '7.2.90') {
            $node_arg = $element->{'args'}->[0];
        } else {
            $node_arg = $element->{'contents'}->[0];
        }
        if ($node_arg->{'extra'} and $node_arg->{'extra'}->{'node_content'}) {
            $node_content = $node_arg->{'extra'}->{'node_content'};
            if ($texinfo_version lt '7.1.90') {
                $node_content = {'contents' => $node_content};
            }
        }
    }

    if (defined($node_content)) {
        $node_file = convert_to_identifier($node_content);
        $node_file = lc($node_file);

        if (!defined($name)) {
            my $node_name;
            if ($texinfo_version lt '7.2.90') {
                my $node_tree = {'type' => '_code', 'contents' => [$node_content]};
                $node_name = $converter->convert_tree($node_tree, 'node in ref');
            } else {
                $node_name = $converter->convert_tree_in_code_context($node_content, 'node in ref');
            }
            if (defined($node_name) and $node_name ne 'Top') {
                $name = $node_name;
            }
        }
    } else {
        my $msg = "could not determine node for link to external manual '$file'";
        if ($texinfo_version lt '7.2.90') {
            $converter->line_error($converter, $msg, $element->{'source_info'});
        } else {
            $converter->converter_line_error($msg, $element->{'source_info'});
        }
    }

    if (!defined($name)) {
        if ($book eq '') {
            my $msg = "could not determine name for link to external manual '$file'";
            if ($texinfo_version lt '7.2.90') {
                $converter->line_error($converter, $msg, $element->{'source_info'});
            } else {
                $converter->converter_line_error($msg, $element->{'source_info'});
            }
        }
        $name = $book;
    }

    my $href;
    my $only_web_version = $ENV{ONLY_WEB_VERSION};
    if ($manual_name eq 'web' and defined($only_web_version)) {
        if (!$converter->get_conf('SPLIT')) {
            my $msg = "unexpected website build without splitting";
            if ($texinfo_version lt '7.2.90') {
                $converter->line_error($converter, $msg, $element->{'source_info'});
            } else {
                $converter->converter_line_error($msg, $element->{'source_info'});
            }
        }
        $href = "../../doc/$only_web_version/Documentation/$file/$node_file.html";
    } elsif ($converter->get_conf('SPLIT')) {
        if ($node_file eq 'top') {
            $node_file = 'index';
        }
        $href = "../$file/$node_file.html";
    } else {
        # In this case, $file already has the big-page postfix.
        my $target = $node_file;
        $target =~ s/^([0-9_])/g_t$1/;
        $href = "$file.html#$target";
    }

    return "<a href=\"$href\">$name</a>";
}

our $ref_commands;
if ($texinfo_version lt '7.0') {
    $ref_commands = \%Texinfo::Common::ref_commands;
} else {
    $ref_commands = \%Texinfo::Commands::ref_commands;
}
foreach my $command (keys(%$ref_commands)) {
    texinfo_register_command_formatting($command, \&lilypond_format_ref);
}


### Print out a Table of Contents on every page.

# We could register the function format_begin_file, but since we only statically
# need to open one <div>, it is easier with AFTER_BODY_OPEN. We keep the setting
# here for symmetry with format_end_file.
texinfo_set_from_init_file('AFTER_BODY_OPEN', "<div id=\"main\">\n");

sub lilypond_format_tocheader {
    my $converter = shift;
    if ($manual_name eq 'web') {
        return '';
    }

    my $lang = $converter->get_conf('documentlanguage');
    # Construct a link back to the documentation index and a header.
    my $lang_ext = '';
    if ($lang and $lang ne 'en') {
        $lang_ext = $lang . '.';
    }
    my $reldir = $ENV{DEPTH};
    # Add a / at the end if there is none.
    if (substr($reldir, -1) ne '/') {
        $reldir .= '/';
    }
    my $uplink = "${reldir}Documentation/web/manuals.${lang_ext}html";
    my $uplink_text = lilypond_gdt($lang, 'Back to Documentation Index');
    my $uplink_title = lilypond_gdt($lang, 'Documentation Index');

    my $title;
    if ($texinfo_version lt '7.0') {
        $title = $converter->{'title_string'};
    } else {
        $title = $converter->get_info('title_string');
    }
    my $top_href = lilypond_from_element_direction($converter, 'Top', 'href');
    # Here we use the already translated text of the 'Top' navigation button.
    my $top_title;
    if ($converter->can('cdt')) {
      $top_title = $converter->cdt('Cover (top) of document');
    } else {
      $top_title = $converter->gdt('Cover (top) of document');
    }
    $top_title = $converter->convert_tree($top_title);

    return "
<p class=\"toc_uplink\">
  <a href=\"$uplink\" title=\"$uplink_title\">
    &lt;&lt; $uplink_text
  </a>
</p>
<h4 class=\"toc_header\">
  <a href=\"$top_href\" title=\"$top_title\">
    $title <!-- Sidebar Version Tag -->
  </a>
</h4>
";
}

sub lilypond_format_tocframe {
    my $converter = shift;

    my $tocheader = lilypond_format_tocheader($converter);

    # Get the contents without highlighting of the current element.
    my $contents;
    if ($texinfo_version lt '7.0') {
        $contents = &{$converter->{'format_contents'}}($converter, 'contents');
    } else {
        $contents = &{$converter->formatting_function('format_contents')}($converter, 'contents');
    }

    # Find the path to the current element.
    my $node = $converter->{'current_node'};
    my $element;
    my $section_relation;
    if ($texinfo_version lt '7.2.91') {
        $element = $node->{'extra'}->{'associated_section'};
    } else {
        my $node_relation = $converter->converter_node_relations_of_node($node);
        if (defined($node_relation)) {
            $section_relation = $node_relation->{'associated_section'};
            if (defined($section_relation)) {
                $element = $section_relation->{'element'};
            }
        }
    }
    my %parentelements;
    while (defined($element)) {
        my $toc_id = $converter->command_contents_target($element, 'contents');
        if (!defined($toc_id)) {
            last;
        }
        $parentelements{$toc_id} = 1;
        if ($texinfo_version lt '7.0') {
            $element = $element->{'section_up'};
        } elsif ($texinfo_version lt '7.1.90') {
            $element = $element->{'structure'}->{'section_up'};
        } elsif ($texinfo_version lt '7.2.90') {
            $element = $element->{'extra'}->{'section_directions'}->{'up'};
        } else {
            $section_relation = $section_relation->{'section_directions'}->{'up'};
            if (defined($section_relation)) {
                $element = $section_relation->{'element'};
            } else {
                $element = undef;
            }
        }
    }

    # Construct the contents with highlighting.
    my $highlighted_contents = '';
    for my $line (split(/\n/, $contents)) {
        if ($line =~ /<a id="(.*)" href=/ and $parentelements{$1}) {
            $line =~ s/<li>/<li class="toc_current">/;
        }
        $highlighted_contents .= "$line\n";
    }

    # Get the (translated) search box.
    local $/ = undef;
    my $search_box_filename = 'search-box.ihtml';
    my $lang = $converter->get_conf('documentlanguage');
    my $fh;
    open($fh, "$ENV{TOP_SRC_DIR}/Documentation/$lang/$search_box_filename") or
        open($fh, "$ENV{TOP_SRC_DIR}/Documentation/en/$search_box_filename") or
        die "no such file: $search_box_filename: $!";
    my $search_box = decode('UTF-8', <$fh>);
    close($fh);
    # We depend on an external entity here, which we cannot control.  Suppose
    # we go from 2.23.x to 2.25.x and put the documentation under "v2.25".
    # For some time, Google won't have the new location in its index, so the
    # search would get nothing.  It is better to keep "v2.23" in the search for
    # a while.
    my $search_site = 'lilypond.org/doc/v2.25';
    if ($manual_name eq 'web') {
        $search_site = 'lilypond.org';
    }
    $search_box =~ s/\{\{site\}\}/site:$search_site/g;

    return "
<div id=\"tocframe\">
$tocheader
$highlighted_contents
$search_box
</div>
";
}

sub lilypond_format_end_file {
    my ($converter, $filename) = @_;

    my $tocframe = lilypond_format_tocframe($converter);

    return "
<!-- FOOTER -->
</div>
$tocframe
</body>
</html>
";
}
texinfo_register_formatting_function('format_end_file',
                                     \&lilypond_format_end_file);


### Custom navigation headers / panels
sub format_navigation_button {
    my ($converter, $direction) = @_;

    my $name = lilypond_from_element_direction($converter, $direction, 'node');
    if (!defined($name)) {
        $name = '';
    } elsif ($name ne 'Top') {
        if ($texinfo_version lt '7.2.90') {
            $name = lilypond_from_element_direction($converter, $direction, 'section');
        } else {
            $name = lilypond_from_element_direction($converter, $direction, 'section_nonumber');
        }
    }
    my $text;
    if ($texinfo_version lt '7.1') {
        $text = $converter->get_conf('BUTTONS_TEXT')->{$direction};
    } else {
        $text = $converter->direction_string($direction, 'text');
    }
    if ($direction eq 'Back' or $direction eq 'FastBack') {
        $text = "$text$name ";
    } elsif ($direction eq 'Forward' or $direction eq 'FastForward') {
        $text = " $name$text";
    } elsif ($direction eq 'Up') {
        if ($name eq '') {
          return "";
        }
        # Strip trailing whitespace.
        $text =~ s/\s+$//g;
        # The translations have whitespace in front, so make sure it's symmetric.
        $text = " $text: $name ";
    }
    my $href = lilypond_from_element_direction($converter, $direction, 'href');
    if (!defined($href)) {
        return "[$text]";
    }

    # Copied and adapted from _direction_href_attributes / _default_format_button
    my $attributes = '';
    my $description;
    if ($texinfo_version lt '7.1') {
        if ($converter->get_conf('BUTTONS_GOTO')) {
            $description = $converter->get_conf('BUTTONS_GOTO')->{$direction};
        }
    } else {
        $description = $converter->direction_string($direction, 'description');
    }
    if (defined($description)) {
        $attributes .= " title=\"$description\"";
    }
    if ($converter->get_conf('USE_ACCESSKEY')) {
        my $accesskey;
        if ($texinfo_version lt '7.1') {
            if ($converter->get_conf('BUTTONS_ACCESSKEY')) {
                $accesskey = $converter->get_conf('BUTTONS_ACCESSKEY')->{$direction};
            }
        } else {
            $accesskey = $converter->direction_string($direction, 'accesskey');
        }
        if (defined($accesskey) and $accesskey ne '') {
            $attributes .= " accesskey=\"$accesskey\"";
        }
    }
    if ($texinfo_version ge '7.2.90' or $converter->get_conf('USE_REL_REV')) {
        my $button_rel;
        if ($texinfo_version lt '7.1') {
            if ($converter->get_conf('BUTTONS_REL')) {
                $button_rel = $converter->get_conf('BUTTONS_REL')->{$direction};
            }
        } else {
            $button_rel = $converter->direction_string($direction, 'rel');
        }
        if (defined($button_rel) and ($button_rel ne '')) {
            $attributes .= " rel=\"$button_rel\"";
        }
    }

    return "[<a href=\"$href\"$attributes>$text</a>]";
}

our $format_navigation_panel = 'format_navigation_panel';
if ($texinfo_version lt '7.0') {
    $format_navigation_panel = 'format_navigation_header_panel';
}
sub lilypond_format_navigation_panel {
    my ($converter, $buttons, $command_name, $element, $vertical) = @_;

    if ($manual_name eq 'web') {
        return '';
    }

    # Compare the button reference with one of the expected values.
    my $section_buttons = (
        ($buttons == $converter->get_conf('SECTION_BUTTONS')) or
        ($buttons == $converter->get_conf('NODE_FOOTER_BUTTONS')));
    my $result = "<table class=\"nav_table\">\n";

    # First row:
    $result .= '<tr>';
    if ($section_buttons) {
        $result .= '<td align="left">';
        $result .= format_navigation_button($converter, 'FastBack');
        $result .= '</td>';
    }
    $result .= '<td align="center">';
    $result .= format_navigation_button($converter, 'Top');
    $result .= format_navigation_button($converter, 'Contents');
    # Not all manuals have an index.
    if (defined(lilypond_from_element_direction($converter, 'Index', 'href'))) {
        $result .= format_navigation_button($converter, 'Index');
    }
    $result .= '</td>';
    if ($section_buttons) {
        $result .= '<td align="right">';
        $result .= format_navigation_button($converter, 'FastForward');
        $result .= '</td>';
    }
    $result .= "</tr>\n";

    if ($section_buttons) {
        # (Optional) second row:
        $result .= '<tr>';
        $result .= '<td align="left">';
        $result .= format_navigation_button($converter, 'Back');
        $result .= '</td>';
        $result .= '<td align="center">';
        $result .= format_navigation_button($converter, 'Up');
        $result .= '</td>';
        $result .= '<td align="right">';
        $result .= format_navigation_button($converter, 'Forward');
        $result .= '</td>';
        $result .= "</tr>\n";
    }
    $result .= "</table>\n";

    return $result;
}
texinfo_register_formatting_function($format_navigation_panel,
                                     \&lilypond_format_navigation_panel);

return 1;
