#!/usr/bin/env raku
use v6.d;

use Chatnik;
use JSON::Fast;

#-----------------------------------------------------------

sub get-n-range(%args, $elems) {
    my ($min, $max) = 0, $elems - 1;
    if (%args<n>:exists) && %args<n> ~~ Int:D {
        my $n = %args<n>;
        if $n >= 0 { $max = $n - 1 } else { $min = $elems + $n}
    }
    return {:$min, :$max};
}

#-----------------------------------------------------------

sub cannot-find(Str:D $chat-id) {
    say $chat-id ?? "Cannot find chat object $chat-id." !! 'No chat ID is specified'
}

#-----------------------------------------------------------

my %*SUB-MAIN-OPTS = :named-anywhere;

# Very likely a separate USAGE definition has to be done. For example:
# Int:D :$n = -1,  #= For the commands "clear" and "messages" specifies the number of messages: positive number means first $n, negative last $n. Ignored with --all.
# Ind:D :$index = -1, #= For the command "message" $n is a reference index.

#| Meta processing of persistent LLM-chat objects.
multi sub MAIN(
        Str:D $command,                    #= Command, one of: card, clear, context, delete, file, first-message, last-message, list, load-llm-personas, message, messages.
        Str:D :i(:id(:$chat-id)) = 'NONE', #= Chat id; ignored if --all is specified.
        Bool:D :$all = False,              #= Whether to apply the command to all chat objects or not.
        *%args,                            #= Additional, optional arguments for the commands: clear, list, message, messages.
         ) {

    my $simple = False;
    given $command {
        when $_.lc ∈ <file filename file-name> {
            $simple = True;
            say Chatnik::get-chat-objects-file-name
        }
        when $_.lc ∈ <delete drop> && $all {
            $simple = True;
            Chatnik::export-chats({});
            say 'Deleted all chat objects.'
        }
        when $_.lc ∈ <load-llm-personas load-personas> {
            $simple = True;
            my %chats;
            if Chatnik::get-chat-objects-file-name.IO.f {
                %chats = from-json(slurp(Chatnik::get-chat-objects-file-name));
            }
            my %predefined-chats = Chatnik::load-llm-personas;
            my @common-ids = (%chats.keys (&) %predefined-chats.keys).keys;
            if @common-ids {
                note "Overwriting chat objects with identifiers: <{@common-ids.sort.join(' ')}>.";
            }
            %chats = %chats , %predefined-chats;
            Chatnik::export-chats(%chats);
            say "Ingested {%predefined-chats.elems} chat objects with identifiers: <{%predefined-chats.keys.sort.join(' ')}>."
        }
    }

    exit(0) if $simple;

    if !Chatnik::get-chat-objects-file-name.IO.f {
        say 'No chat objects file.';
        exit(0)
    }

    my %chats = from-json(slurp(Chatnik::get-chat-objects-file-name));

    given $command {
        when $_.lc ∈ <list personas chats chat-objects chatobjects> {

            if %chats {
                my @ds = do for %chats.kv -> $k, %v {
                    %(chat-id => $k, context => %v<llm-evaluator><context>, messages => %v<messages>.elems, llm-configuration => %v<llm-evaluator><conf>.Hash.grep(*.key ∈ <name model>).Hash)
                }
                if %args<format> && %args<format>.Str.lc eq 'json' {
                    say to-json(@ds)
                } else {
                    .say for @ds
                }
            } else {
                say 'No chat objects.'
            }
        }
        when $_.lc ∈ <delete drop> {
            if %chats{$chat-id}:exists {
                %chats{$chat-id}:delete;
                spurt(Chatnik::get-chat-objects-file-name, to-json(%chats));
                say "Deleted chat object $chat-id."
            } else {
                cannot-find($chat-id)
            }
        }
        when $_.lc ∈ <context prompt> && !$all {
            if %chats{$chat-id}:exists {
                my $ctxt = Chatnik::to-chat-object(%chats{$chat-id}).llm-evaluator.context;
                say ($ctxt ~~ Str:D && $ctxt.trim ?? $ctxt !! "Empty chat object context.")
            } else {
                cannot-find($chat-id)
            }
        }
        when $_.lc ∈ <card form table-form> && !$all {
            if %chats{$chat-id}:exists {
                say Chatnik::to-chat-object(%chats{$chat-id}).form
            } else {
                cannot-find($chat-id)
            }
        }
        when $_.lc eq 'messages' && $all {
            for %chats.kv -> $k, %v {
                say '=' x 60;
                say "Chat ID: $k";
                say '-' x 60;
                say %v<messages>
            }
        }
        when $_.lc eq 'messages' {
            if %chats{$chat-id}:exists {
                my @messages = |%chats{$chat-id}<messages>;
                my ($min, $max) = |get-n-range(%args, @messages.elems)<min max>;
                for @messages.kv -> $k, %v {
                    say "$k : {to-json(%v)}" if $min ≤ $k ≤ $max
                }
                #say Chatnik::to-chat-object(%chats{$chat-id}).form.
            } else {
                cannot-find($chat-id)
            }
        }
        when $_.lc ∈ <first-message firstmessage last-message lastmessage> && !$all {
            if %chats{$chat-id}:exists {
                if %chats{$chat-id}<messages> {
                    my $m = $_ ~~ /^ first / ?? %chats{$chat-id}<messages>.head !! %chats{$chat-id}<messages>.tail;
                    say $m<content> // $m;
                } else {
                    say "The chat object $chat-id has no messages."
                }
            } else {
                cannot-find($chat-id)
            }
        }
        when $_.lc ∈ <message take-message takemessage> && !$all {
            if %chats{$chat-id}:exists {
                if (%args<index>:exists) && %args<index> ~~ Int:D {
                    my $index = %args<index>;
                    my $m = $index >= 0 ?? %chats{$chat-id}<messages>[$index] !! %chats{$chat-id}<messages>[*+$index];
                    say $m<content> // $m;
                } else {
                    say 'The message index is not specfied; use --index.'
                }
            } else {
                cannot-find($chat-id)
            }
        }
        when $_.lc ∈ <clear clear-messages clearmessages> && $all {
            for %chats.kv -> $k, %v {
                %v<messages> = []
            }
            spurt(Chatnik::get-chat-objects-file-name, to-json(%chats));
        }
        when $_.lc ∈ <clear clear-messages clearmessages> {
            if %chats{$chat-id}:exists {
                my $ins = 'messages ';
                if %args<n>:exists {
                    my @messages = |%chats{$chat-id}<messages>;
                    my ($min, $max) = |get-n-range(%args, @messages.elems)<min max>;
                    $ins = $min == $max ?? "message $min " !! "messages from $min to $max ";
                    @messages.splice($min, $max - $min + 1);
                    %chats{$chat-id}<messages> = |@messages
                } else {
                    %chats{$chat-id}<messages> = [];
                }
                spurt(Chatnik::get-chat-objects-file-name, to-json(%chats));
                say "Cleared the {$ins}of chat object $chat-id."
            } else {
                cannot-find($chat-id)
            }
        }
        default {
            say 'Do not know how to process the given command.'
        }
    }
}