# --
# Copyright (C) 2001-2021 OTRS AG, https://otrs.com/
# Copyright (C) 2021 Znuny GmbH, https://znuny.org/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --

package Kernel::Modules::AdminAppointmentCalendarManage;

use strict;
use warnings;

use Kernel::Language              qw(Translatable);
use Kernel::System::VariableCheck qw(:all);

use parent qw(Kernel::System::AsynchronousExecutor);

our $ObjectManagerDisabled = 1;

sub new {
    my ( $Type, %Param ) = @_;

    # allocate new hash for object
    my $Self = {%Param};
    bless( $Self, $Type );

    # Certain search parameters for ticket appointments should be stored as scalars, not array refs.
    $Self->{SearchParamScalar} = [
        'MIMEBase_From',
        'MIMEBase_To',
        'MIMEBase_Cc',
        'MIMEBase_Subject',
        'MIMEBase_Body',
        'MIMEBase_AttachmentName',
    ];

    return $Self;
}

sub Run {
    my ( $Self, %Param ) = @_;

    my $ParamObject     = $Kernel::OM->Get('Kernel::System::Web::Request');
    my $HTMLUtilsObject = $Kernel::OM->Get('Kernel::System::HTMLUtils');

    # get names of all parameters
    my @ParamNames = $ParamObject->GetParamNames();

    # get params
    my %GetParam;
    PARAMNAME:
    for my $Key (@ParamNames) {

        # Queue, OwnerIDs and ResponsibleIDs are multiple selection fields, get array instead.
        if (
            $Key    =~ /^QueueID_/
            || $Key =~ /^SearchParam_[0-9a-f]+_OwnerIDs$/
            || $Key =~ /^SearchParam_[0-9a-f]+_ResponsibleIDs$/
            )
        {
            my @ParamArray = $ParamObject->GetArray( Param => $Key );

            my @SafeParamArray;
            for my $Value (@ParamArray) {
                if ( defined $Value ) {
                    my %SafeValue = $HTMLUtilsObject->Safety(
                        String       => $Value,
                        NoApplet     => 1,
                        NoObject     => 1,
                        NoEmbed      => 1,
                        NoSVG        => 1,
                        NoImg        => 1,
                        NoIntSrcLoad => 1,
                        NoExtSrcLoad => 1,
                        NoJavaScript => 1,
                    );

                    $Value = $SafeValue{String};
                }

                push @SafeParamArray, $Value;
            }

            $GetParam{$Key} = \@SafeParamArray;
            next PARAMNAME;
        }

        $GetParam{$Key} = $ParamObject->GetParam( Param => $Key );
        if ( !ref $GetParam{$Key} ) {
            my %SafeGetParam = $HTMLUtilsObject->Safety(
                String       => $GetParam{$Key},
                NoApplet     => 1,
                NoObject     => 1,
                NoEmbed      => 1,
                NoSVG        => 1,
                NoImg        => 1,
                NoIntSrcLoad => 1,
                NoExtSrcLoad => 1,
                NoJavaScript => 1,
            );
            $GetParam{$Key} = $SafeGetParam{String};
        }
    }

    my $LayoutObject   = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
    my $CalendarObject = $Kernel::OM->Get('Kernel::System::Calendar');

    my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');

    # Get user's permissions to associated modules which are displayed as links.
    for my $Module (qw(AgentAppointmentCalendarOverview AdminAppointmentImport)) {
        my $ModuleGroups = $Kernel::OM->Get('Kernel::Config')->Get('Frontend::Module')
            ->{$Module}->{Group} // [];

        if ( IsArrayRefWithData($ModuleGroups) ) {
            MODULE_GROUP:
            for my $ModuleGroup ( @{$ModuleGroups} ) {
                my $HasPermission = $GroupObject->PermissionCheck(
                    UserID    => $Self->{UserID},
                    GroupName => $ModuleGroup,
                    Type      => 'rw',
                );
                if ($HasPermission) {
                    $Param{ModulePermissions}->{$Module} = 1;
                    last MODULE_GROUP;
                }
            }
        }

        # Always allow links if no groups are specified.
        else {
            $Param{ModulePermissions}->{$Module} = 1;
        }
    }

    if ( $Self->{Subaction} eq 'New' ) {

        my $GroupSelection     = $Self->_GroupSelectionGet();
        my $ColorPalette       = $Self->_ColorPaletteGet();
        my $ValidSelection     = $Self->_ValidSelectionGet();
        my %TicketAppointments = $Self->_TicketAppointments();

        $LayoutObject->Block(
            Name => 'CalendarEdit',
            Data => {
                GroupID      => $GroupSelection,
                ValidID      => $ValidSelection,
                Subaction    => 'StoreNew',
                Color        => $ColorPalette->[ int rand( scalar @{$ColorPalette} ) ],
                Title        => Translatable('Add new Calendar'),
                WidgetStatus => 'Collapsed',
                %TicketAppointments,
            },
        );

        $LayoutObject->AddJSData(
            Key   => 'CalendarColorPalette',
            Value => $ColorPalette,
        );

        $Param{Action} = 'New';
    }
    elsif ( $Self->{Subaction} eq 'StoreNew' ) {

        my %Error;

        # check name
        if ( !$GetParam{CalendarName} ) {
            $Error{'CalendarNameInvalid'} = 'ServerError';
        }
        else {

            # check if there is a calendar with same name
            my %Calendar = $CalendarObject->CalendarGet(
                CalendarName => $GetParam{CalendarName},
            );

            if (%Calendar) {
                $Error{CalendarNameInvalid} = "ServerError";
                $Error{CalendarNameExists}  = 1;
            }
        }

        $GetParam{TicketAppointments} = $Self->_GetTicketAppointmentParams(%GetParam);

        # Get queue create permissions for the user.
        my %UserGroups = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet(
            UserID => $Self->{UserID},
            Type   => 'create',
        );

        my @ValidIDs = $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet();

        my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');

        # Queue field in ticket appointments is mandatory, check if it's present and valid.
        for my $Rule ( @{ $GetParam{TicketAppointments} } ) {
            if ( defined $Rule->{QueueID} && IsArrayRefWithData( $Rule->{QueueID} ) ) {

                QUEUE_ID:
                for my $QueueID ( sort @{ $Rule->{QueueID} || [] } ) {
                    my %QueueData = $QueueObject->QueueGet( ID => $QueueID );

                    if (
                        !grep { $_ eq $QueueData{ValidID} } @ValidIDs
                        || !$UserGroups{ $QueueData{GroupID} }
                        )
                    {
                        $Error{ $Rule->{RuleID} }->{QueueIDInvalid} = 'ServerError';
                        last QUEUE_ID;
                    }
                }
            }
            else {
                $Error{ $Rule->{RuleID} }->{QueueIDInvalid} = 'ServerError';
            }
        }

        if (%Error) {

            # get selections
            my $GroupSelection     = $Self->_GroupSelectionGet(%GetParam);
            my $ColorPalette       = $Self->_ColorPaletteGet();
            my $ValidSelection     = $Self->_ValidSelectionGet(%GetParam);
            my %TicketAppointments = $Self->_TicketAppointments();

            # get rule count
            my $RuleCount = scalar @{ $GetParam{TicketAppointments} || [] };

            $LayoutObject->Block(
                Name => 'CalendarEdit',
                Data => {
                    %Error,
                    %GetParam,
                    GroupID      => $GroupSelection,
                    ValidID      => $ValidSelection,
                    Subaction    => 'StoreNew',
                    Title        => Translatable('Add new Calendar'),
                    WidgetStatus => $RuleCount ? 'Expanded' : 'Collapsed',
                    %TicketAppointments,
                },
            );

            $LayoutObject->AddJSData(
                Key   => 'CalendarColor',
                Value => $GetParam{Color},
            );

            $LayoutObject->AddJSData(
                Key   => 'CalendarColorPalette',
                Value => $ColorPalette,
            );

            $LayoutObject->AddJSData(
                Key   => 'CalendarRuleCount',
                Value => $RuleCount,
            );

            # Show all ticket appointment rule blocks.
            my $RuleNumber = 1;
            my @RuleIDs;
            for my $Rule ( @{ $GetParam{TicketAppointments} || [] } ) {
                $Rule->{Error} = $Error{ $Rule->{RuleID} };
                my %TicketAppointmentRule = $Self->_TicketAppointments( %{$Rule} );
                $LayoutObject->Block(
                    Name => 'TicketAppointmentRule',
                    Data => {
                        RuleNumber => $RuleNumber++,
                        %{$Rule},
                        %TicketAppointmentRule,
                    },
                );

                # Show any search parameter blocks too.
                $Self->_ShowTicketAppointmentParams( %{$Rule} );

                # Save rule ID for button initialization.
                push @RuleIDs, $Rule->{RuleID};
            }
            $LayoutObject->AddJSData(
                Key   => 'RuleIDs',
                Value => \@RuleIDs,
            );

            $Param{Action} = 'New';

            return $Self->_Mask(%Param);
        }

        # create calendar
        my %Calendar = $CalendarObject->CalendarCreate(
            %GetParam,
            UserID => $Self->{UserID},
        );

        if ( !%Calendar ) {
            return $LayoutObject->ErrorScreen(
                Message => Translatable('System was unable to create Calendar!'),
                Comment => Translatable('Please contact the administrator.'),
            );
        }

        # Process ticket appointments in async call to core module method.
        $Self->AsyncCall(
            ObjectName     => 'Kernel::System::Calendar',
            FunctionName   => 'TicketAppointmentProcessCalendar',
            FunctionParams => {
                CalendarID => $Calendar{CalendarID},
            },
        );

        # If the user would like to continue editing the calendar, just redirect to the edit screen.
        if (
            defined $ParamObject->GetParam( Param => 'ContinueAfterSave' )
            && ( $ParamObject->GetParam( Param => 'ContinueAfterSave' ) eq '1' )
            )
        {
            return $LayoutObject->Redirect(
                OP =>
                    "Action=$Self->{Action};Subaction=Edit;CalendarID=$GetParam{CalendarID}"
            );
        }
        else {

            # Otherwise return to overview.
            return $LayoutObject->Redirect( OP => "Action=AdminAppointmentCalendarManage" );
        }
    }
    elsif ( $Self->{Subaction} eq 'Edit' ) {

        # get data
        my %GetParam;
        $GetParam{CalendarID} = $ParamObject->GetParam( Param => 'CalendarID' ) || '';

        if ( !$GetParam{CalendarID} ) {
            return $LayoutObject->ErrorScreen(
                Message => Translatable('No CalendarID!'),
                Comment => Translatable('Please contact the administrator.'),
            );
        }

        # get calendar data
        my %Calendar = $CalendarObject->CalendarGet(
            CalendarID => $GetParam{CalendarID},
            UserID     => $Self->{UserID},
        );

        if ( !%Calendar ) {

            # fake message
            return $LayoutObject->ErrorScreen(
                Message => Translatable('You have no access to this calendar!'),
                Comment => Translatable('Please contact the administrator.'),
            );
        }

        # get selections
        my $GroupSelection     = $Self->_GroupSelectionGet(%Calendar);
        my $ColorPalette       = $Self->_ColorPaletteGet();
        my $ValidSelection     = $Self->_ValidSelectionGet(%Calendar);
        my %TicketAppointments = $Self->_TicketAppointments();

        my $RuleCount = scalar @{ $Calendar{TicketAppointments} || [] };

        $LayoutObject->Block(
            Name => 'CalendarEdit',
            Data => {
                %Calendar,
                GroupID      => $GroupSelection,
                ValidID      => $ValidSelection,
                Subaction    => 'Update',
                Title        => Translatable('Edit Calendar'),
                WidgetStatus => $RuleCount ? 'Expanded' : 'Collapsed',
                %TicketAppointments,
            },
        );

        $LayoutObject->AddJSData(
            Key   => 'CalendarColor',
            Value => $Calendar{Color},
        );

        $LayoutObject->AddJSData(
            Key   => 'CalendarColorPalette',
            Value => $ColorPalette,
        );

        $LayoutObject->AddJSData(
            Key   => 'CalendarRuleCount',
            Value => $RuleCount,
        );

        # Show all ticket appointment rule blocks.
        my $RuleNumber = 1;
        my @RuleIDs;
        for my $Rule ( @{ $Calendar{TicketAppointments} || [] } ) {
            my %TicketAppointmentRule = $Self->_TicketAppointments( %{$Rule} );
            $LayoutObject->Block(
                Name => 'TicketAppointmentRule',
                Data => {
                    RuleNumber => $RuleNumber++,
                    %{$Rule},
                    %TicketAppointmentRule,
                },
            );

            # Show any search parameter blocks too.
            $Self->_ShowTicketAppointmentParams( %{$Rule} );

            # Save rule ID for button initialization.
            push @RuleIDs, $Rule->{RuleID};
        }
        $LayoutObject->AddJSData(
            Key   => 'RuleIDs',
            Value => \@RuleIDs,
        );

        $Param{Action} = 'Update';
        $Param{Name}   = $Calendar{CalendarName};
    }
    elsif ( $Self->{Subaction} eq 'Update' ) {

        my %Error;

        # check needed stuff
        for my $Needed (qw(CalendarID CalendarName Color GroupID)) {
            if ( !$GetParam{$Needed} ) {
                $Error{ $Needed . 'Invalid' } = 'ServerError';

                return $Self->_Mask( %Param, %GetParam, %Error );
            }
        }

        # check if there is already a calendar with same name
        my %Calendar = $CalendarObject->CalendarGet(
            CalendarName => $GetParam{CalendarName},
            UserID       => $Self->{UserID},
        );

        if ( defined $Calendar{CalendarID} && $Calendar{CalendarID} != $GetParam{CalendarID} ) {
            $Error{CalendarNameInvalid} = "ServerError";
            $Error{CalendarNameExists}  = 1;
        }

        $GetParam{TicketAppointments} = $Self->_GetTicketAppointmentParams(%GetParam);

        # Get queue create permissions for the user.
        my %UserGroups = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet(
            UserID => $Self->{UserID},
            Type   => 'create',
        );

        my @ValidIDs = $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet();

        my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');

        # Queue field in ticket appointments is mandatory, check if it's present and valid.
        for my $Rule ( @{ $GetParam{TicketAppointments} } ) {
            if ( defined $Rule->{QueueID} && IsArrayRefWithData( $Rule->{QueueID} ) ) {

                QUEUE_ID:
                for my $QueueID ( sort @{ $Rule->{QueueID} || [] } ) {
                    my %QueueData = $QueueObject->QueueGet( ID => $QueueID );

                    if (
                        !grep { $_ eq $QueueData{ValidID} } @ValidIDs
                        || !$UserGroups{ $QueueData{GroupID} }
                        )
                    {
                        $Error{ $Rule->{RuleID} }->{QueueIDInvalid} = 'ServerError';
                        last QUEUE_ID;
                    }
                }
            }
            else {
                $Error{ $Rule->{RuleID} }->{QueueIDInvalid} = 'ServerError';
            }
        }

        if (%Error) {

            # get selections
            my $GroupSelection     = $Self->_GroupSelectionGet(%GetParam);
            my $ColorPalette       = $Self->_ColorPaletteGet();
            my $ValidSelection     = $Self->_ValidSelectionGet(%GetParam);
            my %TicketAppointments = $Self->_TicketAppointments();

            my $RuleCount = scalar @{ $GetParam{TicketAppointments} || [] };

            $LayoutObject->Block(
                Name => 'CalendarEdit',
                Data => {
                    %Error,
                    %GetParam,
                    GroupID      => $GroupSelection,
                    ValidID      => $ValidSelection,
                    Subaction    => 'Update',
                    Title        => Translatable('Edit Calendar'),
                    WidgetStatus => $RuleCount ? 'Expanded' : 'Collapsed',
                    %TicketAppointments,
                },
            );

            $LayoutObject->AddJSData(
                Key   => 'CalendarColor',
                Value => $Calendar{Color},
            );

            $LayoutObject->AddJSData(
                Key   => 'CalendarColorPalette',
                Value => $ColorPalette,
            );

            $LayoutObject->AddJSData(
                Key   => 'CalendarRuleCount',
                Value => $RuleCount,
            );

            # Show all ticket appointment rule blocks.
            my $RuleNumber = 1;
            my @RuleIDs;
            for my $Rule ( @{ $GetParam{TicketAppointments} || [] } ) {
                $Rule->{Error} = $Error{ $Rule->{RuleID} };
                my %TicketAppointmentRule = $Self->_TicketAppointments( %{$Rule} );
                $LayoutObject->Block(
                    Name => 'TicketAppointmentRule',
                    Data => {
                        RuleNumber => $RuleNumber++,
                        %{$Rule},
                        %TicketAppointmentRule,
                    },
                );

                # Show any search parameter blocks too.
                $Self->_ShowTicketAppointmentParams( %{$Rule} );

                # Save rule ID for button initialization.
                push @RuleIDs, $Rule->{RuleID};
            }
            $LayoutObject->AddJSData(
                Key   => 'RuleIDs',
                Value => \@RuleIDs,
            );

            $Param{Action} = 'Update';
            $Param{Name}   = $Calendar{CalendarName};

            return $Self->_Mask(%Param);
        }

        # update calendar
        my $Success = $CalendarObject->CalendarUpdate(
            %GetParam,
            UserID => $Self->{UserID},
        );

        if ( !$Success ) {
            return $LayoutObject->ErrorScreen(
                Message => Translatable('Error updating the calendar!'),
                Comment => Translatable('Please contact the administrator.'),
            );
        }

        # Process ticket appointments in async call to core module method.
        $Self->AsyncCall(
            ObjectName     => 'Kernel::System::Calendar',
            FunctionName   => 'TicketAppointmentProcessCalendar',
            FunctionParams => {
                CalendarID => $GetParam{CalendarID},
            },
        );

        # If the user would like to continue editing the calendar, just redirect to the edit screen.
        if (
            defined $ParamObject->GetParam( Param => 'ContinueAfterSave' )
            && ( $ParamObject->GetParam( Param => 'ContinueAfterSave' ) eq '1' )
            )
        {
            return $LayoutObject->Redirect(
                OP =>
                    "Action=$Self->{Action};Subaction=Edit;CalendarID=$GetParam{CalendarID}"
            );
        }
        else {

            # Otherwise return to overview.
            return $LayoutObject->Redirect( OP => "Action=AdminAppointmentCalendarManage" );
        }
    }
    elsif ( $Self->{Subaction} eq 'CalendarImport' ) {

        # challenge token check for write action
        $LayoutObject->ChallengeTokenCheck();

        # get the uploaded file content
        my $FormID      = $ParamObject->GetParam( Param => 'FormID' ) || '';
        my %UploadStuff = $ParamObject->GetUploadAll(
            Param => 'FileUpload',
        );
        my $Content = $UploadStuff{Content};

        # check for overwriting option
        my $OverwriteExistingEntities = $ParamObject->GetParam( Param => 'OverwriteExistingEntities' ) || 0;

        # extract the team data from the uploaded file
        my $CalendarData = $Kernel::OM->Get('Kernel::System::YAML')->Load( Data => $Content );
        if ( ref $CalendarData ne 'HASH' ) {
            return $LayoutObject->ErrorScreen(
                Message => Translatable("Couldn't read calendar configuration file."),
                Comment => Translatable('Please make sure your file is valid.'),
            );
        }

        # import the calendar
        my $Success = $CalendarObject->CalendarImport(
            Data                      => $CalendarData,
            OverwriteExistingEntities => $OverwriteExistingEntities,
            UserID                    => $Self->{UserID},
        );

        if ( !$Success ) {
            $Param{NotifyMessage} = {
                Priority => Translatable('Error'),
                Info     => Translatable('Could not import the calendar!'),
            };
        }
        else {
            $Param{NotifyMessage} = {
                Info => Translatable('Calendar imported!'),
            };
        }

        %Param = $Self->_Overview(%Param);
    }

    elsif ( $Self->{Subaction} eq 'CalendarExport' ) {

        # check for CalendarID
        my $CalendarID = $ParamObject->GetParam( Param => 'CalendarID' ) || '';
        if ( !$CalendarID ) {
            return $LayoutObject->ErrorScreen(
                Message => Translatable('Need CalendarID!'),
            );
        }

        # get calendar data
        my %CalendarData = $CalendarObject->CalendarExport(
            CalendarID => $CalendarID,
            UserID     => $Self->{UserID},
        );

        if ( !IsHashRefWithData( \%CalendarData ) ) {
            return $LayoutObject->ErrorScreen(
                Message => Translatable('Could not retrieve data for given CalendarID'),
            );
        }

        # convert the calendar data hash to string
        my $CalendarDataYAML = $Kernel::OM->Get('Kernel::System::YAML')->Dump( Data => \%CalendarData );

        # prepare calendar name to be part of the filename
        my $CalendarName = $CalendarData{CalendarData}->{CalendarName};
        $CalendarName =~ s/\s+/_/g;

        # send the result to the browser
        return $LayoutObject->Attachment(
            ContentType => 'text/html; charset=' . $LayoutObject->{Charset},
            Content     => $CalendarDataYAML,
            Type        => 'attachment',
            Filename    => 'Export_Calendar_' . $CalendarName . '.yml',
            NoCache     => 1,
        );
    }

    else {

        if ( $ParamObject->GetParam( Param => 'ImportAppointmentsSuccess' ) || '' ) {
            $Param{NotifyMessage} = {
                Info => $LayoutObject->{LanguageObject}->Translate(
                    'Successfully imported %s appointment(s) to calendar %s.',
                    $ParamObject->GetParam( Param => 'Count' ) || 0,
                    $ParamObject->GetParam( Param => 'Name' )  || '',
                ),
            };
        }

        %Param = $Self->_Overview(%Param);
    }

    return $Self->_Mask(%Param);
}

