use v6.d;
use Test;

# use lib <. lib>;
use LLM::Tooling;
use JSON::Fast;

plan *;

## 1
subtest {
    #| Fight mechanics.
    sub duel(
            Str:D $a, #= First magician
            Str:D $b, #= Second magician
            --> Int
             ) {
        return (rand > 0.5) + 1;
    }
    #= Magicians only, no mortals.

    isa-ok LLM::Tool.new(&duel), LLM::Tool:D, 'expected type';

    isa-ok LLM::Tool.new(&duel).info, Map:D, 'info is retrievable';

    isa-ok LLM::Tool.new(&duel).json-spec, Str:D, 'spec is retrievable and a string';

    isa-ok from-json(LLM::Tool.new(&duel).json-spec), Map:D, 'spec is a valid JSON';

}, 'Magician duel sub';

## 2
subtest {
    my %info =
            name => 'charscount',
            description => 'number of characters',
            parameters => %('$x' => %(type => 'string', description => 'a string')),
            required => ['$x',];

    isa-ok
            LLM::Tool.new(%info, -> Str:D $x { $x.chars} ),
            LLM::Tool:D,
            'expected type for creation on the spot';

    my $tool = LLM::Tool.new(%info, -> Str:D $x { $x.chars} );

    is $tool.gist, 'LLMTool(charscount, number of characters)', 'expected tool gist';

    isa-ok $tool.json-spec, Str:D, 'json-spec is retrievable and a string';

    isa-ok
            LLM::ToolRequest.new('charscount', %('$x' => 'more raccoons!')),
            LLM::ToolRequest:D,
            'expected request creation type';

    my $request = LLM::ToolRequest.new('charscount', %('$x' => 'more raccoons!'));

    is $request.gist, 'LLMToolRequest(charscount, :$x(more raccoons!), :id(Whatever))', 'expected request gist';

    isa-ok
            generate-llm-tool-response($tool, $request),
            LLM::ToolResponse:D,
            'expected generation with tools result';

    my $res = generate-llm-tool-response($tool, $request);

    is $res.output, 'more raccoons!'.chars, 'expected output';

}, 'String characters counting (simple one argument pure function)';


## 3
subtest {
    #| Another predator duel.
    sub duel(
            Str:D $p,                 #= Predator name
            Str:D $y,                 #= Yakuza name
            Int:D :$n = 5,            #= Number of attacks
            Numeric:D :$prob = 0.8,   #= Probability the predator to win
            Int:D :$film-year = 2010, #= Film year
            --> Str:D
             ) {
        return "The predator $p {rand ≤ $prob ?? 'wins over' !! 'loses to'} $y after $n attacks.";
    }

    isa-ok
            LLM::Tool.new(&duel),
            LLM::Tool:D,
            'expected type for creation on the spot';

    my $tool = LLM::Tool.new(&duel);

    isa-ok $tool.json-spec, Str:D, 'json-spec is retrievable';

    #note (spec => $tool.spec);

    isa-ok
            LLM::ToolRequest.new('duel', {'$y' => 'Hanzo', '$p' => 'Falconer', '$n' => 3, '$prob' => 1}),
            LLM::ToolRequest:D,
            'expected request creation type';

    my $request = LLM::ToolRequest.new('duel', {'$y' => 'Hanzo', '$p' => 'Falconer', '$n' => 3, '$prob' => 1}),

    isa-ok
            generate-llm-tool-response($tool, $request),
            LLM::ToolResponse:D,
            'expected generation with tools result';

    my $res = generate-llm-tool-response($tool, $request);

    note $res.output;
    is $res.output, 'The predator Falconer wins over Hanzo after 3 attacks.', 'expected output';

    is $res.Hash.keys.sort, <output params request tool>, "Expeted .Hash keys";
    isa-ok $res.Hash(Whatever), Hash:D, "OpenAI style hashmap";
    isa-ok $res.Hash('OpenAI'), Hash:D, "OpenAI style hashmap";
    isa-ok $res.Hash('Gemini'), Hash:D, "OpenAI style hashmap";

    note $res.Hash('Gemini');

    is $res.Hash('OpenAI').keys.sort, <content role tool_call_id>, "OpenAI style hashmap keys";
    is $res.Hash('Gemini').keys, <functionResponse>, "Gemini style hashmap keys";

}, 'Two positional arguments and three named arguments sub';

## 4
subtest {
    my %info =
            :name("add"),
            :description("\nAdd two numbers.\n\nArgs:\n    a: First number.\n    b: Second number.\n"),
            :parameters({
                :a({:description(""), :named(Bool::True), :type("string")}),
                :b({:description(""), :named(Bool::True), :type("string")}),
                additionalProperties => %()
            }),
            :required(["a", "b"]);

    my &func =  -> *@args, *%args {
        note "LLM::Tool::arguments: ", (:@args, :%args);
        @args.sum
    };

    isa-ok LLM::Tool.new(%info, &func), LLM::Tool, 'additionalProperties => %()';

    %info<parameters><additionalProperties> = False;

    isa-ok LLM::Tool.new(%info, &func), LLM::Tool, 'additionalProperties => False';

    %info<parameters> = {:!additionalProperties};
    %info<required> = [];

    isa-ok LLM::Tool.new(%info, &func), LLM::Tool, 'just :!additionalProperties';

}, 'Creation with additionalProperties';

done-testing;
