#!/usr/bin/env raku

use AI::Gator;
use AI::Gator::Tools;
use TOML;
use Log::Async <warning color use-args>;
use JSON::Fast;
use Terminal::ANSI::OO 't';
use Terminal::UI;
use HTTP::Tiny;
use Readline;

my $base = %*ENV<XDG_CONFIG_HOME>.?IO // $*HOME.child('.config');
my $*GATOR-HOME = (%*ENV<AI_GATOR_HOME> // $base.child('ai-gator')).IO;
$*GATOR-HOME.d or do {
  warning "$*GATOR-HOME not found, creating it.";
  mkdir $*GATOR-HOME;
}

my $conf-file = $*GATOR-HOME.child('config.toml');
unless $conf-file.IO.e {
  say "Creating default config file $conf-file.";
  say "see https://github.com/bduggan/raku-ai-gator/tree/main?tab=readme-ov-file#configuration-file";
  $conf-file.IO.spurt: q:to/CONF/
  base-uri = "https://api.openai.com/v1"
  model = "gpt-4o"
  CONF
}

my $conf = from-toml($conf-file.IO.slurp) if $conf-file.IO.e;
info "read config from $conf-file";
my $base-uri = $conf<base-uri> // $conf<url>;
without $base-uri {
  info "no base-uri or url specified in config file $conf-file, using OpenAI default";
}
my $session-dir = $*GATOR-HOME.child('sessions');
my $history = $*GATOR-HOME.child('.history').Str;
my $default-tool-dir = $*GATOR-HOME.child('tools');

#| List available tools
multi MAIN('tools', :$*tool-dir = $default-tool-dir) {
  info "looking for tools in $*tool-dir";
  my $tools = get-tools;
  for $tools.map: *<spec><function><name description> -> ( $name, $desc ) {
    say $name ~ ' : ';
    say $desc.trim.indent(5);
  }
}

#| List previous sessions
multi MAIN('sessions') {
  my $session-class = AI::Gator::Session;
  my $sessions = $session-class.list-sessions($session-dir);
  for $sessions.list {
    say ($++).fmt('%3d ')
    ~ DateTime.new(.<timestamp>).yyyy-mm-dd
    ~ ' '
    ~ .<summary>.lines[0].substr(0, 50).fmt('%-50s ');
  }
}

#| Try out a tool in a debug repl
multi MAIN('tool', $name, :$*tool-dir = $default-tool-dir) {
  my $spec = get-tool-spec($name);
  say to-json $spec;
  exit unless $spec;
  my &tool := get-tool($name);
  say "loaded $name as tool.  Try calling &tool";
  repl;
}

sub get-input(:$history) {
  my $p = Readline.new;
  $p.read-history($history) if $history;
  my $all;
  my $prompt = t.green ~ "You:" ~ t.text-reset ~ ' ';
  loop {
    my $line = $p.readline($prompt);
    last unless $line;
    $all ~= $line;
    last unless $line.ends-with('\\' | '+');
    $line = $line.substr(0, *-1).trim; # remove trailing \ or +
  }
  $p.add-history($all) if $history && $all.defined;
  $p.write-history($history) if $history;
  $all;
}

multi MAIN("resume-last") {
  MAIN(:resume);
}

multi MAIN("resume", Int(Str) $session-id!) {
  MAIN(:$session-id, :resume);
}

#| Start an AI Gator session
multi MAIN(
   Str :$model = ($conf<default_model> // $conf<model> // fail("no model specified")), #= The name of the model, e.g. 'gpt-4o'
   Bool :$resume = False, #= Resume the last session
   Int  :$session-id = 0, #= The session number to resume, for another one besides the last one
   Str  :$dir,    #= working directory
   :$*tool-dir = $default-tool-dir, #= directory to load tools from
) {
  my $adapter-conf = 'AI::Gator';
  my $session-class-conf = 'AI::Gator::Session';
  with $conf<adapter> -> $adapter {
    $adapter-conf = "AI::Gator::$adapter";
    $session-class-conf = "AI::Gator::Session::$adapter";
  }
  info "model $model, adapter $adapter-conf";
  info "session class $session-class-conf";
  my $adapter-class = $adapter-conf.EVAL;
  my $session-class = $session-class-conf.EVAL;

  info "making client with base-uri $_" with $base-uri;
  my $client = $adapter-class.new: :$model, tools => get-tools, |%( $base-uri ?? :$base-uri !! Empty);
  my $session = $session-class.new: :$session-dir;

  if $resume {
    if $session-id {
      my $sessions = $session-class.list-sessions($session-dir);
      my $this = $sessions[$session-id] or exit note "No session with id $session-id found.";
      say "Resuming session {t.yellow}{ .<summary> }{t.text-reset} from { .<timestamp> }" with $this;
      $session.load($this<filename>.IO) or exit note "Could not load session { $session-id }";
    } else {
      $session.load-last-session($session-dir) or do {
        warning "No previous session found, starting a new one.";
      }
    }
  }

  say "Type ^D to exit.";
  my $new-messages = 0;
  loop {
    unless $session.last-finish-reason.fc eq 'tool_calls'.fc {
      my $content = get-input(:$history) // last;
      given $content.words[0] {
        when '\\history' {
          my $how = $content.words[1] // '';
          say "Messages in session : { $session.messages.elems }";
          $session.messages.map: {
            my $text = .<content> // (.<parts>.map( { .<text> } ).join(" "));
            unless $text {
              say t.yellow ~ '[empty]' ~ t.text-reset;
              next;
            }
            if $how eq 'mine' {
              next unless .<role> eq 'user';
            }
            if .<role> eq 'tool' {
              if $how ne 'all' {
                say t.yellow ~ '[tool]' ~ t.text-reset;
                next;
              }
            }
            say t.yellow ~ (.<role> // '' ) ~ t.text-reset ~ ': ' ~ $text;
          }

          redo;
        }
        when '\\inspect' {
          inspect-session($session);
          redo;
        }
        default {
           $session.add-message($content);
           $new-messages++;
        }
      }
    }

    my $response = $client.get-response($session);

    $client.do-tool-calls($session); 
    debug "done with tool calls and response is { $response.raku }";
    $response ||= $client.get-response($session);
    $session.add-message: :role<assistant>, $response if $response;
  }
  $session.save(summary => $client.summarize($session)) if $session.messages > 3 && $new-messages > 0;
} 

sub inspect-session(AI::Gator::Session $session) {
  my $ui = Terminal::UI.new;
  $ui.setup(panes => 2);
  my $top = $ui.panes[0];
  $session.messages.map: {
    my $text is default('') = .<content> // ( .<parts>.map( { .<text> // .raku } ).join(" ") );
    $top.put: [
      t.color('#ddff00') => .<role>.fmt('%20s '),
      t.color('#ffffff') => $text.lines[0]
    ], meta => %( message => $_ );
  }
  my $btm = $ui.panes[1];
  $btm.auto-scroll = False;
  $top.on: select => -> :%meta {
    $btm.clear;
    my $msg = %meta<message>;
    my $text = $msg.<content> // ( $msg.<parts>.map( { .<text> // .raku } ).join(" ") );
    for $text.lines -> $line {
      $btm.put: $line, wrap => 'hard';
    }
  }
  $ui.interact;
  $ui.shutdown;
}

