package LIMS::Model::Outreach;

use Moose;

with (
	'LIMS::Model::Roles::DBIxSimple',
	'LIMS::Model::Roles::RequestUpdate', # do_history_log
);
extends 'LIMS::Model::Base';
use namespace::clean -except => 'meta';

has actions => (
    is         => 'ro',
    isa        => 'ArrayRef[Str]',
    default    => sub { [] },
    lazy    => 1,
	traits  => ['Array'],
	handles => {
		add_to_actions => 'push',
		all_actions    => 'elements',
	},
);
__PACKAGE__->meta->make_immutable;

use LIMS::Local::Utils;
use Lingua::EN::Words2Nums;
use Data::Dumper;

#-------------------------------------------------------------------------------
sub get_all_data {
    my ($self, $args) = @_; # warn $request_id;
    
    my $request_id = $args->{request_id};
    my $patient_id = $args->{patient_id}; # warn Dumper $patient_id;
    
    my $dbix = $self->lims_dbix;
    
    # get request object for request_date for data summary section:
    my $request = LIMS::DB::Request->new(id => $request_id)->load;

    my %data = ();    

    { # first get list of all departments, lab_tests, defaults & ranges:
        my $sql = $self->sql_lib->retr('outreach_lab_params');
        my $query = $dbix->query($sql);
        
        # get list of cols from defaults_and_ranges table:
        my $meta = $self->get_meta('outreach.defaults_and_ranges'); # warn Dumper $meta;
        my @cols = grep {
            $meta->{$_}->{key} ne 'PRI' # skip primary key
        } keys %$meta; # warn Dumper \@cols;        

        while ( my $vars = $query->hash ) {
            my $param = $vars->{param_name};
            my $dept  = $vars->{description};
            
            my %params = (
                field_label => $vars->{field_label},
                field_type  => $vars->{field_type},
                result      => undef, # initialise placeholder
            );
            
            $data{$dept}{$param} = \%params;
            
            # add any (optional) default & ranges cols:
            map {
                $data{$dept}{$param}{$_} = $vars->{$_};
            } grep $vars->{$_}, @cols;
        }        
    }
    { # add lab_test results:
        my $sql = $self->sql_lib->retr('outreach_lab_results');
        my $query = $dbix->query($sql, $request_id);
        
        # get epoch value of current request for data summary:
        my $epoch = $request->created_at->epoch;
        
        while ( my $vals = $query->hash ) {
            my $result = $vals->{result};
            my $param  = $vals->{param_name};
            my $dept   = $vals->{description};
            
            # for individual sections (haem, immunol, etc):
            $data{$dept}{$param}{result} = $result;
            # for data summary - using chart instead now:
            # $data{datasets}{$epoch}{$param}{result} = $result;
        }
    }
    { # add patient demographics data:
        my $sql = $self->sql_lib->retr('outreach_patient_demographics');
        my $demographics = $dbix->query($sql, $patient_id)->hash;
        $data{demographics} = $demographics;        
    }
    { # questionnaire:
        my $sql = $self->sql_lib->retr('outreach_patient_questionnaire');
        my $data = $dbix->query($sql, $request_id)->hash;
        $data{questionnaire} = $data;        
    }
    { # questionnaire details:
        my $sql = $self->sql_lib->retr('outreach_questionnaire_details');
        my $data = $dbix->query($sql, $request_id)->hashes; # array of hashes

        for (@$data) {
            my $field = $_->{field_name};
            $data{questionnaire}{details}{$field} = $_->{details};
        }       
    }
    { # followup data:
        my $follow_up = $self->get_followup_data($request_id);
        $data{followup} = $follow_up;        
    }
    { # patient notes:
        my $sql = $self->sql_lib->retr('outreach_patient_notes');
        my $data = $dbix->query($sql, $patient_id)->list;
        $data{demographics}{patient_notes} = $data;
    }
    # get GP's for practice:
    if ( my $practice_id = $data{demographics}{practice_id} ) {
        my $GPs = $self->_get_practitioners($practice_id);
        $data{demographics}{practitioners} = $GPs;
    }
    { # get unknown practitioner id:
        my $o = LIMS::DB::ReferralType->new(description => 'practitioner')->load;
        my $ref
            = LIMS::DB::Referrer->new(national_code => $o->default_unknown)->load;
        $data{demographics}{unknown_gp_id} = $ref->id;
    }
    { # non-participating practices:
        my $sql = 'select practice_id, 1 from outreach.non_participant_practice';
        my $ids = $dbix->query($sql)->map;
        $data{demographics}{non_participant_practices} = $ids;        
    }
    { # get menu options:
        my $opts = $self->get_menu_options; # warn Dumper $opts;
        $data{menu_options} = $opts; 
    }
    
    return \%data;
}

