#include <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#ifdef _WIN32
#include <conio.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <direct.h>
#define STDIN_FILENO 0
#define mkdir_p(path) _mkdir(path)
#else
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#define mkdir_p(path) mkdir(path, 0755)
#endif

#include <crprintf.h>
#include "highlight.h"
#include "readline.h"
#include "utf8.h"

#define MAX_LINE_LENGTH 4096

static volatile sig_atomic_t ctrl_c_pressed = 0;
static crprintf_compiled *hl_prog = NULL;
static highlight_state hl_line_state = HL_STATE_INIT;
static int repl_last_cursor_row = 0;

static void sigint_handler(int sig) {
  (void)sig;
  ctrl_c_pressed++;
}

void ant_readline_install_signal_handler(void) {
#ifdef _WIN32
  signal(SIGINT, sigint_handler);
#else
  struct sigaction sa;
  memset(&sa, 0, sizeof(sa));
  sa.sa_handler = sigint_handler;
  sigemptyset(&sa.sa_mask);
  sigaction(SIGINT, &sa, NULL);
#endif
}

void ant_readline_shutdown(void) {
  if (hl_prog) {
    crprintf_compiled_free(hl_prog);
    hl_prog = NULL;
  }
}

void ant_history_init(ant_history_t *hist, int capacity) {
  hist->capacity = (capacity > 0) ? capacity : 512;
  hist->lines = malloc(sizeof(char *) * (size_t)hist->capacity);
  if (!hist->lines) hist->capacity = 0;
  hist->count = 0;
  hist->current = -1;
}

void ant_history_add(ant_history_t *hist, const char *line) {
  if (!hist || !hist->lines || hist->capacity <= 0 || !line || line[0] == '\0') return;
  if (hist->count > 0 && strcmp(hist->lines[hist->count - 1], line) == 0) return;

  if (hist->count >= hist->capacity) {
    free(hist->lines[0]);
    memmove(hist->lines, hist->lines + 1, sizeof(char *) * (size_t)(hist->capacity - 1));
    hist->count--;
  }

  hist->lines[hist->count++] = strdup(line);
  hist->current = hist->count;
}

const char *ant_history_prev(ant_history_t *hist) {
  if (!hist || !hist->lines || hist->count == 0) return NULL;
  if (hist->current > 0) hist->current--;
  return hist->lines[hist->current];
}

const char *ant_history_next(ant_history_t *hist) {
  if (!hist || !hist->lines || hist->count == 0) return NULL;
  if (hist->current < hist->count - 1) {
    hist->current++;
    return hist->lines[hist->current];
  }
  hist->current = hist->count;
  return "";
}

void ant_history_free(ant_history_t *hist) {
  if (!hist || !hist->lines) return;
  for (int i = 0; i < hist->count; i++) free(hist->lines[i]);
  free(hist->lines);
  hist->lines = NULL;
  hist->count = 0;
  hist->capacity = 0;
  hist->current = -1;
}

static char *get_history_path(void) {
  const char *home = getenv("HOME");
  if (!home) home = getenv("USERPROFILE");
  if (!home) return NULL;

  size_t len = strlen(home) + 32;
  char *path = malloc(len);
  snprintf(path, len, "%s/.ant", home);
  mkdir_p(path);
  snprintf(path, len, "%s/.ant/repl_history", home);
  return path;
}

void ant_history_load(ant_history_t *hist) {
  if (!hist || !hist->lines || hist->capacity <= 0) return;
  char *path = get_history_path();
  if (!path) return;

  FILE *fp = fopen(path, "r");
  free(path);
  if (!fp) return;

  char line[MAX_LINE_LENGTH];
  while (fgets(line, sizeof(line), fp)) {
    size_t len = strlen(line);
    if (len > 0 && line[len - 1] == '\n') line[len - 1] = '\0';
    if (line[0]) ant_history_add(hist, line);
  }
  fclose(fp);
}

