%# BEGIN BPS TAGGED BLOCK {{{
%#
%# COPYRIGHT:
%#
%# This software is Copyright (c) 1996-2026 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>
$id
$oldestTransactionsFirst => RT->Config->Get("OldestTransactionsFirst", $session{CurrentUser});
$lastTransactionId => undef
$focusTransactionId => undef
$loadAll => 0
$mode => 'append'
@FilterTxnTypes => ()
$SearchHistory => undef
$ShowHeaders => undef
$ReverseTxns => undef
$Page => undef
$PerPage => undef
$ShowPagination => 0
</%ARGS>
<%INIT>

my $TicketObj = RT::Ticket->new($session{'CurrentUser'});
$TicketObj->Load($id);

my %extra_args;
$m->callback(
    CallbackPage => '/Helpers/TicketHistory',
    CallbackName => 'ExtraShowHistoryArguments',
    Ticket => $TicketObj,
    ExtraArgs => \%extra_args
);

my $transactions;
# Use ReverseTxns parameter if provided, otherwise use the oldestTransactionsFirst setting
my $order;
if (defined $ReverseTxns) {
    $order = $ReverseTxns eq 'ASC' ? 'ASC' : 'DESC';
    $oldestTransactionsFirst = $ReverseTxns eq 'ASC' ? 1 : 0;
} else {
    $order = $oldestTransactionsFirst ? 'ASC' : 'DESC';
}

