%# 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>
$Object         => undef
$CustomFields   => undef
$ARGSRef        => $DECODED_ARGS
$DefaultType    => undef
</%args>
<%init>
my $id = ( $Object and $Object->id ) ? $Object->id : "new";

my $default_type = RT::Links->DefaultLinkType( CurrentUser => $session{CurrentUser}, Default => $DefaultType );

# Pre-fill rows from link GET/POST params (e.g. CloneTicket, "Create linked ticket"
# shortcuts). A local-object URI is split into the object type (selects the row's
# dropdown) and a bare display value (id, or the unique Name for users/groups); the
# shorthand prefix is recomposed at submit time so the value box stays clean.
# External URLs and remote URIs pass through unchanged.
my %class_object_type = (
    'RT::Ticket'      => 'ticket',
    'RT::Transaction' => 'transaction',
    'RT::Asset'       => 'asset',
    'RT::Article'     => 'article',
    'RT::User'        => 'user',
    'RT::Group'       => 'group',
);
my @prefill;
my %_link_fields = map { $_ => 1 }
    ( "$id-DependsOn", "DependsOn-$id", "$id-MemberOf", "MemberOf-$id", "$id-RefersTo", "RefersTo-$id", );
for my $field ( sort keys %_link_fields ) {
    my $val = $ARGSRef->{$field};
    next unless defined $val && ( ref $val ? @$val : length $val );
    for my $v ( split ' ', ( ref $val ? join( ' ', @$val ) : $val ) ) {
        my ( $class, $local_id ) = RT::URI->ParseObjectURI($v);
        my ( $value, $object_type, $init_id ) = ( $v, '', '' );
        if ( $class && $class_object_type{$class} ) {
            $object_type = $class_object_type{$class};
            if ( $class eq 'RT::User' || $class eq 'RT::Group' ) {

                # Submit the space-free id, not the Name: a Name like "Help Desk" would be
                # split apart by the server's space-delimited link parsing. The box shows the Name.
                my $obj = $class->new( $session{CurrentUser} );
                $obj->Load($local_id);
                $value   = $obj->id ? $obj->Name : $local_id;
                $init_id = $obj->id ? $obj->id   : $local_id;
            }
            else {
                $value = $local_id;
            }
        }
        push @prefill, { field => $field, value => $value, object_type => $object_type, init_id => $init_id };
    }
}

# Autocomplete excludes objects already linked in the same relationship family (both
# directions), plus the object itself. Key by RT's canonical link Type (DependsOn/MemberOf/
# RefersTo), the same value each row exposes as data-type, so the client looks it up untranslated.
my %link_excludes;
if ( $id ne 'new' ) {
    my %family = (
        DependsOn => [qw/DependsOn DependedOnBy/],
        MemberOf  => [qw/MemberOf Members/],
        RefersTo  => [qw/RefersTo ReferredToBy/],
    );

    # The per-type autocomplete endpoints disagree on the exclude separator: Tickets splits on
    # whitespace, the rest on commas. Pre-join with the right one so values drop straight in.
    my %exclude_delim = ( ticket => ' ' );    # everything else uses ','

    my $self_type = $class_object_type{ ref $Object };

    for my $canonical ( keys %family ) {
        my %by_type;    # object_type => { id => 1 }
        for my $relation ( @{ $family{$canonical} } ) {
            my $mode  = $RT::Link::TYPEMAP{$relation}{Mode};    # 'Target' or 'Base': the far end
            my $links = $Object->$relation;
            $links->GotoFirstItem;
            while ( my $link = $links->Next ) {
                my ( $class, $linked_id ) = RT::URI->ParseObjectURI( $link->$mode );
                next unless $class;                             # skip external/remote links
                my $ot = $class_object_type{$class} or next;
                $by_type{$ot}{$linked_id} = 1;
            }
        }

        # Can't link the object to itself: exclude its own id from its own object type.
        $by_type{$self_type}{$id} = 1 if $self_type;

        for my $ot ( keys %by_type ) {
            my $sep = $exclude_delim{$ot} // ',';
            $link_excludes{$canonical}{$ot} = join $sep, sort { $a <=> $b } keys %{ $by_type{$ot} };
        }
    }
}

# Always valid JSON (empty object when there are no links) so the client can JSON.parse unconditionally.
my $link_excludes_json = JSON( \%link_excludes );
</%init>
<div class="add-links-section mb-3" data-link-excludes="<% $link_excludes_json %>">
  <div class="label mb-1"><&|/l&>Add links</&></div>
  <div class="add-links-rows">
% for my $pf (@prefill) {
    <& /Elements/AddLinksRow, id => $id, First => 0, InitField => $pf->{field}, InitValue => $pf->{value}, InitObjectType => $pf->{object_type}, InitId => $pf->{init_id}, DefaultType => $default_type &>
% }
    <& /Elements/AddLinksRow, id => $id, First => 1, DefaultType => $default_type &>
    <& /Elements/AddLinksRow, id => $id, First => 0, DefaultType => $default_type &>
  </div>

%# Link custom fields: only on the create page, where there is no ShowLinks listing. For an
%# existing object the editor's ShowLinks(Edit=1) renders them in place, so skip to avoid a duplicate.
% if ( $id eq 'new' ) {
  <div class="edit-custom-fields-container" hx-get="<% RT->Config->Get('WebPath') %>/Views/Component/EditCustomFields?ObjectType=<% ref $Object %>&ObjectId=<% $Object->Id %>&Grouping=Links&InTable=1" hx-swap="innerHTML">
    <& /Elements/EditCustomFields,
          Object   => $Object,
          Grouping => 'Links',
          InTable  => 1,
          ( $CustomFields ? ( CustomFields => $CustomFields ) : () ),
    &>
  </div>
% }
% $m->callback( CallbackName => 'NewLink' );
</div>
