#include <math.h>
#include <stdio.h>
#include <windows.h>

#include "../include/mavka/prystriy.h"

static void out_utf8_data(char* дані, size_t розмір);
static void out_utf8_string(char* дані);
static void print_color(PristriyMavkyWriteToStdoutColor значення);
static char* do_readline(char* prefix, size_t prefix_size, size_t* out_len);

// ----------------------------

void* pristriy_mavky_malloc(size_t size) {
  return (void*)HeapAlloc(GetProcessHeap(), 0, size);
}

void* pristriy_mavky_realloc(void* value, size_t new_size) {
  if (value == NULL) {
    return (void*)HeapAlloc(GetProcessHeap(), 0, new_size);
  }
  return (void*)HeapReAlloc(GetProcessHeap(), 0, value, new_size);
}

void pristriy_mavky_free(void* value) {
  if (value != NULL) {
    HeapFree(GetProcessHeap(), 0, value);
  }
}

void pristriy_mavky_write_to_stdout(char* data, size_t size) {
  HANDLE консоль = GetStdHandle(STD_OUTPUT_HANDLE);
  DWORD записано;

  WriteFile(консоль, data, (DWORD)size, &записано, NULL);
}

void pristriy_mavky_write_utf8_to_stdout(PristriyMavkyWriteToStdoutColor color,
                                         char* data,
                                         size_t size) {
  print_color(color);

  out_utf8_data(data, size);

  print_color(PristriyMavkyColorReset);
}

double pristriy_mavky_double_pow(double value, double exp) {
  return pow(value, exp);
}
double pristriy_mavky_double_sin(double value) {
  return sin(value);
}
double pristriy_mavky_double_cos(double value) {
  return cos(value);
}
double pristriy_mavky_double_tan(double value) {
  return tan(value);
}
double pristriy_mavky_double_asin(double value) {
  return asin(value);
}
double pristriy_mavky_double_acos(double value) {
  return acos(value);
}
double pristriy_mavky_double_atan(double value) {
  return atan(value);
}
double pristriy_mavky_double_abs(double value) {
  return fabs(value);
}
double pristriy_mavky_double_exp(double value) {
  return exp(value);
}
double pristriy_mavky_double_sqrt(double value) {
  return sqrt(value);
}
double pristriy_mavky_double_ceil(double value) {
  return ceil(value);
}
double pristriy_mavky_double_floor(double value) {
  return floor(value);
}
double pristriy_mavky_double_round(double value) {
  return round(value);
}

bool pristriy_mavky_uint64_to_str(size_t value,
                                  char** data_out,
                                  size_t* size_out) {
  char buf[32];
  int len = wsprintfA(buf, "%I64u", value);
  if (len <= 0) {
    return FALSE;
  }
  *size_out = (size_t)len;
  *data_out = (char*)pristriy_mavky_malloc(*size_out);
  if (!*data_out) {
    return FALSE;
  }
  for (size_t і = 0; і < *size_out; і++) {
    (*data_out)[і] = buf[і];
  }
  return TRUE;
}

bool pristriy_mavky_int64_to_str(ssize_t value,
                                 char** data_out,
                                 size_t* size_out) {
  char buf[32];
  int len = wsprintfA(buf, "%I64d", value);
  if (len <= 0) {
    return FALSE;
  }
  *size_out = (size_t)len;
  *data_out = (char*)pristriy_mavky_malloc(*size_out);
  if (!*data_out) {
    return FALSE;
  }
  for (size_t і = 0; і < *size_out; і++) {
    (*data_out)[і] = buf[і];
  }
  return TRUE;
}