#-------------------------------------------------------------------------------
sub get_menu_options { # get menu options - shared with C::Outreach::dfv_err()
    my $self = shift;    
    
    my $dbix = $self->lims_dbix;

    my $sql = $self->sql_lib->retr('outreach_menu_options');
    my $query = $dbix->query($sql);
    
    my %opts;
    while ( my $vars = $query->array ) {
        my ($field_name, $detail) = @$vars;
        push @{ $opts{$field_name} }, $detail;
    }
        
    { # add follow_up options:
        my $followup_options = $self->get_followup_options;
        $opts{followup_options} = $followup_options;
    }

    return \%opts; # warn Dumper \%opts;
}

#-------------------------------------------------------------------------------
sub get_chart_results {
    my ($self, $patient_id, $fields) = @_; # scalar, arrayref
    
    my $dbix = $self->lims_dbix; 

    my $sql = $self->sql_lib->retr('outreach_request_results');
    # can't use ?? placeholders in sql as already using 1 placeholder:
    $sql =~ s/%PARAMS%/join ',', map $dbix->dbh->quote($_), @$fields/e;

    my $data = $dbix->query( $sql, $patient_id )->hashes; # warn Dumper $data;   
    return $data;
}

#-------------------------------------------------------------------------------
sub update_patient_questionnaire {
    my ($self, $args) = @_; # warn Dumper $args;
    
    my $request_id = $args->{request_id};
    
	if ( $args->{imperial_stones} && ! $args->{weight} ) { # 'imperial_pounds' optional
		$args->{weight} = LIMS::Local::Utils::convert_imperial($args);
	} # warn Dumper $args;

    my @tbls = qw(
        outreach_questionnaire_adenopathy
        outreach_questionnaire_treatment
        outreach_questionnaire_symptoms
        outreach_questionnaire_service
        outreach_questionnaire_pain
    );

    # create hash of corresponding classes for above tables (*in same order*):
    my %class_for_tbl; @class_for_tbl{@tbls} = qw(
        QuestionnaireAdenopathy QuestionnaireTreatment QuestionnaireSymptoms
        QuestionnaireService    QuestionnairePain   
    );
    
    # get existing questionnaire data for this request:
    my $data = LIMS::DB::Request->new(id => $request_id)
        ->load( with => \@tbls ); # warn Dumper $data;

    # create hashref of $data object to avoid 2nd db query when testing for accessors:
    my $h = $data->as_tree;
    
    # take list of tables, if accessor exists, update it, otherwise create new:
    for my $tbl (@tbls) { # eg outreach_questionnaire_pain
        if ( $h->{$tbl} ) { # exists so update it:
            my $o = $data->$tbl; # get object eg $data->outreach_questionnaire_pain
            my @cols = grep $_ ne 'request_id', $o->meta->column_names;
            COL: for (@cols) {
                next COL if $o->$_ eq $args->{$_}; # skip unchanged
                $o->$_($args->{$_});
            }
        }
        else { # doesn't exists so insert new:
            my $class = 'LIMS::DB::Outreach::'.$class_for_tbl{$tbl}; # eg QuestionnairePain
            # get cols from meta data:
            my @cols = $class->new->meta->column_names; # warn Dumper \@cols;
            my %data = map +($_ => $args->{$_}), @cols;
            $data->$tbl(%data);
        }
    }

	my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;
    
    my $update = sub {
        # update/insert all tables:
        for my $tbl (@tbls) {
            $data->$tbl->save(changes_only => 1);
        }        
        # $self->do_history_log($args); # just needs _request_id attr
        die 'rollback now';
    };
    
	my $ok = $db->do_transaction($update);
    
	# don't need return value unless error:
    return $ok ? 0 : 'update_patient_questionnaire() error - ' . $db->error;    
}