void ant_history_save(const ant_history_t *hist) {
  if (!hist || !hist->lines) return;
  char *path = get_history_path();
  if (!path) return;

  FILE *fp = fopen(path, "w");
  free(path);
  if (!fp) return;

  for (int i = 0; i < hist->count; i++) {
    fprintf(fp, "%s\n", hist->lines[i]);
  }
  fclose(fp);
}

typedef enum {
  KEY_NONE, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT,
  KEY_HOME, KEY_END, KEY_DELETE, KEY_BACKSPACE, KEY_ENTER, KEY_EOF, KEY_CHAR
} key_type_t;

typedef struct {
  key_type_t type;
  int ch;
} key_event_t;

static int repl_terminal_cols(void) {
  int cols = 80;
#ifdef _WIN32
  CONSOLE_SCREEN_BUFFER_INFO csbi;
  if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
    cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
  }
#else
  struct winsize ws;
  if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) {
    cols = ws.ws_col;
  }
#endif
  return (cols > 0) ? cols : 80;
}

static void repl_jump_cursor(int x_pos, int y_offset) {
#ifdef _WIN32
  if (y_offset != 0) {
    char seq[32];
    snprintf(seq, sizeof(seq), "\033[%d%c", abs(y_offset), (y_offset > 0) ? 'B' : 'A');
    fputs(seq, stdout);
  }
  char seq[32];
  snprintf(seq, sizeof(seq), "\033[%dG", x_pos + 1);
  fputs(seq, stdout);
#else
  if (y_offset != 0) {
    char seq[32];
    snprintf(seq, sizeof(seq), "\033[%d%c", abs(y_offset), (y_offset > 0) ? 'B' : 'A');
    fputs(seq, stdout);
  }
  char seq[32];
  snprintf(seq, sizeof(seq), "\033[%dG", x_pos + 1);
  fputs(seq, stdout);
#endif
}

static void repl_clear_to_end(void) {
  fputs("\033[J", stdout);
}

static void repl_set_cursor_visible(bool visible) {
#ifdef _WIN32
  (void)visible;
#else
  fputs(visible ? "\033[?25h" : "\033[?25l", stdout);
#endif
}

static size_t repl_skip_ansi_escape(const char *s, size_t len, size_t i) {
  if (i >= len || (unsigned char)s[i] != 0x1B) return i;
  if (i + 1 >= len) return i + 1;

  unsigned char next = (unsigned char)s[i + 1];

  if (next == '[') {
    i += 2;
    while (i < len) {
      unsigned char ch = (unsigned char)s[i++];
      if (ch >= 0x40 && ch <= 0x7E) break;
    }
    return i;
  }

  if (next == ']') {
    i += 2;
    while (i < len) {
      unsigned char ch = (unsigned char)s[i];
      if (ch == '\a') return i + 1;
      if (ch == 0x1B && i + 1 < len && s[i + 1] == '\\') return i + 2;
      i++;
    }
    return i;
  }

  return i + 2;
}

static void repl_virtual_render(
  const char *s, size_t n,
  int screen_cols, int prompt_len,
  int *x, int *y
) {
  bool wrapped = false;
  size_t i = 0;
  while (i < n) {
    unsigned char c = (unsigned char)s[i];
    if (c == '\n' || c == '\r') {
      if (c == '\n' && !wrapped) (*y)++;
      *x = prompt_len;
      i++;
      wrapped = false;
      continue;
    }

    if (c == 0x1B) {
      i = repl_skip_ansi_escape(s, n, i);
      continue;
    }

    utf8proc_int32_t cp = 0;
    utf8proc_ssize_t clen =
      utf8_next((const utf8proc_uint8_t *)(s + i), (utf8proc_ssize_t)(n - i), &cp);
    if (clen <= 0) {
      clen = 1;
      cp = c;
    }

    int w = utf8proc_charwidth(cp);
    if (w < 0) w = 1;

    if (w > 0) {
      *x += w;
      wrapped = false;
      if (*x >= screen_cols) {
        *x = 0;
        (*y)++;
        wrapped = true;
      }
    }

    i += (size_t)clen;
  }
}