sub _Overview {
    my ( $Self, %Param ) = @_;

    my $CalendarObject = $Kernel::OM->Get('Kernel::System::Calendar');

    # get all calendars user has RW access to
    my @Calendars = $CalendarObject->CalendarList(
        UserID     => $Self->{UserID},
        Permission => 'rw',
    );

    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');

    $LayoutObject->Block(
        Name => 'CalendarFilter',
    );

    $LayoutObject->Block(
        Name => 'Overview',
        Data => {
            Title => Translatable('Calendars'),
        },
    );

    $Param{ValidCount} = 0;
    for my $Calendar (@Calendars) {

        # group name
        $Calendar->{Group} = $Kernel::OM->Get('Kernel::System::Group')->GroupLookup(
            GroupID => $Calendar->{GroupID},
        );

        # valid text
        $Calendar->{Valid} = $Kernel::OM->Get('Kernel::System::Valid')->ValidLookup(
            ValidID => $Calendar->{ValidID},
        );
        if ( $Calendar->{ValidID} == 1 ) {
            $Param{ValidCount}++;
        }

        # get access tokens
        $Calendar->{AccessToken} = $CalendarObject->GetAccessToken(
            CalendarID => $Calendar->{CalendarID},
            UserLogin  => $Self->{UserLogin},
        );

        $LayoutObject->Block(
            Name => 'Calendar',
            Data => {
                %{$Calendar},
            },
        );
    }

    if ( scalar @Calendars == 0 ) {
        $LayoutObject->Block(
            Name => 'CalendarNoDataRow',
        );
    }
    $Param{Overview} = 1;

    $LayoutObject->Block(
        Name => 'MainActions',
        Data => {
            %Param,
        },
    );

    $LayoutObject->Block( Name => 'ActionImport' );

    return %Param;
}