# If we have a search term, create a new collection with FromSQL
if ($SearchHistory && $SearchHistory =~ /\S/) {
    $transactions = RT::Transactions->new($session{'CurrentUser'});

    $SearchHistory =~ s/(['\\])/\\$1/g;
    # Build the search query with ticket constraint
    my $query = "Content LIKE '$SearchHistory' AND TicketId = " . $TicketObj->id . " AND ObjectType = 'RT::Ticket'";
    my ($ok, $msg) = $transactions->FromSQL($query);

    RT->Logger->error("Transaction search with query $query failed: $msg") unless $ok;
} else {
    # Use the standard ticket transactions collection
    $transactions = $TicketObj->Transactions;
}

# Apply transaction type filter if provided
if (@FilterTxnTypes) {
    $transactions->Limit(
        FIELD    => 'Type',
        OPERATOR => 'IN',
        VALUE    => \@FilterTxnTypes
    );
}
elsif ( $ARGS{'FilterTxnTypes-Magic'} ) {
    $transactions->Limit(
        FIELD      => 'Type',
        OPERATOR   => 'IS',
        VALUE      => 'NULL',
        QUOTEVALUE => 0,
    );
}

# Apply common filters and ordering
if ($lastTransactionId) {
    my $op = $mode eq 'prepend' || $oldestTransactionsFirst ? '>' : '<';

    # Use a compound filter matching the ORDER BY (Created, id) rather than
    # filtering on id alone.  An id-only filter breaks when a transaction's
    # Created date has been back-dated, causing earlier transactions to appear
    # more than once in scroll mode.
    my $last_txn = RT::Transaction->new( $session{'CurrentUser'} );
    $last_txn->Load($lastTransactionId);

    if ( $last_txn->id ) {
        my $last_created = $last_txn->Created;

        $transactions->Limit(
            FIELD           => 'Created',
            OPERATOR        => $op,
            VALUE           => $last_created,
            SUBCLAUSE       => 'cursor',
        );
        $transactions->_OpenParen('cursor');
        $transactions->Limit(
            FIELD           => 'Created',
            OPERATOR        => '=',
            VALUE           => $last_created,
            ENTRYAGGREGATOR => 'OR',
            SUBCLAUSE       => 'cursor',
        );
        $transactions->Limit(
            FIELD           => 'id',
            OPERATOR        => $op,
            VALUE           => $lastTransactionId,
            ENTRYAGGREGATOR => 'AND',
            SUBCLAUSE       => 'cursor',
        );
        $transactions->_CloseParen('cursor');
    }
    else {
        # Fall back to the simple id-only filter if the transaction can't be loaded.
        $transactions->Limit(
            FIELD    => 'id',
            OPERATOR => $op,
            VALUE    => $lastTransactionId,
        );
    }
}

$transactions->OrderByCols(
    { FIELD => 'Created',   ORDER => $order },
    { FIELD => 'id',        ORDER => $order },
);

# Handle pagination parameters
my $total_transactions_count;
my $total_pages;
if ($ShowPagination && defined $Page) {
    # Use configured default or fallback if not set
    $PerPage ||= RT->Config->Get('HistoryPerPage') || 10;

    # Calculate total count for pagination BEFORE applying pagination limits
    $total_transactions_count = $transactions->CountAll;
    $total_pages = int(($total_transactions_count + $PerPage - 1) / $PerPage);

    # Validate and adjust page number
    $Page = 1 unless $Page =~ /\A\d+\z/ && $Page > 1;
    $Page = $total_pages if $Page > $total_pages && $total_pages > 0;

    # Apply pagination to collection (RT uses 0-based page indexing internally)
    $transactions->RowsPerPage($PerPage);
    $transactions->GotoPage($Page - 1);

    # Auto jump to the page that contains specified txn
    if ($focusTransactionId) {
        my $auto_page = $Page;
        while ( !grep { $focusTransactionId == $_->Id } @{ $transactions->ItemsArrayRef || [] } ) {
            $auto_page++;
            $transactions->GotoPage( $auto_page - 1 );
            if ( $auto_page > $total_pages ) {
                RT->Logger->debug("Couldn't find transaction #$focusTransactionId");
                undef $auto_page;
                last;
            }
        }
        $Page = $auto_page if $auto_page;
    }
} elsif ($focusTransactionId) { # make sure we load enough if we need to focus a transaction
    $transactions->Limit(
        FIELD    => 'id',
        OPERATOR => $oldestTransactionsFirst ? '<=' : '>=',
        VALUE    => $focusTransactionId,
        ENTRYAGGREGATOR => 'AND',
    );
} elsif (!$loadAll) { # otherwise, just load the standard page of 10 transactions
    $transactions->RowsPerPage(10);
    $transactions->FirstPage();
}
</%INIT>
<%perl>
if ($ShowPagination && defined $Page) {

    $m->comp('/Elements/ShowHistoryPage',
        Object => $TicketObj,
        ShowHeaders => (defined $ShowHeaders ? $ShowHeaders : $ARGS{'ShowHeaders'}),
        Transactions => $transactions,
        Attachments => $TicketObj->Attachments,
        AttachmentContent => $TicketObj->TextAttachments,
        %extra_args,
    );

    # Add pagination controls if there are multiple pages
    if ($total_pages > 1) {
        my %url_params = map { $_ => $ARGS{$_} }
            grep { defined $ARGS{$_} && !blessed $ARGS{$_} && $_ ne 'focusTransactionId' } keys %ARGS;
        $url_params{ShowPagination} = 1;

        my $base_url = RT->Config->Get('WebPath') . "/Helpers/TicketHistoryPage";

        $m->comp('/Elements/CollectionListPaging',
            BaseURL => $base_url,
            Rows => $PerPage,
            TotalFound => $total_transactions_count,
            CurrentPage => $Page,
            Pages => $total_pages,
            URLParams => \%url_params,
            PageParam => 'Page',
            UseHtmx => 1,
            HtmxTarget => '.history-container',
            # go to the top of the whole History widget to allow space for the floating page menu
            HtmxSwap => 'innerHTML show:.row:has(.history-container):top',
        );
    }
} else {
    # Non-pagination mode
    $m->comp('/Elements/ShowHistoryPage',
        Object => $TicketObj,
        ShowHeaders => (defined $ShowHeaders ? $ShowHeaders : $ARGS{'ShowHeaders'}),
        Transactions => $transactions,
        %extra_args,
    );
}
</%perl>
% $m->abort();