bool pristriy_mavky_double_to_str(double value,
                                  char** data_out,
                                  size_t* size_out,
                                  ssize_t* exp_size_out) {
  char buf[64];
  int len;

  // Check for NaN
  if (value != value) { // NaN != NaN
    char nan_str[] = "невизначеність";
    *size_out = sizeof(nan_str) - 1;
    *data_out = (char*)pristriy_mavky_malloc(*size_out);
    if (!*data_out)
      return FALSE;
    for (size_t і = 0; і < *size_out; і++) {
      (*data_out)[і] = nan_str[і];
    }
    *exp_size_out = 0;
    return TRUE;
  }

  // Check for infinity
  if (value > 1e308 || value < -1e308) {
    char* inf_str = value > 0 ? "нескінченність" : "-нескінченність";
    *size_out = value > 0 ? sizeof("нескінченність") - 1
                          : sizeof("-нескінченність") - 1;
    *data_out = (char*)pristriy_mavky_malloc(*size_out);
    if (!*data_out)
      return FALSE;
    for (size_t і = 0; і < *size_out; і++) {
      (*data_out)[і] = inf_str[і];
    }
    *exp_size_out = 0;
    return TRUE;
  }

  len = sprintf(buf, "%.17g", value);
  if (len < 0 || len >= (int)sizeof(buf)) {
    return FALSE;
  }

  // Знаходимо найкоротше представлення, яке round-trips
  // Пробуємо від 15 до 17 цифр точності
  for (int precision = 15; precision <= 17; precision++) {
    len = sprintf(buf, "%.*g", precision, value);
    if (len < 0 || len >= (int)sizeof(buf)) {
      continue;
    }

    // Перевіряємо чи round-trip працює
    double parsed = strtod(buf, NULL);
    if (parsed == value) {
      break;
    }
  }

  if (len < 0 || len >= (int)sizeof(buf)) {
    return FALSE;
  }

  // Find 'e' for exponential notation
  char* e_pos = NULL;
  for (int і = 0; і < len; і++) {
    if (buf[і] == 'e') {
      e_pos = &buf[і];
      break;
    }
  }

  if (e_pos != NULL) {
    // Parse exponent
    *exp_size_out = 0;
    ssize_t знак_експоненти = 1;
    char* p = e_pos + 1;
    if (*p == '+') {
      p++;
    } else if (*p == '-') {
      знак_експоненти = -1;
      p++;
    }
    while (*p >= '0' && *p <= '9') {
      *exp_size_out = *exp_size_out * 10 + (*p - '0');
      p++;
    }
    *exp_size_out *= знак_експоненти;

    size_t довжина_мантиси = (size_t)(e_pos - buf);

    *size_out = довжина_мантиси;
    *data_out = (char*)pristriy_mavky_malloc(*size_out);
    if (!*data_out)
      return FALSE;

    for (size_t і = 0; і < довжина_мантиси; і++) {
      (*data_out)[і] = buf[і];
    }
  } else {
    *exp_size_out = 0;

    *size_out = (size_t)len;
    *data_out = (char*)pristriy_mavky_malloc(*size_out);
    if (!*data_out)
      return FALSE;

    for (size_t і = 0; і < *size_out; і++) {
      (*data_out)[і] = buf[і];
    }
  }

  return TRUE;
}

bool pristriy_mavky_str_to_double(char* data, size_t size, double* out) {
  int широких_символів =
      MultiByteToWideChar(CP_UTF8, 0, (char*)data, size, NULL, 0);
  if (широких_символів == 0) {
    return FALSE;
  }

  WCHAR* широкий_рядок =
      (WCHAR*)pristriy_mavky_malloc((широких_символів + 1) * sizeof(WCHAR));
  if (!широкий_рядок) {
    return FALSE;
  }

  MultiByteToWideChar(CP_UTF8, 0, (char*)data, size, широкий_рядок,
                      широких_символів);
  широкий_рядок[широких_символів] = L'\0';

  for (int i = 0; i < широких_символів; i++) {
    if (широкий_рядок[i] == L'е') {
      широкий_рядок[i] = L'e';
    } else if (широкий_рядок[i] == L'Е') {
      широкий_рядок[i] = L'E';
    }
  }

  WCHAR* endptr;
  *out = wcstod(широкий_рядок, &endptr);

  if (широкий_рядок == endptr) {
    pristriy_mavky_free(широкий_рядок);
    return FALSE;
  } else if (errno == ERANGE) {
    pristriy_mavky_free(широкий_рядок);
    return FALSE;
  } else if (*endptr != L'\0') {
    pristriy_mavky_free(широкий_рядок);
    return FALSE;
  }

  pristriy_mavky_free(широкий_рядок);
  return TRUE;
}