sub _Mask {
    my ( $Self, %Param ) = @_;

    # get needed objects
    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');

    # output page
    my $Output = $LayoutObject->Header();
    $Output .= $LayoutObject->NavigationBar();

    if ( $Param{NotifyMessage} ) {
        $Output .= $LayoutObject->Notify(
            %{ $Param{NotifyMessage} },
        );
    }

    $Output .= $LayoutObject->Output(
        TemplateFile => 'AdminAppointmentCalendarManage',
        Data         => {
            Title => Translatable('Edit Calendar'),
            %Param,
        },
    );
    $Output .= $LayoutObject->Footer();
    return $Output;
}

sub _GroupSelectionGet {
    my ( $Self, %Param ) = @_;

    my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');

    my $HasAdminPermission = $GroupObject->PermissionCheck(
        UserID    => $Self->{UserID},
        GroupName => 'admin',
        Type      => 'rw',
    );

    my %GroupList;
    if ($HasAdminPermission) {

        # Get list of all groups
        %GroupList = $GroupObject->GroupList(
            Valid => 1,
        );
    }
    else {
        # Get list of groups where user has RW permissions
        %GroupList = $GroupObject->PermissionUserGet(
            UserID => $Self->{UserID},
            Type   => 'rw',
        );
    }

    my $GroupSelection = $Kernel::OM->Get('Kernel::Output::HTML::Layout')->BuildSelection(
        Data        => \%GroupList,
        Name        => 'GroupID',
        SelectedID  => $Param{GroupID} || '',
        Translation => 0,
        Class       => 'Modernize Validate_Required',
    );

    return $GroupSelection;
}