static int repl_prompt_width(const char *prompt, int cols) {
  int x = 0;
  int y = 0;
  repl_virtual_render(prompt, strlen(prompt), cols, 0, &x, &y);
  return x;
}

static void refresh_line(const char *line, int len, int pos, const char *prompt) {
  int cols = repl_terminal_cols();
  int prompt_len = repl_prompt_width(prompt, cols);
  int x_cursor = prompt_len;
  int y_cursor = 0;
  int x_end = prompt_len;
  int y_end = 0;

  repl_virtual_render(line, (size_t)pos, cols, prompt_len, &x_cursor, &y_cursor);
  repl_virtual_render(line, (size_t)len, cols, prompt_len, &x_end, &y_end);

  repl_set_cursor_visible(false);
  repl_jump_cursor(prompt_len, -repl_last_cursor_row);
  repl_clear_to_end();
  if (crprintf_get_color() && len > 0) {
    if (len <= 2048) {
    char tagged[8192];
    char rendered[8192];
    
    highlight_state state = hl_line_state;
    ant_highlight_stateful(line, (size_t)len, tagged, sizeof(tagged), &state);
    
    hl_prog = crprintf_recompile(hl_prog, tagged);
    crprintf_state *rs = crprintf_state_new();
    crsprintf_compiled(rendered, sizeof(rendered), rs, hl_prog);
    crprintf_state_free(rs);
    
    fputs(rendered, stdout);
    } else fwrite(line, 1, (size_t)len, stdout);
  } else if (len > 0) fwrite(line, 1, (size_t)len, stdout);
  
#ifndef _WIN32
  if ((x_end == 0) && (y_end > 0) && (len > 0) && (line[len - 1] != '\n')) {
    fputs("\n", stdout);
  }
#endif

  repl_jump_cursor(x_cursor, -(y_end - y_cursor));
  repl_set_cursor_visible(true);
  repl_last_cursor_row = y_cursor;
  fflush(stdout);
}

static void move_cursor_only(const char *line, int pos, const char *prompt) {
  int cols = repl_terminal_cols();
  int prompt_len = repl_prompt_width(prompt, cols);
  int x_cursor = prompt_len;
  int y_cursor = 0;
  repl_virtual_render(line, (size_t)pos, cols, prompt_len, &x_cursor, &y_cursor);
  repl_jump_cursor(x_cursor, -(repl_last_cursor_row - y_cursor));
  repl_last_cursor_row = y_cursor;
  fflush(stdout);
}

static int utf8_prev_pos(const char *line, int pos) {
  if (pos <= 0) return 0;
  int prev = 0;
  int i = 0;
  while (i < pos) {
    prev = i;
    utf8proc_int32_t cp = 0;
    utf8proc_ssize_t n = utf8_next(
      (const utf8proc_uint8_t *)(line + i),
      (utf8proc_ssize_t)(pos - i),
      &cp
    );
    i += (int)((n > 0) ? n : 1);
  }
  return prev;
}

static int utf8_next_pos(const char *line, int len, int pos) {
  if (pos >= len) return len;
  utf8proc_int32_t cp = 0;
  utf8proc_ssize_t n = utf8_next(
    (const utf8proc_uint8_t *)(line + pos),
    (utf8proc_ssize_t)(len - pos),
    &cp
  );
  int next = pos + (int)((n > 0) ? n : 1);
  return (next > len) ? len : next;
}

static void line_set(char *line, int *pos, int *len, const char *str, const char *prompt) {
  size_t n = strlen(str);
  if (n >= (size_t)MAX_LINE_LENGTH) n = (size_t)MAX_LINE_LENGTH - 1;
  memcpy(line, str, n);
  line[n] = '\0';
  *len = (int)strlen(line);
  *pos = *len;
  refresh_line(line, *len, *pos, prompt);
}

static void line_backspace(char *line, int *pos, int *len, const char *prompt) {
  if (*pos <= 0) return;

  int prev = utf8_prev_pos(line, *pos);
  memmove(line + prev, line + *pos, (size_t)(*len - *pos + 1));
  *len -= (*pos - prev);
  *pos = prev;
  refresh_line(line, *len, *pos, prompt);
}

