#include <dirent.h>
#include <json-c/json.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#define MAX_FILES 20000
#define MAX_PATH 4096

static const char *extensions[] = {
    ".c",      ".cpp",   ".h",        ".py",      ".java",   ".js",
    ".mk",     ".html",  "Makefile",  ".css",     ".json",   ".cs",
    ".csproj", ".sln",   ".toml",     ".rs",      ".go",     ".rb",
    ".swift",  ".php",   ".pl",       ".sh",      ".bash",   ".sql",
    ".xml",    ".yaml",  ".yml",      ".kt",      ".dart",   ".scala",
    ".clj",    ".asm",   ".m",        ".r",       ".lua",    ".groovy",
    ".v",      ".pas",   ".d",        ".f90",     ".f95",    ".for",
    ".s",      ".tcl",   ".vhdl",     ".verilog", ".coffee", ".less",
    ".scss",   ".ps1",   ".psm1",     ".cmd",     ".bat",    ".json5",
    ".cxx",    ".cc",    ".hpp",      ".hxx",     ".inc",    ".nsi",
    ".ninja",  ".cmake", ".cmake.in", ".mk.in",   ".make",   ".makefile",
    ".gyp",    ".gypi",  ".pro",      ".qml",     ".ui",     ".wxs",
    ".wxl",    ".wxi",   ".wxl",      ".wxs",     ".wxi",    ".wxl",
    ".wxs",    ".wxi"};
static const size_t ext_count = sizeof(extensions) / sizeof(extensions[0]);

typedef struct {
  char name[MAX_PATH];
  char modification_date[20];
  char creation_date[20];
  char type[10];
  size_t size_bytes;
} FileInfo;

static FileInfo file_list[MAX_FILES];
static size_t file_count = 0;

static int is_valid_extension(const char *filename) {
  const char *dot = strrchr(filename, '.');
  if (!dot)
    dot = filename;
  for (size_t i = 0; i < ext_count; i++) {
    if (strcmp(dot, extensions[i]) == 0)
      return 1;
  }
  return 0;
}

static int is_ignored_directory(const char *dir_name) {
  const char *ignored_dirs[] = {"env", ".venv", "node_modules", "venv",
                                "virtualenv"};
  for (size_t i = 0; i < sizeof(ignored_dirs) / sizeof(ignored_dirs[0]); i++) {
    if (strcmp(dir_name, ignored_dirs[i]) == 0)
      return 1;
  }
  return 0;
}

static void get_file_info(const char *path) {
  struct stat file_stat;
  if (stat(path, &file_stat) == 0) {
    FileInfo info;
    strncpy(info.name, path, MAX_PATH - 1);
    info.name[MAX_PATH - 1] = '\0';
    strftime(info.modification_date, sizeof(info.modification_date),
             "%Y-%m-%d %H:%M:%S", localtime(&file_stat.st_mtime));
    strftime(info.creation_date, sizeof(info.creation_date),
             "%Y-%m-%d %H:%M:%S", localtime(&file_stat.st_ctime));
    strncpy(info.type, S_ISDIR(file_stat.st_mode) ? "directory" : "file",
            sizeof(info.type) - 1);
    info.type[sizeof(info.type) - 1] = '\0';
    info.size_bytes = file_stat.st_size;
    file_list[file_count++] = info;
  }
}

char *index_directory(const char *dir_path) {
  DIR *dir = opendir(dir_path);
  if (!dir) {
    perror("Failed to open directory");
    return NULL;
  }

  struct dirent *entry;
  json_object *jarray = json_object_new_array();

  while ((entry = readdir(dir)) != NULL) {
    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
      continue;
    if (entry->d_name[0] == '.' || is_ignored_directory(entry->d_name))
      continue;

    char full_path[MAX_PATH];
    snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);

    if (entry->d_type == DT_DIR) {
      char *subdir_json = index_directory(full_path);
      if (subdir_json) {
        json_object *jsubdir = json_object_new_string(subdir_json);
        json_object_array_add(jarray, jsubdir);
        free(subdir_json);
      }
    } else if (is_valid_extension(entry->d_name)) {
      get_file_info(full_path);
      json_object *jfile = json_object_new_object();
      json_object_object_add(
          jfile, "file_name",
          json_object_new_string(file_list[file_count - 1].name));
      json_object_object_add(
          jfile, "modification_date",
          json_object_new_string(file_list[file_count - 1].modification_date));
      json_object_object_add(
          jfile, "creation_date",
          json_object_new_string(file_list[file_count - 1].creation_date));
      json_object_object_add(
          jfile, "type",
          json_object_new_string(file_list[file_count - 1].type));
      json_object_object_add(
          jfile, "size_bytes",
          json_object_new_int64(file_list[file_count - 1].size_bytes));

      json_object_array_add(jarray, jfile);
    }
  }
  closedir(dir);

  char *result = strdup(json_object_to_json_string(jarray));
  json_object_put(jarray);
  return result;
}