%# BEGIN BPS TAGGED BLOCK {{{
%#
%# COPYRIGHT:
%#
%# This software is Copyright (c) 1996-2025 Best Practical Solutions, LLC
%#                                          <sales@bestpractical.com>
%#
%# (Except where explicitly superseded by other copyright notices)
%#
%#
%# LICENSE:
%#
%# This work is made available to you under the terms of Version 2 of
%# the GNU General Public License. A copy of that license should have
%# been provided with this software, but in any event can be snarfed
%# from www.gnu.org.
%#
%# This work 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 this program; if not, write to the Free Software
%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
%# 02110-1301 or visit their web page on the internet at
%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
%#
%#
%# CONTRIBUTION SUBMISSION POLICY:
%#
%# (The following paragraph is not intended to limit the rights granted
%# to you to modify and distribute this software under the terms of
%# the GNU General Public License and is only of importance to you if
%# you choose to contribute your changes and enhancements to the
%# community by submitting them to Best Practical Solutions, LLC.)
%#
%# By intentionally submitting any modifications, corrections or
%# derivatives to this work, or any other work intended for use with
%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
%# you are the copyright holder for those contributions and you grant
%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
%# royalty-free, perpetual, license to use, copy, create derivative
%# works based on those contributions, and sublicense and distribute
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
<%args>
$Query => "id > 0"
@GroupBy => ()
$ChartStyle => 'bar+table+sql'
@ChartFunction => 'COUNT'
$Width  => undef
$Height => undef
$SavedSearchId => ''
$StackedGroupBy => undef
$Class => 'RT::Tickets'
$Columns => undef
$Report => undef
</%args>

% my $id = join '-', 'search-chart', $SavedSearchId || ();
<canvas id="<% $id %>" width="<% $Width %>" height="<% $Height %>"></canvas>
<script type="text/javascript">
// wrap code to not pollute default namespaces to support multiple charts on one page
(function() {
var data_labels = data_labels || {};
data_labels['<% $SavedSearchId %>'] = <% JSON( \@data_labels ) |n %>;

% if ( $#data == 1 )  {
var backgroundColor = Chart.colorschemes[<% $scheme_parts[0] |n,j %>][<% $scheme_parts[1] |n,j %>];
% }

var ctx = document.getElementById(<% $id |n,j %>).getContext('2d');
var searchChart = new Chart(ctx, {
    type: <% $ChartStyle =~ /\b(bar|line|pie)\b/ ? $1 : 'bar' |n,j %>,
    data: {
        labels: <% JSON( [ map { join ' - ', @$_ } @{$data[0]} ] ) |n %>,
        datasets: [
% for my $index ( 1 .. $#data ) {
          {
%   if ( $#data == 1 )  {
            backgroundColor: backgroundColor || [],
%   }

%   if ( $ChartStyle =~ /\bbar|line\b/ ) {
            label: <% $labels[$index-1] |n,j %>,
%   }
            data: <% JSON($data[$index]) |n %>
          }
%   if ( $index != $#data ) {
        ,
%   }
% }
      ]
    },
    options: {
        plugins: {
            colorschemes: {
%# Unset scheme as backgroundColor is explicitly specified.
%# Because of a colorschemes bug, setting it overrides explicit backgroundColor setting.
                scheme: <% $#data == 1 ? '' : $scheme |n,j %>
            }
        },
% if ( $ChartStyle =~ /\bbar|line\b/ ) {
        legend: {
            display: <% @data == 2 ? 'false' : 'true' %>
        },
        tooltips: {
            callbacks: {
                label: function(tooltipItem, data) {
                    var label = data.datasets[tooltipItem.datasetIndex].label || '';

                    if (label) {
                        label += ': ';
                    }
                    label += data_labels['<% $SavedSearchId %>'][tooltipItem.datasetIndex][tooltipItem.index];
                    return label;
                }
            }
        },
        scales: {
            x: {
                stacked: <% $stacked ? 'true' : 'false' %>,
                title: {
                    display: true,
                    labelString: <% join(' - ', map $report->Label( $_), @{ $columns{'Groups'} }) |n,j %>
                },
                grid: {
                    display: false
                }
            },
            y: {
                stacked: <% $stacked ? 'true' : 'false' %>,
                title: {
                    display: true,
                    labelString: <% $report->Label( $columns{'Functions'}[0] ) |n,j %>
                },
                grid: {
                    drawTicks: true,
                    drawOnChartArea: false
                },
                ticks: {
                    beginAtZero: true,
                    callback: function(value, index, values) {
%                       my $info = $report->ColumnInfo($columns{'Functions'}[0]);
%                       if ( $info->{META} && ($info->{META}{Display}//'') eq 'DurationAsString' ) {
                            return;
%                       } else {
                            if (Math.floor(value) === value) {
                                return value;
                            }
%                       }
                    }
                }
            }
        }
% }
    }
});

var group_by = <% JSON( \@GroupBy ) |n %>;
var data_queries = <% JSON( \@data_queries ) |n %>;

jQuery('[id="<% $id |n %>"]').click(function(e) {
    let slice = searchChart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, true);

    if ( !slice[0] ) return;
    var query;
%   if ( $stacked ) { # data_queries is array of array for stacked charts
        if ( data_queries[slice[0].datasetIndex][slice[0].index] ) {
            query = data_queries[slice[0].datasetIndex][slice[0].index];
        }
%   } else {
        if ( data_queries[slice[0].index] ) {
            query = data_queries[slice[0].index];
        }
%   }
    window.open(RT.Config.WebPath + '/Search/Results.html?' + query);
});
})();
</script>