bool pristriy_mavky_get_cwd(char** data_out, size_t* size_out) {
  DWORD розмір = GetCurrentDirectoryW(0, NULL);
  if (розмір == 0) {
    return FALSE;
  }

  WCHAR* широкий_шлях = (WCHAR*)pristriy_mavky_malloc(розмір * sizeof(WCHAR));
  if (!широкий_шлях) {
    return FALSE;
  }

  if (GetCurrentDirectoryW(розмір, широкий_шлях) == 0) {
    pristriy_mavky_free(широкий_шлях);
    return FALSE;
  }

  int довжина_шляху = wcslen(широкий_шлях);
  int байтів_ю8 = WideCharToMultiByte(CP_UTF8, 0, широкий_шлях, довжина_шляху,
                                      NULL, 0, NULL, NULL);
  if (байтів_ю8 == 0) {
    pristriy_mavky_free(широкий_шлях);
    return FALSE;
  }

  *size_out = байтів_ю8;
  *data_out = (char*)pristriy_mavky_malloc(*size_out);
  if (!*data_out) {
    pristriy_mavky_free(широкий_шлях);
    return FALSE;
  }

  WideCharToMultiByte(CP_UTF8, 0, широкий_шлях, довжина_шляху, (char*)*data_out,
                      байтів_ю8, NULL, NULL);

  pristriy_mavky_free(широкий_шлях);
  return TRUE;
}

bool pristriy_mavky_get_absolute_path(char* path_data,
                                      size_t path_size,
                                      char** data_out,
                                      size_t* size_out) {
  int широких_символів =
      MultiByteToWideChar(CP_UTF8, 0, (char*)path_data, path_size, NULL, 0);
  if (широких_символів == 0) {
    return FALSE;
  }

  WCHAR* широкий_шлях = (WCHAR*)HeapAlloc(
      GetProcessHeap(), 0, (широких_символів + 1) * sizeof(WCHAR));
  if (!широкий_шлях) {
    return FALSE;
  }

  MultiByteToWideChar(CP_UTF8, 0, (char*)path_data, path_size, широкий_шлях,
                      широких_символів);
  широкий_шлях[широких_символів] = L'\0';

  DWORD потрібно = GetFullPathNameW(широкий_шлях, 0, NULL, NULL);
  if (потрібно == 0) {
    pristriy_mavky_free(широкий_шлях);
    return FALSE;
  }

  WCHAR* абсолютний_широкий =
      (WCHAR*)pristriy_mavky_malloc(потрібно * sizeof(WCHAR));
  if (!абсолютний_широкий) {
    pristriy_mavky_free(широкий_шлях);
    return FALSE;
  }

  if (GetFullPathNameW(широкий_шлях, потрібно, абсолютний_широкий, NULL) == 0) {
    pristriy_mavky_free(абсолютний_широкий);
    pristriy_mavky_free(широкий_шлях);
    return FALSE;
  }

  pristriy_mavky_free(широкий_шлях);

  // Get exact size of string excluding null terminator by passing explicit length
  int довжина_абсолютного = wcslen(абсолютний_широкий);
  int байтів_ю8 = WideCharToMultiByte(CP_UTF8, 0, абсолютний_широкий,
                                      довжина_абсолютного, NULL, 0, NULL, NULL);
  if (байтів_ю8 == 0) {
    pristriy_mavky_free(абсолютний_широкий);
    return FALSE;
  }

  *size_out = байтів_ю8;
  *data_out = (char*)pristriy_mavky_malloc(*size_out);
  if (!*data_out) {
    pristriy_mavky_free(абсолютний_широкий);
    return FALSE;
  }

  // Pass exact size, no null-terminator is written to the allocated buffer
  WideCharToMultiByte(CP_UTF8, 0, абсолютний_широкий, довжина_абсолютного,
                      (char*)*data_out, байтів_ю8, NULL, NULL);

  pristriy_mavky_free(абсолютний_широкий);
  return TRUE;
}

