// Written by retoor@molodetz.nl

// This code defines functions for HTTP requests and structuring tool
// descriptions and executions in JSON format using the JSON-C library. It uses
// cURL for HTTP requests.

// Imports not part of the language itself: JSON-C (json-c/json.h,
// json-c/json_object.h) and a custom header "http_curl.h"

// MIT License

#ifndef R_TOOLS_H
#define R_TOOLS_H

#include "http_curl.h"
#include "r.h"
#include "utils.h"
#include <dirent.h>
#include <errno.h>
#include <glob.h>
#include <json-c/json.h>
#include <json-c/json_object.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#include "browse.h"
#include "db_utils.h"
#include "indexer.h"
#include "r.h"

struct json_object *tool_description_http_get();
struct json_object *tool_description_linux_terminal();
struct json_object *tool_description_directory_glob();
struct json_object *tool_description_read_file();
struct json_object *tool_description_write_file();
struct json_object *tool_description_directory_rglob();
struct json_object *tool_description_linux_terminal_interactive();
struct json_object *tool_description_index_source_directory();
struct json_object *tool_description_chdir();
struct json_object *tool_description_getpwd();
struct json_object *tool_description_db_set();
struct json_object *tool_description_db_query();
struct json_object *tool_description_db_get();
struct json_object *tool_description_web_search_news();
struct json_object *tool_description_web_search();
struct json_object *tool_description_mkdir();

struct json_object *tools_descriptions() {
  struct json_object *root = json_object_new_array();
  json_object_array_add(root, tool_description_http_get());
  json_object_array_add(root, tool_description_linux_terminal());
  json_object_array_add(root, tool_description_directory_glob());
  json_object_array_add(root, tool_description_read_file());
  json_object_array_add(root, tool_description_write_file());
  json_object_array_add(root, tool_description_directory_rglob());
  json_object_array_add(root, tool_description_linux_terminal_interactive());
  json_object_array_add(root, tool_description_index_source_directory());
  json_object_array_add(root, tool_description_chdir());
  json_object_array_add(root, tool_description_getpwd());
  json_object_array_add(root, tool_description_db_set());
  json_object_array_add(root, tool_description_db_query());
  json_object_array_add(root, tool_description_db_get());
  json_object_array_add(root, tool_description_web_search_news());
  json_object_array_add(root, tool_description_web_search());
  json_object_array_add(root, tool_description_mkdir());
  return root;
}

char *tool_function_web_search_news(char *query) {
  if (query == NULL) {
    return strdup("Query cannot be NULL.");
  }

  char *result = web_search_news(query);
  if (result == NULL) {
    return strdup("Failed to fetch news.");
  }

  return result;
}