static void line_delete(char *line, int *pos, int *len, const char *prompt) {
  if (*pos >= *len) return;
  int next = utf8_next_pos(line, *len, *pos);
  memmove(line + *pos, line + next, (size_t)(*len - next + 1));
  *len -= (next - *pos);
  refresh_line(line, *len, *pos, prompt);
}

static void line_insert(char *line, int *pos, int *len, int c, const char *prompt) {
  if (*len >= MAX_LINE_LENGTH - 1) return;

  memmove(line + *pos + 1, line + *pos, (size_t)(*len - *pos + 1));
  line[*pos] = (char)c;
  (*pos)++;
  (*len)++;
  refresh_line(line, *len, *pos, prompt);
}

#ifdef _WIN32
static key_event_t read_key(void) {
  if (ctrl_c_pressed > 0) return (key_event_t){ KEY_EOF, 0 };
  int c = _getch();
  if (c == 0 || c == 0xE0) {
    int ext = _getch();
    switch (ext) {
      case 72: return (key_event_t){ KEY_UP, 0 };
      case 80: return (key_event_t){ KEY_DOWN, 0 };
      case 77: return (key_event_t){ KEY_RIGHT, 0 };
      case 75: return (key_event_t){ KEY_LEFT, 0 };
      case 71: return (key_event_t){ KEY_HOME, 0 };
      case 79: return (key_event_t){ KEY_END, 0 };
      case 83: return (key_event_t){ KEY_DELETE, 0 };
      default: return (key_event_t){ KEY_NONE, 0 };
    }
  }
  if (c == 8) return (key_event_t){ KEY_BACKSPACE, 0 };
  if (c == '\r' || c == '\n') return (key_event_t){ KEY_ENTER, 0 };
  if (c == 3) {
    ctrl_c_pressed++;
    return (key_event_t){ KEY_EOF, 0 };
  }
  if (c == 4 || c == 26) return (key_event_t){ KEY_EOF, 0 };
  if (isprint(c) || (unsigned char)c >= 0x80) return (key_event_t){ KEY_CHAR, c };
  return (key_event_t){ KEY_NONE, 0 };
}
#else
static struct termios saved_tio;

static key_event_t read_key(void) {
  if (ctrl_c_pressed > 0) return (key_event_t){ KEY_EOF, 0 };
  int c = getchar();
  if (c == EOF && !feof(stdin)) {
    clearerr(stdin);
    return (key_event_t){ KEY_EOF, 0 };
  }
  if (c == EOF) return (key_event_t){ KEY_EOF, 0 };

  if (c == 27) {
    int seq1 = getchar();
    if (seq1 == EOF) return (key_event_t){ KEY_NONE, 0 };
    if (seq1 == 'O') {
      int seq2 = getchar();
      if (seq2 == 'H') return (key_event_t){ KEY_HOME, 0 };
      if (seq2 == 'F') return (key_event_t){ KEY_END, 0 };
      return (key_event_t){ KEY_NONE, 0 };
    }
    if (seq1 != '[') return (key_event_t){ KEY_NONE, 0 };
    int seq2 = getchar();
    if (seq2 == EOF) return (key_event_t){ KEY_NONE, 0 };
    switch (seq2) {
      case 'A': return (key_event_t){ KEY_UP, 0 };
      case 'B': return (key_event_t){ KEY_DOWN, 0 };
      case 'C': return (key_event_t){ KEY_RIGHT, 0 };
      case 'D': return (key_event_t){ KEY_LEFT, 0 };
      case 'H': return (key_event_t){ KEY_HOME, 0 };
      case 'F': return (key_event_t){ KEY_END, 0 };
      default: {
        if (seq2 >= '0' && seq2 <= '9') {
          int seq3 = getchar();
          if (seq3 == '~') {
            if (seq2 == '1' || seq2 == '7') return (key_event_t){ KEY_HOME, 0 };
            if (seq2 == '4' || seq2 == '8') return (key_event_t){ KEY_END, 0 };
            if (seq2 == '3') return (key_event_t){ KEY_DELETE, 0 };
          }
        }
        return (key_event_t){ KEY_NONE, 0 };
      }
    }
  }

  if (c == 127 || c == 8) return (key_event_t){ KEY_BACKSPACE, 0 };
  if (c == '\n' || c == '\r') return (key_event_t){ KEY_ENTER, 0 };
  if (isprint(c) || (unsigned char)c >= 0x80) return (key_event_t){ KEY_CHAR, c };
  return (key_event_t){ KEY_NONE, 0 };
}
#endif