bool pristriy_mavky_check_if_file_exists(char* path_data, size_t path_size) {
  // Convert UTF-8 path to wide char
  int широких_символів =
      MultiByteToWideChar(CP_UTF8, 0, (char*)path_data, path_size, NULL, 0);
  if (широких_символів == 0) {
    return FALSE;
  }

  WCHAR* широкий_шлях = (WCHAR*)HeapAlloc(
      GetProcessHeap(), 0, (широких_символів + 1) * sizeof(WCHAR));
  if (!широкий_шлях) {
    return FALSE;
  }

  MultiByteToWideChar(CP_UTF8, 0, (char*)path_data, path_size, широкий_шлях,
                      широких_символів);
  широкий_шлях[широких_символів] = L'\0';

  // Get file attributes using Windows API
  DWORD атрибути = GetFileAttributesW(широкий_шлях);
  pristriy_mavky_free(широкий_шлях);

  if (атрибути == INVALID_FILE_ATTRIBUTES) {
    return FALSE;
  }

  // Check if it's a regular file (not a directory)
  return (атрибути & FILE_ATTRIBUTE_DIRECTORY) == 0;
}

bool pristriy_mavky_read_file(char* path_data,
                              size_t path_size,
                              char** data_out,
                              size_t* size_out) {
  // Convert UTF-8 path to wide char
  int широких_символів =
      MultiByteToWideChar(CP_UTF8, 0, (char*)path_data, path_size, NULL, 0);
  if (широких_символів == 0) {
    return FALSE;
  }

  WCHAR* широкий_шлях = (WCHAR*)HeapAlloc(
      GetProcessHeap(), 0, (широких_символів + 1) * sizeof(WCHAR));
  if (!широкий_шлях) {
    return FALSE;
  }

  MultiByteToWideChar(CP_UTF8, 0, (char*)path_data, path_size, широкий_шлях,
                      широких_символів);
  широкий_шлях[широких_символів] = L'\0';

  // Open file using Windows API
  HANDLE файл = CreateFileW(широкий_шлях, GENERIC_READ, FILE_SHARE_READ, NULL,
                            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

  pristriy_mavky_free(широкий_шлях);

  if (файл == INVALID_HANDLE_VALUE) {
    return FALSE;
  }

  // Get file size
  LARGE_INTEGER розмір_файлу;
  if (!GetFileSizeEx(файл, &розмір_файлу)) {
    CloseHandle(файл);
    return FALSE;
  }

  *size_out = (size_t)розмір_файлу.QuadPart;

  // Allocate buffer
  *data_out = (char*)pristriy_mavky_malloc(*size_out);
  if (!*data_out) {
    CloseHandle(файл);
    return FALSE;
  }

  // Read file
  DWORD прочитано;
  if (!ReadFile(файл, *data_out, *size_out, &прочитано, NULL)) {
    pristriy_mavky_free(*data_out);
    CloseHandle(файл);
    return FALSE;
  }

  CloseHandle(файл);
  return TRUE;
}

bool pristriy_mavky_read_input_async(
    char* prefix_data,
    size_t prefix_size,
    void (*callback)(char* data, size_t data_size, bool eof, void* arg),
    void* arg,
    bool allow_history) {
  size_t len;
  char* data = do_readline(prefix_data, prefix_size, &len);

  if (!data) {
    callback(data, len, TRUE, arg);

    return TRUE;
  }

  callback(data, len, FALSE, arg);

  return TRUE;
}

void* pristriy_mavky_dlopen(char* path_data, size_t path_size, int* err_out) {
  *err_out = 0;

  if (!path_data || path_size == 0) {
    *err_out = -1;
    return NULL;
  }

  /* path_data may or may not be NUL-terminated within path_size bytes. */
  size_t len = strnlen(path_data, path_size);

  /* UTF-8 -> UTF-16, two-step (measure, then convert). */
  int wlen = MultiByteToWideChar(CP_UTF8, 0, path_data, (int)len, NULL, 0);
  if (wlen <= 0) {
    *err_out = -1;
    return NULL;
  }

  wchar_t* wpath = (wchar_t*)pristriy_mavky_malloc(
      (size_t)wlen * sizeof(wchar_t) + sizeof(wchar_t));
  if (!wpath) {
    *err_out = -1;
    return NULL;
  }

  int written =
      MultiByteToWideChar(CP_UTF8, 0, path_data, (int)len, wpath, wlen);
  if (written <= 0) {
    pristriy_mavky_free(wpath);
    *err_out = -1;
    return NULL;
  }
  wpath[written] = L'\0';

  HMODULE dl = LoadLibraryW(wpath);
  pristriy_mavky_free(wpath);

  if (!dl) {
    *err_out = -1;
    return NULL;
  }

  return (void*)dl;
}

void* pristriy_mavky_dlsym(void* dl,
                           char* name_data,
                           size_t name_size,
                           int* err_out) {
  *err_out = 0;

  if (!dl || !name_data || name_size == 0) {
    *err_out = -1;
    return NULL;
  }

  /* PE export names are ASCII by spec, so valid UTF-8 (i.e. pure ASCII)
     names work directly with GetProcAddress (LPCSTR). We only need to
     guarantee NUL-termination, since name_size implies the buffer may
     not be NUL-terminated. */
  size_t len = strnlen(name_data, name_size);

  FARPROC dls;
  if (len < name_size && name_data[len] == '\0') {
    /* Already NUL-terminated. */
    dls = GetProcAddress((HMODULE)dl, name_data);
  } else {
    char* tmp = (char*)pristriy_mavky_malloc(len + 1);
    if (!tmp) {
      *err_out = -1;
      return NULL;
    }
    memcpy(tmp, name_data, len);
    tmp[len] = '\0';
    dls = GetProcAddress((HMODULE)dl, tmp);
    pristriy_mavky_free(tmp);
  }

  if (!dls) {
    *err_out = -1;
    return NULL;
  }

  return (void*)dls;
}

int pristriy_mavky_dlclose(void* dl) {
  BOOL result = FreeLibrary((HMODULE)dl);
  return result ? 0 : -1;
}

void pristriy_mavky_exit(int code) {
  ExitProcess((UINT)code);
}

// ----------------------------

static void out_utf8_data(char* дані, size_t розмір) {
  if (розмір == 0) {
    return;
  }

  HANDLE консоль = GetStdHandle(STD_OUTPUT_HANDLE);
  DWORD режим;
  DWORD записано;

  // Check if the output is a real console
  if (GetConsoleMode(консоль, &режим)) {
    // It's a console. Convert to UTF-16 and use WriteConsoleW
    int широких_символів =
        MultiByteToWideChar(CP_UTF8, 0, (char*)дані, розмір, NULL, 0);
    if (широких_символів == 0) {
      return;
    }

    WCHAR* широкий_буфер =
        (WCHAR*)pristriy_mavky_malloc(широких_символів * sizeof(WCHAR));
    if (!широкий_буфер) {
      return;
    }

    MultiByteToWideChar(CP_UTF8, 0, (char*)дані, розмір, широкий_буфер,
                        широких_символів);

    WriteConsoleW(консоль, широкий_буфер, широких_символів, &записано, NULL);

    pristriy_mavky_free(широкий_буфер);
  } else {
    // It's a pipe or file (e.g. CI pipeline). Write the original UTF-8 bytes directly.
    WriteFile(консоль, дані, (DWORD)розмір, &записано, NULL);
  }
}

static void out_utf8_string(char* дані) {
  if (!дані) {
    return;
  }

  // Calculate string length
  size_t довжина = 0;
  while (дані[довжина] != '\0') {
    довжина++;
  }

  out_utf8_data((char*)дані, довжина);
}

static void print_color(PristriyMavkyWriteToStdoutColor значення) {
  HANDLE консоль = GetStdHandle(STD_OUTPUT_HANDLE);

  // Enable virtual terminal processing for ANSI color support
  DWORD режим;
  if (GetConsoleMode(консоль, &режим)) {
    SetConsoleMode(консоль, режим | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
  }

  WORD атрибути;
  switch (значення) {
    case PristriyMavkyColorReset:
      атрибути = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
      break;
    case PristriyMavkyColorRed:
      атрибути = FOREGROUND_RED | FOREGROUND_INTENSITY;
      break;
    case PristriyMavkyColorGreen:
      атрибути = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
      break;
    case PristriyMavkyColorBlue:
      атрибути = FOREGROUND_BLUE | FOREGROUND_INTENSITY;
      break;
    case PristriyMavkyColorYellow:
      атрибути = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
      break;
    default:
      атрибути = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
      break;
  }

  SetConsoleTextAttribute(консоль, атрибути);
}

static char* do_readline(char* prefix, size_t prefix_size, size_t* out_len) {
  pristriy_mavky_write_utf8_to_stdout(PristriyMavkyColorReset, prefix,
                                      prefix_size);

  HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
  if (hStdin == INVALID_HANDLE_VALUE) {
    return NULL;
  }

  DWORD режим;
  int is_console = GetConsoleMode(hStdin, &режим);

  size_t wide_cap = 128;
  size_t wide_len = 0;
  WCHAR* wide_buf = (WCHAR*)pristriy_mavky_malloc(wide_cap * sizeof(WCHAR));
  if (!wide_buf)
    return NULL;

  if (is_console) {
    // Real console: read UTF-16
    WCHAR wc;
    DWORD charsRead;

    while (ReadConsoleW(hStdin, &wc, 1, &charsRead, NULL) && charsRead == 1) {
      if (wc == L'\n')
        break;
      if (wc == L'\r')
        continue;

      if (wide_len + 1 >= wide_cap) {
        wide_cap *= 2;
        WCHAR* tmp =
            (WCHAR*)pristriy_mavky_realloc(wide_buf, wide_cap * sizeof(WCHAR));
        if (!tmp) {
          pristriy_mavky_free(wide_buf);
          return NULL;
        }
        wide_buf = tmp;
      }

      wide_buf[wide_len++] = wc;
    }
  } else {
    // Pipe or file: read UTF-8 bytes, then convert to UTF-16
    char c;
    DWORD charsRead;
    size_t utf8_cap = 128;
    size_t utf8_len = 0;
    char* utf8_buf = (char*)pristriy_mavky_malloc(utf8_cap);
    if (!utf8_buf) {
      pristriy_mavky_free(wide_buf);
      return NULL;
    }

    while (ReadFile(hStdin, &c, 1, &charsRead, NULL) && charsRead == 1) {
      if (c == '\n')
        break;
      if (c == '\r')
        continue;

      if (utf8_len + 1 >= utf8_cap) {
        utf8_cap *= 2;
        char* tmp = (char*)pristriy_mavky_realloc(utf8_buf, utf8_cap);
        if (!tmp) {
          pristriy_mavky_free(utf8_buf);
          pristriy_mavky_free(wide_buf);
          return NULL;
        }
        utf8_buf = tmp;
      }
      utf8_buf[utf8_len++] = c;
    }

    if (utf8_len > 0) {
      int w_len = MultiByteToWideChar(CP_UTF8, 0, utf8_buf, utf8_len, NULL, 0);
      if (w_len > 0) {
        if ((size_t)w_len >= wide_cap) {
          wide_cap = w_len + 1;
          WCHAR* tmp = (WCHAR*)pristriy_mavky_realloc(wide_buf,
                                                      wide_cap * sizeof(WCHAR));
          if (!tmp) {
            pristriy_mavky_free(utf8_buf);
            pristriy_mavky_free(wide_buf);
            return NULL;
          }
          wide_buf = tmp;
        }
        MultiByteToWideChar(CP_UTF8, 0, utf8_buf, utf8_len, wide_buf, w_len);
        wide_len = w_len;
      }
    }
    pristriy_mavky_free(utf8_buf);
  }

  // If nothing read, return NULL
  if (wide_len == 0) {
    pristriy_mavky_free(wide_buf);
    return NULL;
  }

  // Convert UTF-16 back to UTF-8 for Mavka
  int utf8_size =
      WideCharToMultiByte(CP_UTF8, 0, wide_buf, wide_len, NULL, 0, NULL, NULL);
  if (utf8_size == 0) {
    pristriy_mavky_free(wide_buf);
    return NULL;
  }

  char* buf = (char*)pristriy_mavky_malloc(utf8_size + 1);
  if (!buf) {
    pristriy_mavky_free(wide_buf);
    return NULL;
  }

  WideCharToMultiByte(CP_UTF8, 0, wide_buf, wide_len, buf, utf8_size, NULL,
                      NULL);
  buf[utf8_size] = '\0';

  pristriy_mavky_free(wide_buf);

  if (out_len)
    *out_len = utf8_size;

  return buf;
}