#include <compat.h> // IWYU pragma: keep

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef _WIN32
#define popen _popen
#define pclose _pclose
#else
#include <sys/wait.h>
#endif

#include "ant.h"
#include "errors.h"
#include "internal.h"
#include "modules/symbol.h"

static ant_value_t builtin_shell_text(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t builtin_shell_lines(ant_t *js, ant_value_t *args, int nargs);

static ant_value_t shell_exec(ant_t *js, const char *cmd, size_t cmd_len) {
  ant_value_t result = js_mkobj(js);
  
  FILE *fp = popen(cmd, "r");
  if (!fp) {
    js_set(js, result, "stdout", js_mkstr(js, "", 0));
    js_set(js, result, "stderr", js_mkstr(js, "Failed to execute command", 25));
    js_set(js, result, "exitCode", js_mknum(1));
    return result;
  }
  
  char *output = NULL;
  size_t output_size = 0;
  size_t output_capacity = 4096;
  output = malloc(output_capacity);
  
  if (!output) {
    pclose(fp);
    return js_mkerr(js, "Out of memory");
  }
  
  char buffer[4096];
  while (fgets(buffer, sizeof(buffer), fp) != NULL) {
    size_t len = strlen(buffer);
    if (output_size + len >= output_capacity) {
      output_capacity *= 2;
      char *new_output = realloc(output, output_capacity);
      if (!new_output) {
        free(output);
        pclose(fp);
        return js_mkerr(js, "Out of memory");
      }
      output = new_output;
    }
    memcpy(output + output_size, buffer, len);
    output_size += len;
  }
  
  int status = pclose(fp);
#ifdef _WIN32
  int exit_code = status;
#else
  int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
#endif
  if (output_size > 0 && output[output_size - 1] == '\n') output_size--;
  
  ant_value_t stdout_val = js_mkstr(js, output, output_size);
  free(output);
  
  js_set(js, result, "exitCode", js_mknum(exit_code));
  js_set(js, result, "text", js_heavy_mkfun(js, builtin_shell_text, stdout_val));
  js_set(js, result, "lines", js_heavy_mkfun(js, builtin_shell_lines, stdout_val));
  
  return result;
}

static ant_value_t builtin_shell_text(ant_t *js, ant_value_t *args, int nargs) {
  (void)args;
  (void)nargs;
  
  ant_value_t fn = js_getcurrentfunc(js);
  return js_get_slot(fn, SLOT_DATA);
}

static ant_value_t builtin_shell_lines(ant_t *js, ant_value_t *args, int nargs) {
  (void)args;
  (void)nargs;
  
  ant_value_t fn = js_getcurrentfunc(js);
  ant_value_t stdout_val = js_get_slot(fn, SLOT_DATA);
  
  size_t text_len;
  char *text = js_getstr(js, stdout_val, &text_len);
  if (!text) return js_mkarr(js);
  
  ant_value_t lines_array = js_mkarr(js);
  size_t line_start = 0;
  
  for (size_t i = 0; i <= text_len; i++) {
    if (i == text_len || text[i] == '\n') {
      size_t line_len = i - line_start;
      ant_value_t line_val = js_mkstr(js, text + line_start, line_len);
      js_arr_push(js, lines_array, line_val);
      line_start = i + 1;
    }
  }
  
  return lines_array;
}

static ant_value_t builtin_shell_dollar(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "$() requires at least one argument");
    
  if (!is_special_object(args[0])) {
    if (vtype(args[0]) == T_STR) {
      size_t cmd_len;
      char *cmd = js_getstr(js, args[0], &cmd_len);
      if (!cmd) return js_mkerr(js, "Failed to get command string");
      return shell_exec(js, cmd, cmd_len);
    }
    
    return js_mkerr(js, "$() requires a template string");
  }
  
  ant_value_t strings_array = args[0];
  
  char *command = malloc(4096);
  if (!command) return js_mkerr(js, "Out of memory");
  
  size_t cmd_pos = 0;
  size_t cmd_capacity = 4096;
  
  ant_value_t length_val = js_get(js, strings_array, "length");
  int length = (int)js_getnum(length_val);
  
  for (int i = 0; i < length; i++) {
    char idx_str[32];
    snprintf(idx_str, sizeof(idx_str), "%d", i);
    
    ant_value_t str_val = js_get(js, strings_array, idx_str);
    if (vtype(str_val) == T_STR) {
      size_t str_len;
      char *str = js_getstr(js, str_val, &str_len);
      
      if (cmd_pos + str_len >= cmd_capacity) {
        cmd_capacity *= 2;
        char *new_cmd = realloc(command, cmd_capacity);
        if (!new_cmd) {
          free(command);
          return js_mkerr(js, "Out of memory");
        }
        command = new_cmd;
      }
      
      memcpy(command + cmd_pos, str, str_len);
      cmd_pos += str_len;
    }
    
    if (i + 1 < nargs) {
      ant_value_t val = args[i + 1];
      char val_str[256];
      size_t val_len = 0;
      
      if (vtype(val) == T_STR) {
        size_t len;
        char *s = js_getstr(js, val, &len);
        val_len = len < sizeof(val_str) - 1 ? len : sizeof(val_str) - 1;
        memcpy(val_str, s, val_len);
      } else if (vtype(val) == T_NUM) {
        val_len = snprintf(val_str, sizeof(val_str), "%g", js_getnum(val));
      }
      
      if (cmd_pos + val_len >= cmd_capacity) {
        cmd_capacity *= 2;
        char *new_cmd = realloc(command, cmd_capacity);
        if (!new_cmd) {
          free(command);
          return js_mkerr(js, "Out of memory");
        }
        command = new_cmd;
      }
      
      memcpy(command + cmd_pos, val_str, val_len);
      cmd_pos += val_len;
    }
  }
  
  command[cmd_pos] = '\0';
  
  ant_value_t result = shell_exec(js, command, cmd_pos);
  free(command);
  
  return result;
}

ant_value_t shell_library(ant_t *js) {
  ant_value_t lib = js_mkobj(js);
  
  js_set(js, lib, "$", js_mkfun(builtin_shell_dollar));
  js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "shell", 5));
  
  return lib;
}