#-------------------------------------------------------------------------------
sub update_followup {
    my ($self, $args) = @_; # warn Dumper $args;
    
    my $option_id  = $args->{followup_option_id}; # required
    my $request_id = $args->{_request_id};        # required
    my $return_due = $args->{return_due};         # optional
    
    # get follow-up options table data:
    my $options_map = $self->get_followup_options; # warn Dumper $options;
    
    # map of followup option name => duration (months):
    my $followup_durations = $self->_followup_durations($options_map);
    
    # get existing data for this request:
    my @tbls = qw( outreach_request_followup outreach_request_pack_dispatch );
    my $data = LIMS::DB::Request->new(id => $request_id)
        ->load( with => \@tbls ); # warn Dumper $data->as_tree;

    # set null date to 1/1/2000: 
    my $null_date = LIMS::Local::Utils::to_datetime_using_datecalc('1/1/2000');
    
    # 1) where follow-up option has changed:
    if ( $option_id != $data->outreach_request_followup->followup_option_id ) {
        # get value of new duration (will be undef if not a duration choice):
        my $followup_option_name = $options_map->{$option_id}->{option}; # eg six_month
        my $new_duration = $followup_durations->{$followup_option_name}; # eg 6
        
        # where new follow-up option is a duration (including '0'):
        if ( defined $new_duration ) { # warn Dumper $new_duration;
            # calculate new pack due date - add new_duration to today:
            my $pack_due_date = $self->time_now->add( months => $new_duration );
            
            # if existing entry in request_pack_dispatch table, update:
            if ( $data->outreach_request_pack_dispatch ) {
                $data->outreach_request_pack_dispatch->pack_due($pack_due_date);
                # ?? may need to update return due date if pack already sent
            }
            else { # else add a new request_pack_dispatch row:
                $data->outreach_request_pack_dispatch(
                    request_id => $request_id,
                    pack_due   => $pack_due_date,
                );
            }
        }
        # new follow-up not a duration - check existing pack dispatch:
        elsif ( my $pack_dispatch = $data->outreach_request_pack_dispatch ) {
            # set request_pack_dispatch dates to null:
            # $pack_dispatch->pack_due($null_date); # probably not necessary
            if ($pack_dispatch->return_due) {
                $pack_dispatch->return_due($null_date->ymd);
            }
        }
        
        { # log follow-up option change:
            my $original_option_id
                = $data->outreach_request_followup->followup_option_id;
            $self->add_to_actions('changed outreach follow-up option from ' .
                $options_map->{$original_option_id}->{label});
        }
        
        # register the follow-up option change (AFTER changing pack_due date):
        $data->outreach_request_followup->followup_option_id($option_id);
    }
    
    # 2) elsif return_due date submitted (and follow-up option NOT changed):
    elsif ($return_due) {
        my $old_date = $data->outreach_request_pack_dispatch->return_due;
        my $new_date = LIMS::Local::Utils::to_datetime_using_datecalc($return_due);
        
        if ( $new_date->delta_days($old_date)->delta_days ) { # date changed:
            $data->outreach_request_pack_dispatch->return_due($new_date);
            # and log date change
            $self->add_to_actions('changed pack return date from ' . $old_date->dmy);
        }
    }

	my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;
    
    my $update = sub {
        $data->outreach_request_pack_dispatch->save(changes_only => 1);
        $data->outreach_request_followup->save(changes_only => 1);
        
        $self->do_history_log($args); # just needs _request_id attr
        die 'rollback now';
    };
    
	my $ok = $db->do_transaction($update);
    
	# don't need return value unless error:
    return $ok ? 0 : 'update_followup() error - ' . $db->error;
}