sub _ColorPaletteGet {
    my ( $Self, %Param ) = @_;

    # get color palette
    my $CalendarColors = $Kernel::OM->Get('Kernel::Config')->Get('AppointmentCalendar::CalendarColors') || [
        '#000000', '#1E1E1E', '#3A3A3A', '#545453', '#6E6E6E', '#878687', '#888787', '#A09FA0',
        '#B8B8B8', '#D0D0D0', '#E8E8E8', '#FFFFFF', '#891100', '#894800', '#888501', '#458401',
        '#028401', '#018448', '#008688', '#004A88', '#001888', '#491A88', '#891E88', '#891648',
        '#FF2101', '#FF8802', '#FFFA03', '#83F902', '#05F802', '#03F987', '#00FDFF', '#008CFF',
        '#002EFF', '#8931FF', '#FF39FF', '#FF2987', '#FF726E', '#FFCE6E', '#FFFB6D', '#CEFA6E',
        '#68F96E', '#68FDFF', '#68FBD0', '#6ACFFF', '#6E76FF', '#D278FF', '#FF7AFF', '#FF7FD3',
    ];

    return $CalendarColors;
}

sub _ValidSelectionGet {
    my ( $Self, %Param ) = @_;

    my %Valid          = $Kernel::OM->Get('Kernel::System::Valid')->ValidList();
    my $ValidSelection = $Kernel::OM->Get('Kernel::Output::HTML::Layout')->BuildSelection(
        Data       => \%Valid,
        Name       => 'ValidID',
        ID         => 'ValidID',
        Class      => 'Modernize Validate_Required',
        SelectedID => $Param{ValidID} || 1,
        Title      => Translatable("Valid"),
    );

    return $ValidSelection;
}