struct json_object *tool_description_web_search_news() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name",
                         json_object_new_string("web_search_news"));
  json_object_object_add(
      function, "description",
      json_object_new_string("Searches for news articles based on a query."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();
  struct json_object *query = json_object_new_object();
  json_object_object_add(query, "type", json_object_new_string("string"));
  json_object_object_add(
      query, "description",
      json_object_new_string(
          "The url encoded query string to search for news articles."));
  json_object_object_add(properties, "query", query);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("query"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

char *tool_function_web_search(char *query) {
  if (query == NULL) {
    return strdup("Query cannot be NULL.");
  }

  char *result = web_search_news(query);
  if (result == NULL) {
    return strdup("Failed to fetch news.");
  }

  return result;
}

struct json_object *tool_description_web_search() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name",
                         json_object_new_string("web_search"));
  json_object_object_add(
      function, "description",
      json_object_new_string("Searches for information based on a query using "
                             "search engines like google."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();
  struct json_object *query = json_object_new_object();
  json_object_object_add(query, "type", json_object_new_string("string"));
  json_object_object_add(
      query, "description",
      json_object_new_string(
          "The url encoded query string to search for information."));
  json_object_object_add(properties, "query", query);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("query"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

char *tool_function_db_get(char *key) {
  json_object *result = db_get(key);
  if (result == NULL) {
    return strdup("Failed to retrieve value from the database.");
  }

  char *response = strdup(json_object_to_json_string(result));
  json_object_put(result); // Free the json_object
  return response;
}

struct json_object *tool_description_db_get() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name", json_object_new_string("db_get"));
  json_object_object_add(
      function, "description",
      json_object_new_string(
          "Retrieves a value from the database for a given key."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();

  struct json_object *key = json_object_new_object();
  json_object_object_add(key, "type", json_object_new_string("string"));
  json_object_object_add(
      key, "description",
      json_object_new_string("The key to retrieve from the database."));
  json_object_object_add(properties, "key", key);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("key"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

char *tool_function_db_query(char *query) {
  json_object *result = db_query(query);
  if (result == NULL) {
    return strdup("Failed to execute query on the database.");
  }

  char *response = strdup(json_object_to_json_string(result));

  json_object_put(result); // Free the json_object
  return response;
}
struct json_object *tool_description_db_query() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name", json_object_new_string("db_query"));
  json_object_object_add(
      function, "description",
      json_object_new_string(
          "Executes a query on the database and returns the results."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();

  struct json_object *query = json_object_new_object();
  json_object_object_add(query, "type", json_object_new_string("string"));
  json_object_object_add(query, "description",
                         json_object_new_string("The SQL query to execute."));
  json_object_object_add(properties, "query", query);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("query"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

char *tool_function_db_set(char *key, char *value) {
  json_object *result = db_set(key, value);
  if (result == NULL) {
    return strdup("Failed to set value in the database.");
  }

  const char *response = json_object_get_string(result);
  json_object_put(result); // Free the json_object
  return strdup(response);
}
struct json_object *tool_description_db_set() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name", json_object_new_string("db_set"));
  json_object_object_add(
      function, "description",
      json_object_new_string("Sets a value in the database for a given key."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();

  struct json_object *key = json_object_new_object();
  json_object_object_add(key, "type", json_object_new_string("string"));
  json_object_object_add(
      key, "description",
      json_object_new_string("The key to set in the database."));
  json_object_object_add(properties, "key", key);

  struct json_object *value = json_object_new_object();
  json_object_object_add(value, "type", json_object_new_string("string"));
  json_object_object_add(
      value, "description",
      json_object_new_string("The value to set for the given key."));
  json_object_object_add(properties, "value", value);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("key"));
  json_object_array_add(required, json_object_new_string("value"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

char *tool_function_http_get(char *url) { return curl_get(url); }

char *tool_function_chdir(char *path) {
  if (chdir(path) != 0) {
    perror("chdir failed");
    return strdup("Failed to change directory!");
  }

  return strdup("Directory successfully changed.");
}

char *tool_function_linux_terminal(char *command) {
  FILE *fp;
  char buffer[1024];
  size_t total_size = 0;
  char *output = NULL;

  char stderr_command[1024] = {0};

  if (!strstr(command, "2>")) {
    snprintf(stderr_command, sizeof(stderr_command), "%s 2>&1", command);
    command = stderr_command;
  }

  fp = popen(command, "r");
  if (fp == NULL) {
    perror("popen failed");
    return strdup("Popen failed!");
  }

  while (fgets(buffer, sizeof(buffer), fp) != NULL) {
    size_t chunk_size = strlen(buffer);
    char *new_output = realloc(output, total_size + chunk_size + 1);
    if (new_output == NULL) {
      perror("realloc failed");
      free(output);
      pclose(fp);
      return strdup("Failed to allocate memory!");
    }
    output = new_output;
    if (is_verbose)
      fprintf(stderr, "%s", buffer);
    strcpy(output + total_size, buffer);
    total_size += chunk_size;
  }

  pclose(fp);
  return output ? output : strdup("");
}

char *tool_function_linux_terminal_interactive(char *command) {
  int result_code = system(command);

  char *result = malloc(100);
  result[0] = 0;
  sprintf(result, "Command exited with status code %d.", result_code);

  return result;
}

char *tool_function_getpwd() {
  char *cwd = (char *)malloc(PATH_MAX);
  if (cwd == NULL) {
    perror("Memory allocation failed");
    return strdup("Failed to allocate memory for current working directory!");
  }

  if (getcwd(cwd, PATH_MAX) == NULL) {
    free(cwd);
    perror("getcwd failed");
    return strdup("Failed to get current working directory!");
  }

  return cwd;
}

struct json_object *tool_description_getpwd() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name", json_object_new_string("getpwd"));
  json_object_object_add(
      function, "description",
      json_object_new_string(
          "Returns the current working directory as a string."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  json_object_object_add(function, "parameters", json_object_new_null());

  json_object_object_add(root, "function", function);

  return root;
}

struct json_object *tool_description_chdir() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name", json_object_new_string("chdir"));
  json_object_object_add(
      function, "description",
      json_object_new_string(
          "Changes the current working directory to the specified path. Call "
          "this function when `cd` is prompted."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();
  struct json_object *path = json_object_new_object();
  json_object_object_add(path, "type", json_object_new_string("string"));
  json_object_object_add(
      path, "description",
      json_object_new_string(
          "Path to change the current working directory to."));
  json_object_object_add(properties, "path", path);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("path"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

struct json_object *tool_description_index_source_directory() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name",
                         json_object_new_string("index_source_directory"));
  json_object_object_add(
      function, "description",
      json_object_new_string("Returns a JSON array containing every source "
                             "file with it's contents from given directory. "
                             "Execute with '.' for current directory."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();
  struct json_object *path = json_object_new_object();
  json_object_object_add(path, "type", json_object_new_string("string"));
  json_object_object_add(
      path, "description",
      json_object_new_string("Path to index and retreive files from."));
  json_object_object_add(properties, "path", path);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("path"));
  json_object_object_add(parameters, "required", required);
  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

struct json_object *tool_description_linux_terminal_interactive() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(
      function, "name",
      json_object_new_string("linux_terminal_execute_interactive"));
  json_object_object_add(
      function, "description",
      json_object_new_string(
          "Executes interactive terminal for user. You will not be able to "
          "read the result. Do not use if you need to know output."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();
  struct json_object *path = json_object_new_object();
  json_object_object_add(path, "type", json_object_new_string("string"));
  json_object_object_add(
      path, "description",
      json_object_new_string(
          "Executable with parameters to execute interactively."));
  json_object_object_add(properties, "command", path);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("command"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

struct json_object *tool_description_directory_rglob() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name",
                         json_object_new_string("directory_rglob"));
  json_object_object_add(
      function, "description",
      json_object_new_string(
          "Recursively list the contents of a specified directory in glob "
          "format. "
          "Result is a JSON array containing objects with keys: name, "
          "modification_date(iso), creation_date(iso), type, and size_bytes."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();
  struct json_object *directory = json_object_new_object();
  json_object_object_add(directory, "type", json_object_new_string("string"));
  json_object_object_add(
      directory, "description",
      json_object_new_string("Path to the directory to list in glob format."));
  json_object_object_add(properties, "path", directory);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("path"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

struct json_object *tool_description_read_file() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name", json_object_new_string("read_file"));
  json_object_object_add(
      function, "description",
      json_object_new_string("Reads / opens / loads a file and returns its "
                             "contents as a string."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();
  struct json_object *path = json_object_new_object();
  json_object_object_add(path, "type", json_object_new_string("string"));
  json_object_object_add(
      path, "description",
      json_object_new_string("Path to the file to read / open / load."));
  json_object_object_add(properties, "path", path);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("path"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

struct json_object *tool_description_write_file() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name",
                         json_object_new_string("write_file"));
  json_object_object_add(
      function, "description",
      json_object_new_string(
          "Writes / saves / stores content to a file. Content should be in "
          "plain format, not json encoded."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();

  struct json_object *path = json_object_new_object();
  json_object_object_add(path, "type", json_object_new_string("string"));
  json_object_object_add(
      path, "description",
      json_object_new_string(
          "Path to the plain file to write / save / store."));
  json_object_object_add(properties, "path", path);

  struct json_object *content = json_object_new_object();
  json_object_object_add(content, "type", json_object_new_string("string"));
  json_object_object_add(
      content, "description",
      json_object_new_string(
          "Plain content to write / save / store into the file."));
  json_object_object_add(properties, "content", content);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("path"));
  json_object_array_add(required, json_object_new_string("content"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

char *tool_function_index_source_directory(char *path) {
  return index_directory(path);
}

char *tool_function_mkdir(char *path);

void ensure_parent_directory_exists(char *path_including_file_name) {
  if (!path_including_file_name)
    return;

  char *path = strdup(path_including_file_name);
  char *last_slash = strrchr(path, '/');
  if (last_slash != NULL) {
    *last_slash = '\0';
    free(tool_function_mkdir(path));
  }
  free(path);
}

char *tool_function_read_file(char *path) {

  char *expanded_path = expand_home_directory(path);
  FILE *fp = fopen(expanded_path, "r");
  free(expanded_path);
  if (fp == NULL) {
    perror("fopen failed");
    return strdup("Failed to open file for reading!");
  }

  fseek(fp, 0, SEEK_END);
  long size = ftell(fp);
  rewind(fp);

  char *content = (char *)malloc(size + 1);
  if (content == NULL) {
    fclose(fp);
    return strdup("Memory allocation failed!");
  }

  ssize_t read_size = fread(content, 1, size, fp);

  content[read_size] = '\0';
  fclose(fp);

  return content;
}

char *tool_function_write_file(char *path, char *content) {
  db_store_file_version(path);
  ensure_parent_directory_exists(path);
  FILE *fp = fopen(path, "w+");
  if (fp == NULL) {
    perror("fopen failed");
    return strdup("Failed to open file for writing!");
  }

  fwrite(content, 1, strlen(content), fp);
  fclose(fp);

  return strdup("File successfully written.");
}

const char *get_file_type(struct stat *st) {
  if (S_ISREG(st->st_mode))
    return "file";
  if (S_ISDIR(st->st_mode))
    return "directory";
  return "other";
}

void format_time(time_t raw_time, char *buffer, size_t size) {
  struct tm *time_info = localtime(&raw_time);
  strftime(buffer, size, "%Y-%m-%d %H:%M:%S", time_info);
}

void recursive_glob(const char *pattern, glob_t *results) {
  glob_t current_matches;
  int ret = glob(pattern, GLOB_NOSORT | GLOB_TILDE, NULL, &current_matches);

  if (ret != 0 && ret != GLOB_NOMATCH) {
    results->gl_pathc = 0;
    results->gl_pathv = NULL;
    results->gl_offs = 0;
    return;
  }

  if (results->gl_pathv == NULL) {
    memset(results, 0, sizeof(glob_t));
  }

  for (size_t i = 0; i < current_matches.gl_pathc; i++) {
    char *path = current_matches.gl_pathv[i];

    if (results->gl_pathc == 0) {
      results->gl_pathc = 1;
      results->gl_pathv = malloc(sizeof(char *));
      results->gl_pathv[0] = strdup(path);
    } else {
      results->gl_pathc++;
      results->gl_pathv =
          realloc(results->gl_pathv, results->gl_pathc * sizeof(char *));
      results->gl_pathv[results->gl_pathc - 1] = strdup(path);
    }
  }

  DIR *dir;
  struct dirent *entry;
  struct stat statbuf;

  char *pattern_copy = strdup(pattern);
  char *last_slash = strrchr(pattern_copy, '/');
  char *dir_path;
  char *file_pattern;

  if (last_slash) {
    *last_slash = '\0';
    dir_path = pattern_copy;
    file_pattern = last_slash + 1;
  } else {
    dir_path = ".";
    file_pattern = pattern_copy;
  }

  if ((dir = opendir(dir_path)) != NULL) {
    while ((entry = readdir(dir)) != NULL) {
      if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
        continue;
      }

      char full_path[PATH_MAX];
      int path_len;

      if (strcmp(dir_path, ".") == 0) {
        path_len = snprintf(full_path, PATH_MAX, "%s", entry->d_name);
      } else {
        path_len =
            snprintf(full_path, PATH_MAX, "%s/%s", dir_path, entry->d_name);
      }

      if (path_len >= PATH_MAX) {
        continue;
      }

      if (stat(full_path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
        char new_pattern[PATH_MAX];
        int new_pattern_len =
            snprintf(new_pattern, PATH_MAX, "%s/%s", full_path, file_pattern);

        if (new_pattern_len >= PATH_MAX) {
          continue;
        }

        recursive_glob(new_pattern, results);
      }
    }
    closedir(dir);
  }

  free(pattern_copy);
  globfree(&current_matches);
}

char *tool_function_directory_rglob(char *target_dir) {
  glob_t results;
  results.gl_pathc = 0;
  struct stat file_stat;
  char mod_time[20], create_time[20];

  if (!strcmp(target_dir, ".")) {
    target_dir[0] = '*';
  }

  recursive_glob(target_dir, &results);

  json_object *json_array = json_object_new_array();

  for (size_t i = 0; i < results.gl_pathc; i++) {
    const char *file_path = results.gl_pathv[i];

    if (stat(file_path, &file_stat) == -1) {
      perror("stat failed");
      continue;
    }

    format_time(file_stat.st_mtime, mod_time, sizeof(mod_time));
    format_time(file_stat.st_ctime, create_time, sizeof(create_time));

    json_object *json_entry = json_object_new_object();
    json_object_object_add(json_entry, "name",
                           json_object_new_string(file_path));
    json_object_object_add(json_entry, "modification_date",
                           json_object_new_string(mod_time));
    json_object_object_add(json_entry, "creation_date",
                           json_object_new_string(create_time));
    json_object_object_add(json_entry, "type",
                           json_object_new_string(get_file_type(&file_stat)));
    json_object_object_add(json_entry, "size_bytes",
                           json_object_new_int64(file_stat.st_size));

    json_object_array_add(json_array, json_entry);
  }

  globfree(&results);
  char *result = strdup(
      json_object_to_json_string_ext(json_array, JSON_C_TO_STRING_PRETTY));

  json_object_put(json_array);

  return result;
}

char *tool_function_directory_glob(char *target_dir) {
  glob_t results;
  struct stat file_stat;
  char mod_time[20], create_time[20];

  if (!strcmp(target_dir, ".")) {
    target_dir[0] = '*';
  }

  if (glob(target_dir, GLOB_TILDE, NULL, &results) != 0) {
    perror("glob failed");
    return strdup("");
  }

  json_object *json_array = json_object_new_array();

  for (size_t i = 0; i < results.gl_pathc; i++) {
    const char *file_path = results.gl_pathv[i];

    if (stat(file_path, &file_stat) == -1) {
      perror("stat failed");
      continue;
    }

    format_time(file_stat.st_mtime, mod_time, sizeof(mod_time));
    format_time(file_stat.st_ctime, create_time, sizeof(create_time));

    json_object *json_entry = json_object_new_object();
    json_object_object_add(json_entry, "name",
                           json_object_new_string(file_path));
    json_object_object_add(json_entry, "modification_date",
                           json_object_new_string(mod_time));
    json_object_object_add(json_entry, "creation_date",
                           json_object_new_string(create_time));
    json_object_object_add(json_entry, "type",
                           json_object_new_string(get_file_type(&file_stat)));
    json_object_object_add(json_entry, "size_bytes",
                           json_object_new_int64(file_stat.st_size));

    json_object_array_add(json_array, json_entry);
  }

  globfree(&results);
  char *result = strdup(
      json_object_to_json_string_ext(json_array, JSON_C_TO_STRING_PRETTY));

  json_object_put(json_array);

  return result;
}

struct json_object *tool_description_http_get() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name",
                         json_object_new_string("http_fetch"));
  json_object_object_add(function, "description",
                         json_object_new_string("Get the contents of a URL."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();
  struct json_object *url = json_object_new_object();
  json_object_object_add(url, "type", json_object_new_string("string"));
  json_object_object_add(url, "description",
                         json_object_new_string("Fetch URL contents."));
  json_object_object_add(properties, "url", url);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("url"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);
  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

struct json_object *tool_description_directory_glob() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name",
                         json_object_new_string("directory_glob"));
  json_object_object_add(
      function, "description",
      json_object_new_string(
          "List the contents of a specified directory in glob format. "
          "Result is a JSON array containing objects with keys: name, "
          "modification_date(iso), creation_date(iso), type, and size_bytes."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();
  struct json_object *directory = json_object_new_object();
  json_object_object_add(directory, "type", json_object_new_string("string"));
  json_object_object_add(
      directory, "description",
      json_object_new_string("Path to the directory to list in glob format."));
  json_object_object_add(properties, "path", directory);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("path"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

struct json_object *tool_description_linux_terminal() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name",
                         json_object_new_string("linux_terminal_execute"));
  json_object_object_add(
      function, "description",
      json_object_new_string(
          "Execute a linux_terminal command on user terminal."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();
  struct json_object *url = json_object_new_object();
  json_object_object_add(url, "type", json_object_new_string("string"));
  json_object_object_add(url, "description",
                         json_object_new_string("Bash command to execute."));
  json_object_object_add(properties, "command", url);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("command"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

char *tool_function_mkdir(char *path) {
  char temp[2048];
  char *p = NULL;
  size_t len;

  snprintf(temp, sizeof(temp), "%s", path);
  len = strlen(temp);

  if (temp[len - 1] == '/') {
    temp[len - 1] = '\0';
  }

  for (p = temp + 1; *p; p++) {
    if (*p == '/') {
      *p = '\0';
      if (mkdir(temp, 0777) != 0 && errno != EEXIST) {
        return strdup("Failed to create directory!");
      }
      *p = '/';
    }
  }
  return strdup("Directory successfully created.");
}

struct json_object *tool_description_mkdir() {
  struct json_object *root = json_object_new_object();
  json_object_object_add(root, "type", json_object_new_string("function"));

  struct json_object *function = json_object_new_object();
  json_object_object_add(function, "name", json_object_new_string("mkdir"));
  json_object_object_add(
      function, "description",
      json_object_new_string(
          "Creates a new directory with the specified path."));

  struct json_object *parameters = json_object_new_object();
  json_object_object_add(parameters, "type", json_object_new_string("object"));

  struct json_object *properties = json_object_new_object();
  struct json_object *path = json_object_new_object();
  json_object_object_add(path, "type", json_object_new_string("string"));
  json_object_object_add(
      path, "description",
      json_object_new_string("Path of the directory to create."));
  json_object_object_add(properties, "path", path);

  json_object_object_add(parameters, "properties", properties);

  struct json_object *required = json_object_new_array();
  json_object_array_add(required, json_object_new_string("path"));
  json_object_object_add(parameters, "required", required);

  json_object_object_add(parameters, "additionalProperties",
                         json_object_new_boolean(0));

  json_object_object_add(function, "parameters", parameters);

  if (get_use_strict())
    json_object_object_add(function, "strict",
                           json_object_new_boolean(get_use_strict()));

  json_object_object_add(root, "function", function);

  return root;
}

struct json_object *tools_execute(struct json_object *tools_array) {
  struct json_object *tools_result_messages = json_object_new_array();
  int array_len = json_object_array_length(tools_array);

  for (int i = 0; i < array_len; i++) {
    struct json_object *obj = json_object_array_get_idx(tools_array, i);
    struct json_object *tool_result = json_object_new_object();

    json_object_object_add(tool_result, "tool_call_id",
                           json_object_new_string(json_object_get_string(
                               json_object_object_get(obj, "id"))));
    json_object_object_add(tool_result, "role", json_object_new_string("tool"));

    struct json_object *type_obj;
    if (json_object_object_get_ex(obj, "type", &type_obj)) {
      if (strcmp(json_object_get_string(type_obj), "function")) {
        continue;
      }
    }

    struct json_object *function_obj;
    if (json_object_object_get_ex(obj, "function", &function_obj)) {
      struct json_object *name_obj;
      char *function_name = NULL;
      if (json_object_object_get_ex(function_obj, "name", &name_obj)) {
        function_name = (char *)json_object_get_string(name_obj);
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));

          if (is_verbose)
            fprintf(stderr, "Executing function %s with arguments %s\n",
                    function_name, json_object_to_json_string(arguments));
        }
      }

      if (!strcmp(function_name, "linux_terminal_execute")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *url_obj;
          if (json_object_object_get_ex(arguments, "command", &url_obj)) {
            char *command = (char *)json_object_get_string(url_obj);
            char *terminal_result = tool_function_linux_terminal(command);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(terminal_result));
            free(terminal_result);
          }
        }
      } else if (!strcmp(function_name, "linux_terminal_execute_interactive")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *url_obj;
          if (json_object_object_get_ex(arguments, "command", &url_obj)) {
            char *command = (char *)json_object_get_string(url_obj);
            char *terminal_result =
                tool_function_linux_terminal_interactive(command);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(terminal_result));
            free(terminal_result);
          }
        }
      } else if (!strcmp(function_name, "chdir")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *path_obj;
          if (json_object_object_get_ex(arguments, "path", &path_obj)) {
            char *path = (char *)json_object_get_string(path_obj);
            char *chdir_result = tool_function_chdir(path);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(chdir_result));
            free(chdir_result);
          }
        }
      } else if (!strcmp(function_name, "directory_glob")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *path_obj;
          if (json_object_object_get_ex(arguments, "path", &path_obj)) {
            char *path = (char *)json_object_get_string(path_obj);
            char *listing_result = tool_function_directory_glob(path);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(listing_result));
            free(listing_result);
          }
        }
      } else if (!strcmp(function_name, "http_fetch")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *url_obj;
          if (json_object_object_get_ex(arguments, "url", &url_obj)) {
            char *url = (char *)json_object_get_string(url_obj);
            char *http_result = tool_function_http_get(url);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(http_result));
            free(http_result);
          }
        }
      } else if (!strcmp(function_name, "read_file")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *path_obj;

          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));

          if (json_object_object_get_ex(arguments, "path", &path_obj)) {
            char *path = (char *)json_object_get_string(path_obj);
            char *file_content = tool_function_read_file(path);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(file_content));
            free(file_content);
          }
        }
      } else if (!strcmp(function_name, "write_file")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *path_obj, *content_obj;

          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));

          if (json_object_object_get_ex(arguments, "path", &path_obj) &&
              json_object_object_get_ex(arguments, "content", &content_obj)) {
            char *path = (char *)json_object_get_string(path_obj);
            char *content = (char *)json_object_get_string(content_obj);
            char *write_result = tool_function_write_file(path, content);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(write_result));
            free(write_result);
          }
        }
      } else if (!strcmp(function_name, "db_query")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *query_obj;

          if (json_object_object_get_ex(arguments, "query", &query_obj)) {
            char *query = (char *)json_object_get_string(query_obj);
            char *db_query_result = tool_function_db_query(query);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(db_query_result));
            ;
            if (db_query_result != NULL)
              free(db_query_result);
          }
        }
      } else if (!strcmp(function_name, "db_set")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *key_obj;
          struct json_object *value_obj;

          if (json_object_object_get_ex(arguments, "key", &key_obj) &&
              json_object_object_get_ex(arguments, "value", &value_obj)) {
            char *key = (char *)json_object_get_string(key_obj);
            char *value = (char *)json_object_get_string(value_obj);
            char *db_set_result = tool_function_db_set(key, value);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(db_set_result));
            free(db_set_result);
          }
        }

      } else if (!strcmp(function_name, "index_source_directory")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *path_obj;
          if (json_object_object_get_ex(arguments, "path", &path_obj)) {
            char *path = (char *)json_object_get_string(path_obj);
            char *listing_result = tool_function_index_source_directory(path);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(listing_result));
            free(listing_result);
          }
        }
      } else if (!strcmp(function_name, "web_search_news")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *query_obj;
          if (json_object_object_get_ex(arguments, "query", &query_obj)) {
            char *query = (char *)json_object_get_string(query_obj);
            char *news_result = tool_function_web_search_news(query);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(news_result));
            free(news_result);
          }
        }
      } else if (!strcmp(function_name, "web_search")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *query_obj;
          if (json_object_object_get_ex(arguments, "query", &query_obj)) {
            char *query = (char *)json_object_get_string(query_obj);
            char *news_result = tool_function_web_search(query);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(news_result));
            free(news_result);
          }
        }
      } else if (!strcmp(function_name, "db_get")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *key_obj;

          if (json_object_object_get_ex(arguments, "key", &key_obj)) {
            char *key = (char *)json_object_get_string(key_obj);
            char *db_get_result = tool_function_db_get(key);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(db_get_result));
            free(db_get_result);
          }
        }
      } else if (!strcmp(function_name, "getpwd")) {
        char *pwd_result = tool_function_getpwd();
        json_object_object_add(tool_result, "content",
                               json_object_new_string(pwd_result));
        free(pwd_result);
      } else if (!strcmp(function_name, "directory_rglob")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *path_obj;
          if (json_object_object_get_ex(arguments, "path", &path_obj)) {
            char *path = (char *)json_object_get_string(path_obj);
            char *listing_result = tool_function_directory_rglob(path);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(listing_result));
            free(listing_result);
          }
        }
      } else if (!strcmp(function_name, "mkdir")) {
        struct json_object *arguments_obj;
        if (json_object_object_get_ex(function_obj, "arguments",
                                      &arguments_obj)) {
          struct json_object *arguments =
              json_tokener_parse(json_object_get_string(arguments_obj));
          struct json_object *path_obj;
          if (json_object_object_get_ex(arguments, "path", &path_obj)) {
            char *path = (char *)json_object_get_string(path_obj);
            char *mkdir_result = tool_function_mkdir(path);
            json_object_object_add(tool_result, "content",
                                   json_object_new_string(mkdir_result));
            free(mkdir_result);
          }
        }
      } else {
        fprintf(stderr, "Unknown function: %s\n", function_name);
        json_object_object_add(
            tool_result, "content",
            json_object_new_string("Error: function not found."));
      }

      json_object_array_add(tools_result_messages, tool_result);
    }
  }
  return tools_result_messages;
}

#endif