#-------------------------------------------------------------------------------
sub update_patient_notes {
    my ($self, $args) = @_;
    
    my $dbix = $self->lims_dbix;

    my $tbl = 'outreach.patient_notes';
    
    my $patient_id = $args->{patient_id};
    my $form_param = $args->{detail};    

	my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;

	my $update = sub {
        my $sql = qq!select `detail` from $tbl where patient_id = ?!;
        if ( $dbix->query($sql, $patient_id)->into(my $detail) ) { # update or delete:
            if ($form_param) { # update:
                return 0 if $form_param eq $detail; # skip if no change
                $dbix->update($tbl,
                    { detail => $form_param },
                    { patient_id => $patient_id }
                );
                $self->add_to_actions('updated patient notes');
            }
            else { # delete:
                $dbix->delete($tbl, { patient_id => $patient_id });
                $self->add_to_actions('deleted patient notes');
            }
        }
        else { # new insert:
            $dbix->insert($tbl,
                { detail => $form_param, patient_id => $patient_id }
            );
            $self->add_to_actions('added new patient notes');
        }
		$self->do_history($patient_id);
    };
    
	my $ok = $db->do_transaction($update);
	
	# don't need return value unless error:
    return $ok ? 0 : 'update_patient_notes() error - ' . $db->error;    
}

#-------------------------------------------------------------------------------
sub update_dispatch_detail {
    my ($self, $args) = @_;
    
    my $patient_id  = $args->{patient_id};
    my $dispatch_to = $args->{dispatch_to};
    
	my $user_id = $self->user_profile->{id};

	my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;

    my $update = sub {
        my $o = LIMS::DB::Outreach::PatientDispatchDetail
            ->new(patient_id => $patient_id);
        # do patient demographic history if dispatch_to changed:
        if ( $o->load_speculative && $o->dispatch_to ne $args->{dispatch_to} ) {
			my $action = sprintf q!updated 'dispatch_to' from '%s'!, $o->dispatch_to;
			my %data = (
				patient_id => $patient_id,
				user_id    => $user_id,
				action 	   => $action,
			);
            LIMS::DB::PatientDemographicHistory->new(%data)->save;
        }
        $o->dispatch_to($dispatch_to);
        $o->save(changes_only => 1); # doesn't do changes_only
    };
    
	my $ok = $db->do_transaction($update);
	
	# don't need return value unless error:
    return $ok ? 0 : 'update_dispatch_detail() error - ' . $db->error;    
}

#-------------------------------------------------------------------------------
sub get_overdue_packs {
    my $self = shift;
    
    my $dbix = $self->lims_dbix;
    
    # get nhs_numbers of all request_ids where return_date < today:
    my $sql = $self->sql_lib->retr('outreach_overdue_packs_one');
    my $query = $dbix->query($sql);

    # query to find if follow-up sample registered after pack_sent date:
    my $follow_up = $self->sql_lib->retr('outreach_overdue_packs_two');
    
    my @overdue;
    
    my $today = LIMS::Local::Utils::time_now();

    REQ:
    while ( my $row = $query->hash ) { # nhs_number, pack_sent
        # skip if follow-up sample received after pack_sent date: 
        next REQ if $dbix->query( $follow_up,
            $row->{nhs_number}, $row->{pack_sent} )->list; # warn Dumper $row;
        
        # get details for overdue request:
        my $o = LIMS::DB::Request->new(id => $row->{request_id})
            ->load( with => 'patient_case.patient' );
        my $data = $o->as_tree(deflate => 0); # preserve DateTime
        # add pack_sent date:
        $data->{pack_sent} = $row->{pack_sent};
        $self->inflate_mysql_dates_to_datetime($data, ['pack_sent']);
        
        { # calculate delta_days (from pack_dispatch to current_date):
            my $delta = $data->{pack_sent}->delta_days($today)->delta_days;
            $data->{delta_days} = $delta;
        }
        
        push @overdue, $data;
    }
    
    return \@overdue;
}

#-------------------------------------------------------------------------------
sub get_packs_due_details {
    my $self = shift;
    
    my $dbix = $self->lims_dbix;    

    my $sql = $self->sql_lib->retr('outreach_packs_due_details');
    my $query = $dbix->query($sql);
    
    my @packs;
    
    while ( my $row = $query->hash ) {
        $self->inflate_mysql_dates_to_datetime($row, ['dob']);
        # re-arrange GP name:
        $row->{practitioner} = join ' ', reverse split ' ', $row->{practitioner};
        push @packs, $row;
    }
    
    return \@packs;
}