<%init>
s/\D//g for grep defined, $Width, $Height;
$Width  ||= ($ChartStyle =~ /\bpie\b/ ? 400 : 600);
$Height ||= ($ChartStyle =~ /\bpie\b/ ? $Width : 400);
$Height = $Width if $ChartStyle =~ /\bpie\b/;

my $report_class = ( $Class || 'RT::Tickets' )->ReportClass
    or Abort( loc( "Couldn't find ReportClass for [_1]", $Class || 'RT::Tickets' ) );
RT::StaticUtil::RequireModule($report_class) or Abort( loc("Couldn't load [_1]", $report_class) );
my $report = $report_class->new( $session{'CurrentUser'} );

@GroupBy = $report_class->DefaultGroupBy unless @GroupBy;

my %columns;
if ( $Columns && $Report ) {
    %columns = %{$Columns};
    $report = $Report;
} else {
    %columns = $report->SetupGroupings(
        Query => $Query,
        GroupBy => \@GroupBy,
        Function => \@ChartFunction,
    );

    $report->SortEntries( map { $_ => $ARGS{$_} } grep { $ARGS{$_} } qw(ChartOrderBy ChartOrder ChartLimit ChartLimitType) );
}

my @data = ([],[]);
my @data_labels;
my @data_queries;
while ( my $entry = $report->Next ) {
    push @{ $data[0] }, [ map $entry->LabelValue( $_ ), @{ $columns{'Groups'} } ];

    my $q = $entry->Query; # it's empty if group by is invalid
    push @data_queries,
        QueryString(
            ShortenSearchQuery(
                map( { $_ => $ARGS{$_} }
                    grep { defined $ARGS{$_} and length $ARGS{$_} } @RT::Interface::Web::SHORTENER_SEARCH_FIELDS ),
                Query => !$q || $Query =~ /^\s*\(.*\) AND \(?\Q$q\E\)?$/
                ? $Query
                : join( ' AND ', map { /^\s*\(.*\)$/ ? $_ : "($_)" } $Query, $q ),
            )
        );

    my @values;
    my @label_values;
    foreach my $column ( @{ $columns{'Functions'} } ) {
        my $v = $entry->RawValue( $column );
        my $label_v = $entry->LabelValue( $column );
        unless ( ref $v ) {
            push @values, $v;
            push @label_values, $label_v;
            next;
        }

        my @subs = $report->FindImplementationCode(
            $report->ColumnInfo( $column )->{'META'}{'SubValues'}
        )->( $report );
        push @values, map $v->{$_}, @subs;
        push @label_values, map $label_v->{$_}, @subs;
    }

    my $i = 0;
    my $j = 0;
    push @{ $data[++$i] }, $_ foreach @values;
    push @{ $data_labels[$j++] }, $_ foreach @label_values;
}

$ChartStyle =~ s/\bpie\b/bar/ if @data > 2;

my @labels;
if ( $ChartStyle =~ /\b(?:bar|line)\b/ ) {
    for my $column ( @{$columns{'Functions'}} ) {
        my $info = $report->ColumnInfo( $column );
        my @subs;
        if ( $info->{'META'}{'SubValues'} ) {
            push @labels, map { join ': ', $report->Label($column), $_ } $report->FindImplementationCode( $info->{'META'}{'SubValues'} )->($report);
        }
        else {
            push @labels, $report->Label($column);
        }
    }
}

my $stacked;
if ( $StackedGroupBy && $ChartStyle =~ /\bbar\b/ ) {
    if ( scalar @data > 2 ) {
        RT->Logger->warning("Invalid stack option: it can't apply to multiple data rows");
    }
    else {

        my $labels = $data[0];

        # find the stacked group index
        require List::MoreUtils;
        my $stacked_index = List::MoreUtils::first_index { $_ eq $StackedGroupBy } @GroupBy;
        if ( $stacked_index >= 0 ) {
            my %rows;
            my $i = 0;
            my %new_label;

            for my $label (@$labels) {
                my @new_label = @$label;
                splice @new_label, $stacked_index, 1;    # remove the stacked group
                my $key = join ';;;', @new_label;
                $new_label{$key}                               = \@new_label;
                $rows{ $label->[$stacked_index] }{$key}{value} = $data[1][$i];
                $rows{ $label->[$stacked_index] }{$key}{label} = $data_labels[0][$i];
                $rows{ $label->[$stacked_index] }{$key}{query} = $data_queries[$i];
                $i++;
            }

            @data = [ map { $new_label{$_} } sort { lc $a cmp lc $b } keys %new_label ];

            my @dataset_labels;
            @data_queries = ();
            @data_labels  = ();
            for my $stacked_field ( sort { lc $a cmp lc $b } keys %rows ) {
                push @dataset_labels, join ' - ', $labels[0], $stacked_field;
                my ( @new_values, @new_labels, @new_queries );
                for my $key ( sort { lc $a cmp lc $b } keys %new_label ) {
                    push @new_values,  $rows{$stacked_field}->{$key}{value} || 0;
                    push @new_labels,  $rows{$stacked_field}->{$key}{label};
                    push @new_queries, $rows{$stacked_field}->{$key}{query};
                }
                push @data,         \@new_values;
                push @data_labels,  \@new_labels;
                push @data_queries, \@new_queries;
            }
            @labels  = @dataset_labels;
            $stacked = 1;
        }
        else {
            RT->Logger->warning("Invalid StackedGroupBy: $StackedGroupBy");
        }
    }
}

my $scheme = RT->Config->Get('JSChartColorScheme', $session{CurrentUser}) || 'brewer.Paired12';
my @scheme_parts = split /\./, $scheme, 2;
</%init>
