#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#include "ant.h"

static jsval_t builtin_shell_text(struct js *js, jsval_t *args, int nargs);
static jsval_t builtin_shell_lines(struct js *js, jsval_t *args, int nargs);

static jsval_t shell_exec(struct js *js, const char *cmd, size_t cmd_len) {
  jsval_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);
  int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
  if (output_size > 0 && output[output_size - 1] == '\n') output_size--;
  
  jsval_t stdout_val = js_mkstr(js, output, output_size);
  free(output);
  
  js_set(js, result, "stdout", stdout_val);
  js_set(js, result, "stderr", js_mkstr(js, "", 0));
  js_set(js, result, "exitCode", js_mknum(exit_code));
  
  jsval_t text_fn = js_mkfun(builtin_shell_text);
  js_set(js, result, "text", text_fn);
  
  jsval_t lines_fn = js_mkfun(builtin_shell_lines);
  js_set(js, result, "lines", lines_fn);
  
  return result;
}

static jsval_t builtin_shell_text(struct js *js, jsval_t *args, int nargs) {
  (void)args;
  (void)nargs;
  
  jsval_t this_val = js_getthis(js);
  if (js_type(this_val) != JS_PRIV) {
    return js_mkerr(js, "text() must be called on a shell result");
  }
  
  jsval_t stdout_val = js_get(js, this_val, "stdout");
  return stdout_val;
}

static jsval_t builtin_shell_lines(struct js *js, jsval_t *args, int nargs) {
  (void)args;
  (void)nargs;
  
  jsval_t this_val = js_getthis(js);
  if (js_type(this_val) != JS_PRIV) return js_mkerr(js, "lines() must be called on a shell result");
  
  jsval_t stdout_val = js_get(js, this_val, "stdout");
  if (js_type(stdout_val) != JS_STR) return js_mkerr(js, "No stdout available");
  
  size_t text_len;
  char *text = js_getstr(js, stdout_val, &text_len);
  if (!text) return js_mkerr(js, "Failed to get stdout");
  
  jsval_t lines_array = js_mkobj(js);
  
  size_t line_count = 0;
  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;
      if (i < text_len && text[i] == '\n') {}
      
      char idx_str[32];
      snprintf(idx_str, sizeof(idx_str), "%zu", line_count);
      
      jsval_t line_val = js_mkstr(js, text + line_start, line_len);
      js_set(js, lines_array, idx_str, line_val);
      
      line_count++;
      line_start = i + 1;
    }
  }
  
  js_set(js, lines_array, "length", js_mknum((double)line_count));
  
  return lines_array;
}

static jsval_t builtin_shell_dollar(struct js *js, jsval_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "$() requires at least one argument");
    
  if (js_type(args[0]) != JS_PRIV) {
    if (js_type(args[0]) == JS_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");
      
      jsval_t result = shell_exec(js, cmd, cmd_len);
      jsval_t promise = js_mkpromise(js);
      js_resolve_promise(js, promise, result);
      
      return promise;
    }
    
    return js_mkerr(js, "$() requires a template string");
  }
  
  jsval_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;
  
  jsval_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);
    
    jsval_t str_val = js_get(js, strings_array, idx_str);
    if (js_type(str_val) == JS_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) {
      jsval_t val = args[i + 1];
      char val_str[256];
      size_t val_len = 0;
      
      if (js_type(val) == JS_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 (js_type(val) == JS_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';
  
  jsval_t result = shell_exec(js, command, cmd_pos);
  free(command);
  
  jsval_t promise = js_mkpromise(js);
  js_resolve_promise(js, promise, result);
  
  return promise;
}

jsval_t shell_library(struct js *js) {
  jsval_t lib = js_mkobj(js);
  js_set(js, lib, "$", js_mkfun(builtin_shell_dollar));
  
  return lib;
}