static void term_restore(void) {
#ifndef _WIN32
  tcsetattr(STDIN_FILENO, TCSANOW, &saved_tio);
#endif
}

static char *read_line_with_history(ant_history_t *hist, const char *prompt) {
  char *line = malloc(MAX_LINE_LENGTH);
  if (!line) return NULL;
  int pos = 0;
  int len = 0;
  line[0] = '\0';
  repl_last_cursor_row = 0;

#ifndef _WIN32
  struct termios new_tio;
  tcgetattr(STDIN_FILENO, &saved_tio);
  new_tio = saved_tio;
  new_tio.c_lflag &= ~(ICANON | ECHO);
  tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
#endif

  for (;;) {
    key_event_t key = read_key();
    static const void *dispatch[] = {
      [KEY_NONE] = &&l_none,
      [KEY_UP] = &&l_up,
      [KEY_DOWN] = &&l_down,
      [KEY_LEFT] = &&l_left,
      [KEY_RIGHT] = &&l_right,
      [KEY_HOME] = &&l_home,
      [KEY_END] = &&l_end,
      [KEY_DELETE] = &&l_delete,
      [KEY_BACKSPACE] = &&l_backspace,
      [KEY_ENTER] = &&l_enter,
      [KEY_EOF] = &&l_eof,
      [KEY_CHAR] = &&l_char,
    };

    unsigned int t = (unsigned int)key.type;
    if (t < (sizeof(dispatch) / sizeof(*dispatch)) && dispatch[t]) goto *dispatch[t];
    goto l_none;

  l_enter:
    putchar('\n');
    term_restore();
    return line;

  l_eof:
    putchar('\n');
    term_restore();
    free(line);
    return NULL;

  l_up: {
    const char *h = ant_history_prev(hist);
    if (h) line_set(line, &pos, &len, h, prompt);
    continue;
  }

  l_down: {
    const char *h = ant_history_next(hist);
    if (h) line_set(line, &pos, &len, h, prompt);
    continue;
  }

  l_left:
    if (pos > 0) {
      pos = utf8_prev_pos(line, pos);
      move_cursor_only(line, pos, prompt);
    }
    continue;

  l_right:
    if (pos < len) {
      pos = utf8_next_pos(line, len, pos);
      move_cursor_only(line, pos, prompt);
    }
    continue;

  l_home:
    if (pos != 0) {
      pos = 0;
      move_cursor_only(line, pos, prompt);
    }
    continue;

  l_end:
    if (pos != len) {
      pos = len;
      move_cursor_only(line, pos, prompt);
    }
    continue;

  l_delete:
    line_delete(line, &pos, &len, prompt);
    continue;

  l_backspace:
    line_backspace(line, &pos, &len, prompt);
    continue;

  l_char:
    line_insert(line, &pos, &len, key.ch, prompt);
    continue;

  l_none:
    continue;
  }
}

ant_readline_result_t ant_readline(
  ant_history_t *hist,
  const char *prompt,
  highlight_state line_state,
  char **out_line
) {
  if (out_line) *out_line = NULL;

  hl_line_state = line_state;
  ctrl_c_pressed = 0;
  char *line = read_line_with_history(hist, prompt);

  if (ctrl_c_pressed > 0) {
    if (line) free(line);
    return ANT_READLINE_INTERRUPT;
  }
  if (!line) return ANT_READLINE_EOF;

  if (out_line) *out_line = line;
  return ANT_READLINE_LINE;
}