#-------------------------------------------------------------------------------
sub get_packs_due_summary {
    my $self = shift;
    
    my $dbix = $self->lims_dbix;    

    my $sql = $self->sql_lib->retr('outreach_packs_due_summary');
    my $query = $dbix->query($sql);
    
    my @packs;
    
    while ( my $row = $query->hash ) {
        $self->inflate_mysql_dates_to_datetime($row, ['pack_due']);
        $self->inflate_mysql_timestamp_to_datetime($row, ['created_at']);
        push @packs, $row;
    }
    
    return \@packs;
}

#-------------------------------------------------------------------------------
sub pack_labels {
    my $self = shift;
    my $dbix = $self->lims_dbix;    

    my $sql = $self->sql_lib->retr('outreach_pack_labels');
    
    my $data = $dbix->query($sql)->hashes;
    return $data;
}

#-------------------------------------------------------------------------------
sub report_labels {
    my ($self, $patient_ids) = @_; # arrayref of request_ids
    
    my $dbix = $self->lims_dbix;    

    my $sql = $self->sql_lib->retr('outreach_report_labels');
    
    my $data = $dbix->query($sql, @$patient_ids)->hashes;
    return $data;
}

#-------------------------------------------------------------------------------
sub get_outreach_practices {
    my $self = shift;
    
    my $dbix = $self->lims_dbix;    

    my $sql = $self->sql_lib->retr('outreach_practices');
    
    my $practices = $dbix->query($sql)->hashes;
    return $practices;
}

#-------------------------------------------------------------------------------
sub get_practice_blood_tube {
    my ($self, $practice_id) = @_;
    
    my $data
        = LIMS::DB::ReferralSource->new(id => $practice_id )
        ->load(with => 'outreach_practice_blood_tube');
    return $data;
}

#-------------------------------------------------------------------------------
sub update_practice_blood_tube {
    my ($self, $args) = @_;
    
    my $practice_id = $args->{practice_id};
    my $tube_type   = $args->{tube_type}; # optional
    
    my $o = LIMS::DB::Outreach::PracticeBloodTube->new(practice_id => $practice_id);
    
    # if row exists, only valid action is to delete:
    if ( $o->load(speculative => 1) ) {
        $o->delete;
    }
    elsif ($tube_type) { # insert new entry:
        $o->tube_type($tube_type);
        $o->save;
    }        

    return 0;  # or could return error, but will already propagate 
}

#-------------------------------------------------------------------------------
sub update_alternate_address {
    my $self = shift;
    my $data = shift; # warn Dumper $data;
    
    my $patient_id = $data->{patient_id};
    my $post_code  = $data->{post_code};
    my $address    = $data->{address};
    
    my $o = LIMS::DB::Outreach::PatientAlternateAddress
        ->new(patient_id => $patient_id)->load(speculative => 1);
    $o->address($address);
    $o->post_code($post_code);
    
    $o->save(changes_only => 1); # doesn't do changes_only, but ok as only 2 cols

    return 0;  # or could return error, but will already propagate 
}

#-------------------------------------------------------------------------------
sub get_packs_due_future {
    my $self = shift;
    
    my $dbix = $self->lims_dbix;    

    my $sql = $self->sql_lib->retr('outreach_packs_due_summary');
    # change pack_due <= CURRENT_DATE() to > CURRENT_DATE():
    $sql =~ s/<= (CURRENT_DATE)/> $1/; # warn $sql;    
    
    my @packs;
    
    my $today = LIMS::Local::Utils::time_now();

    my $query = $dbix->query($sql);
    while ( my $row = $query->hash ) {
        $self->inflate_mysql_dates_to_datetime($row, ['pack_due']);
        $self->inflate_mysql_timestamp_to_datetime($row, ['created_at']);
        { # calculate delta_days (from current_date to pack_dispatch):
            my $delta = $today->delta_days($row->{pack_due})->delta_days;
            $row->{delta_days} = $delta;
        }
        push @packs, $row;
    }
    
    return \@packs;
}

#-------------------------------------------------------------------------------
sub do_pack_dispatch {
    my ($self, $request_ids) = @_; # arrayref

	my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;
    
    my $today = LIMS::Local::Utils::time_now();
    
    my $update = sub {
        for my $id(@$request_ids) {
            my $o = LIMS::DB::Outreach::RequestPackDispatch
                ->new(request_id => $id)->load;
            $o->pack_sent($today->ymd);
            $o->return_due( $today->add(months => 1)->ymd );
            $o->save(changes_only => 1);
        }
    };
    
	my $ok = $db->do_transaction($update);
    
	# don't need return value unless error:
    return $ok ? 0 : 'do_pack_dispatch() error - ' . $db->error;
}