sub _UserSelectionGet {
    my ( $Self, %Param ) = @_;

    my %UserList = $Kernel::OM->Get('Kernel::System::User')->UserList( Type => 'Long' );

    my $UserSelection = $Kernel::OM->Get('Kernel::Output::HTML::Layout')->BuildSelection(
        Data => \%UserList,
        Name => ( $Param{RuleID} && $Param{ParamName} )
        ? 'SearchParam_' . $Param{RuleID} . '_' . $Param{ParamName}
        : 'SearchParamUser',
        Multiple   => 1,
        Class      => 'SearchParam Modernize Validate_Required',
        SelectedID => $Param{ParamValue} || [],
    );

    # Add param name as data attribute for later reference in JS.
    if ( $Param{ParamName} ) {
        $UserSelection =~ s/<select/<select data-param="$Param{ParamName}"/;
    }

    return $UserSelection;
}

sub _TicketAppointments {
    my ( $Self, %Param ) = @_;

    # get field id suffix
    my $FieldID = '';
    if ( $Param{RuleID} ) {
        $FieldID = '_' . $Param{RuleID};
    }

    my %TicketAppointments;

    # get create permission queues
    my %UserGroups = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet(
        UserID => $Self->{UserID},
        Type   => 'create',
    );

    my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');

    my %Queues = $QueueObject->QueueList();
    my %AvailableQueues;

    # build selection string
    QUEUEID:
    for my $QueueID ( sort keys %Queues ) {
        my %QueueData = $QueueObject->QueueGet( ID => $QueueID );

        # permission check, can we create new tickets in queue
        next QUEUEID if !$UserGroups{ $QueueData{GroupID} };

        $AvailableQueues{$QueueID} = $QueueData{Name};
    }

    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');

    # get list type
    my $TreeView = 0;
    if ( $ConfigObject->Get('Ticket::Frontend::ListType') eq 'tree' ) {
        $TreeView = 1;
    }

    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');

    $TicketAppointments{QueueIDStrg} = $LayoutObject->AgentQueueListOption(
        Class              => 'Validate_Required Modernize ' . ( $Param{Error}->{QueueIDInvalid} // '' ),
        Data               => \%AvailableQueues,
        Multiple           => 1,
        Size               => 0,
        Name               => 'QueueID' . $FieldID,
        TreeView           => $TreeView,
        OnChangeSubmit     => 0,
        SelectedIDRefArray => $Param{QueueID},
    );

    # get ticket appointment types
    my $TicketAppointmentConfig = $ConfigObject->Get('AppointmentCalendar::TicketAppointmentType') // {};
    my @AppointmentTypes;
    my @DynamicFieldTypes;

    TYPE:
    for my $TypeKey ( sort keys %{$TicketAppointmentConfig} ) {
        next TYPE if !$TicketAppointmentConfig->{$TypeKey}->{Key};
        next TYPE if !$TicketAppointmentConfig->{$TypeKey}->{Name};

        if ( $TypeKey =~ /DynamicField$/ ) {
            my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');

            # get list of all valid date and date/time dynamic fields
            my $DynamicFieldList = $DynamicFieldObject->DynamicFieldListGet(
                ObjectType => 'Ticket',
            );

            DYNAMICFIELD:
            for my $DynamicField ( @{$DynamicFieldList} ) {
                if ( $DynamicField->{FieldType} ne 'Date' && $DynamicField->{FieldType} ne 'DateTime' ) {
                    next DYNAMICFIELD;
                }

                push @DynamicFieldTypes, {
                    Key   => sprintf( $TicketAppointmentConfig->{$TypeKey}->{Key},  $DynamicField->{Name} ),
                    Value => sprintf( $TicketAppointmentConfig->{$TypeKey}->{Name}, $DynamicField->{Name} ),
                };
            }

            next TYPE;
        }

        push @AppointmentTypes, {
            Key   => $TicketAppointmentConfig->{$TypeKey}->{Key},
            Value => $TicketAppointmentConfig->{$TypeKey}->{Name},
        };
    }

    my @StartDateTypes = ( @AppointmentTypes, @DynamicFieldTypes );
    $TicketAppointments{StartDateStrg} = $LayoutObject->BuildSelection(
        Class      => 'Modernize',
        Data       => \@StartDateTypes,
        Multiple   => 0,
        Size       => 0,
        Name       => 'StartDate' . $FieldID,
        SelectedID => $Param{StartDate},
    );

    my @EndPlusTypes = (
        {
            Key   => 'Plus_5',
            Value => $LayoutObject->{LanguageObject}->Translate('+5 minutes'),
        },
        {
            Key   => 'Plus_15',
            Value => $LayoutObject->{LanguageObject}->Translate('+15 minutes'),
        },
        {
            Key   => 'Plus_30',
            Value => $LayoutObject->{LanguageObject}->Translate('+30 minutes'),
        },
        {
            Key   => 'Plus_60',
            Value => $LayoutObject->{LanguageObject}->Translate('+1 hour'),
        },
    );

    my @EndDateTypes = ( @EndPlusTypes, @DynamicFieldTypes );
    $TicketAppointments{EndDateStrg} = $LayoutObject->BuildSelection(
        Class      => 'Modernize',
        Data       => \@EndDateTypes,
        Multiple   => 0,
        Size       => 0,
        Name       => 'EndDate' . $FieldID,
        SelectedID => $Param{EndDate},
    );

    my $SearchParamsConfig =
        $ConfigObject->Get('AppointmentCalendar::TicketAppointmentSearchParam') // {};

    # Translate search parameter labels prior to sorting.
    for my $ParamName ( sort keys %{$SearchParamsConfig} ) {
        $SearchParamsConfig->{$ParamName} = $LayoutObject->{LanguageObject}->Translate(
            $SearchParamsConfig->{$ParamName}
        );
    }

    # Sort search parameter list by translated labels.
    my @SearchParams;
    for my $ParamName ( sort { $SearchParamsConfig->{$a} cmp $SearchParamsConfig->{$b} } keys %{$SearchParamsConfig} ) {
        push @SearchParams, {
            Key      => $ParamName,
            Value    => $SearchParamsConfig->{$ParamName},
            Disabled => $Param{SearchParam}->{$ParamName} || 0,
        };
    }

    $TicketAppointments{SearchParamsStrg} = $LayoutObject->BuildSelection(
        Class    => 'SearchParams Modernize',
        Data     => \@SearchParams,
        Multiple => 0,
        Size     => 0,
        Name     => 'SearchParams' . $FieldID,
    );

    $TicketAppointments{SearchParamUser} = $Self->_UserSelectionGet();

    if ( !defined $Param{RuleID} ) {
        $LayoutObject->AddJSData(
            Key   => 'TicketAppointments',
            Value => \%TicketAppointments,
        );
    }

    return %TicketAppointments;
}

sub _GetTicketAppointmentParams {
    my ( $Self, %Param ) = @_;

    # get main object
    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');

    # create data structure
    my %TicketAppointmentParams;
    for my $Key ( sort keys %Param ) {
        for my $Field (qw(StartDate EndDate QueueID SearchParam)) {
            if ( $Key =~ /^${Field}_([A-Za-z0-9]+)/ ) {
                my $RuleID = $1;

                # if rule id is integer, generate random guid
                if ( IsInteger($RuleID) ) {
                    $TicketAppointmentParams{$RuleID}->{RuleID} = $MainObject->GenerateRandomString(
                        Length     => 32,
                        Dictionary => [ 0 .. 9, 'a' .. 'f' ],    # hexadecimal
                    );
                }

                # otherwise, use it as-is
                else {
                    $TicketAppointmentParams{$RuleID}->{RuleID} = $RuleID;
                }
                if ( $Field eq 'SearchParam' ) {
                    if ( $Key =~ /^SearchParam_${RuleID}_([A-Za-z_]+)$/ ) {
                        my $SearchParam = $1;

                        # Store search params:
                        #   - as scalar, per TicketSearch() documentation
                        #   - as array ref, multiple selection
                        #   - as array ref, single value
                        if ( grep { $SearchParam eq $_ } @{ $Self->{SearchParamScalar} } ) {
                            $TicketAppointmentParams{$RuleID}->{SearchParam}->{$SearchParam} = $Param{$Key};
                        }
                        elsif ( ref $Param{$Key} eq 'ARRAY' ) {
                            $TicketAppointmentParams{$RuleID}->{SearchParam}->{$SearchParam} = $Param{$Key};
                        }
                        else {
                            $TicketAppointmentParams{$RuleID}->{SearchParam}->{$SearchParam} = [ $Param{$Key} ];
                        }
                    }
                }
                else {
                    $TicketAppointmentParams{$RuleID}->{$Field} = $Param{$Key};
                }
            }
        }
    }

    # transform to array
    my @TicketAppointmentData;
    for my $RuleID ( sort keys %TicketAppointmentParams ) {
        push @TicketAppointmentData, $TicketAppointmentParams{$RuleID};
    }

    return \@TicketAppointmentData;
}

sub _ShowTicketAppointmentParams {
    my ( $Self, %Param ) = @_;

    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');

    for my $ParamName ( sort keys %{ $Param{SearchParam} } ) {
        my $ParamValue;
        my $ParamStrg;

        if ( grep { $ParamName eq $_ } @{ $Self->{SearchParamScalar} } ) {
            $ParamValue = $Param{SearchParam}->{$ParamName};
        }
        elsif ( ref $Param{SearchParam}->{$ParamName} eq 'ARRAY' ) {
            $ParamValue = $Param{SearchParam}->{$ParamName}->[0] // '';
        }

        # OwnerIDs and ResponsibleIDs are multiple selection user fields.
        if ( $ParamName eq 'OwnerIDs' || $ParamName eq 'ResponsibleIDs' ) {
            $ParamStrg = $Self->_UserSelectionGet(
                ParamName  => $ParamName,
                ParamValue => $Param{SearchParam}->{$ParamName} // [],
                %Param,
            );
        }

        $LayoutObject->Block(
            Name => 'TicketAppointmentRuleSearchParam',
            Data => {
                ParamName  => $ParamName,
                ParamValue => $ParamValue,
                ParamStrg  => $ParamStrg,
                %Param,
            },
        );
    }

    return;
}

1;