#-------------------------------------------------------------------------------
sub get_authorised_cases_data {
    my $self = shift;
    my $date = shift; # warn Dumper $date; # hashref of date_from & date_to DateTimes
    
    my $dbix = $self->lims_dbix; 

    my $sql = $self->sql_lib->retr('outreach_authorised_between_dates');
    
    my @date_fields = qw(created_at authorised); # for inflation to DateTimes
    
    my $date_from = $date->{begin}->ymd;
    # make date_to extend to end of day - effectively 00:00:00 on following day:
    my $date_to = $date->{end}->clone->add(days => 1)->ymd; # clone, or affects tmpl
    
    my $query = $dbix->query( $sql, $date_from, $date_to ); # warn Dumper $query;   
    
    my @data;    
    while ( my $row = $query->hash ) {
        $self->inflate_mysql_timestamp_to_datetime($row, \@date_fields);
        push @data, $row; 
    }
    
    return \@data;    
}

#-------------------------------------------------------------------------------
# method shared by get_all_data & C::Outreach::edit_followup:
sub get_followup_data {
    my ($self, $request_id) = @_;
    
    my $dbix = $self->lims_dbix;

    my $sql = $self->sql_lib->retr('outreach_followup_data');
    my $data = $dbix->query($sql, $request_id)->hash;

	my $meta = $self->get_meta('outreach.request_pack_dispatch'); # warn Dumper $meta;
	my @date_fields = grep { $meta->{$_}->{type} eq 'date' } keys %$meta; # warn Dumper \@date_cols;

    $self->inflate_mysql_dates_to_datetime($data, \@date_fields);
    
    return $data;
}

#-------------------------------------------------------------------------------
# method shared by get_all_data & C::Outreach::edit_followup:
sub get_followup_options {
    my $self = shift;
    
    my $dbix = $self->lims_dbix;

    my $sql = 'select `id`, `label`, `option` from outreach.followup_options';
    my $options = $dbix->query($sql)->map_hashes('id');
    
    return $options;
}

#-------------------------------------------------------------------------------
sub get_diagnosis_ids {
    my $self = shift;
    
    my $dbix = $self->lims_dbix;
    
    my $ids = $dbix->query('select diagnosis_id from outreach.diagnoses')->flat;
    return $ids;    
}

#-------------------------------------------------------------------------------
sub do_history {
	my ($self, $patient_id) = @_;
	
	my @actions = $self->all_actions;
	
	my $dbix = $self->lims_dbix;

	my $user_id = $self->user_profile->{id};
	
	for my $action(@actions) {
		my %data = (
			patient_id => $patient_id,
			user_id    => $user_id,
			action 	   => $action,
		); # warn Dumper \%data;
		$dbix->insert('some_table', \%data); # request_history
	}	
}

#-------------------------------------------------------------------------------
sub _followup_durations {
    my $self = shift;
    my $opts = shift;
    
    # get list of 'option' col:
    my @follow_up_opts = map $opts->{$_}->{option}, keys %$opts;
    # warn Dumper \@follow_up_opts;
    
    my %map;
    for my $option (@follow_up_opts) {
        if ( $option =~ /(\w+)_month/ ) {
            my $num = words2nums($1); # warn Dumper $num;
            $map{$option} = $num if defined $num; # could be zero
        }
    } # warn Dumper \%map;
    
    return \%map;
}

#-------------------------------------------------------------------------------
sub _get_practitioners { # from M::Referrer::get_referrers_by_source_id
    my ($self, $source_id) = @_;
    
    my @args = (
        query => [ 'referral_sources.id' => $source_id ],
        require_objects => [
            'referrer',
            'parent_organisation.referral_source'
        ],
    );

    # use ReferrerDepartment - easier to retrieve data from object:
    my $referrers = LIMS::DB::ReferrerDepartment::Manager
        ->get_referrer_department(@args);

    my @data = map { # warn Dumper $_->as_tree;
        [ $_->referrer->id, $_->referrer->name ];
    } sort { $a->referrer->name cmp $b->referrer->name } @$referrers;
    
    return \@data;
}

1;