#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <uv.h>

#ifdef _WIN32
    #include <direct.h>
    #include <io.h>
    #define mkdir(path, mode) _mkdir(path)
    #define access _access
    #define F_OK 0
#else
    #include <sys/stat.h>
    #include <dirent.h>
    #include <unistd.h>
#endif


#include "dowa/dowa.h"
#include "seobeo/seobeo.h"
#include "third_party/raylib/include/raylib.h"
#include "third_party/raylib/include/raygui.h"
#include "third_party/raylib/custom.h"

#ifndef POSTDOG_PATHS
  #define POSTDOG_PATHS "/Users/mrjunejune/zenbu/postdog/history"
#endif

#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 780
#define MAX_SCROLL_HEIGHT 10000

#define HEADER_BUFFER_LENGTH 1024 * 4
#define DEFAULT_TEXT_BUFFER_LENGTH 1024 * 4
#define URL_TEXT_BUFFER_LENGTH 1024 * 10
#define BODY_BUFFER_LENGTH 1024 * 1024 * 5
#define RESULT_BUFFER_LENGTH 1024 * 1024 * 5

#define URL_TEXT_DEFAULT "https://httpbin.org/get"
#define HEADER_TEXT_DEFAULT "Content-Type: application/json"
#define BODY_TEXT_DEFAULT ""
#define GET_PARAM_TEXT_DEFAULT "foo bar"

// ============================================================================
// TextArea Component
// ============================================================================

#define TEXT_SIZE_DEFAULT 10 // used to calcualte spacing
#define TEXT_AREA_LINE_HEIGHT 20
#define TEXT_AREA_PADDING 30
#define TEXT_AREA_CURSOR_WIDTH 2
#define TEXT_AREA_MAX_UNDO_STATES 64
#define TEXT_AREA_MAX_INSTANCES 8

typedef struct {
    char *text;
    int cursor_pos;
    int selection_start;
    int selection_end;
} TextAreaUndoEntry;

typedef struct {
    int id;                       // Unique ID for this text area
    int cursor_pos;               // Current cursor position in text
    int selection_start;          // Selection start (-1 if no selection)
    int selection_end;            // Selection end (-1 if no selection)
    float scroll_offset_y;        // Vertical scroll offset
    float scroll_offset_x;        // Horizontal scroll offset (for non-wrap mode)
    boolean is_selecting;         // Currently dragging to select
    boolean is_initialized;       // State has been initialized

    // Undo history
    TextAreaUndoEntry *undo_stack;  // Dowa array of undo entries
    int undo_index;                 // Current position in undo stack

    // Internal tracking
    double last_blink_time;
    boolean cursor_visible;
} TextAreaState;

static TextAreaState g_text_area_states[TEXT_AREA_MAX_INSTANCES] = {0};
static int g_text_area_state_count = 0;
static char *g_clipboard_text = NULL;
static Dowa_Arena *g_text_area_arena = NULL;

// Helper functions
static int TA_Min_Int(int a, int b) { return a < b ? a : b; }
static int TA_Max_Int(int a, int b) { return a > b ? a : b; }
static float TA_Min_Float(float a, float b) { return a < b ? a : b; }
static float TA_Max_Float(float a, float b) { return a > b ? a : b; }

static TextAreaState* GetTextAreaState(int id) {
    for (int i = 0; i < g_text_area_state_count; i++) {
        if (g_text_area_states[i].id == id && g_text_area_states[i].is_initialized) {
            return &g_text_area_states[i];
        }
    }
    return NULL;
}

static TextAreaState* CreateTextAreaState(int id) {
    if (g_text_area_state_count >= TEXT_AREA_MAX_INSTANCES) {
        return &g_text_area_states[0]; // Reuse first slot
    }

    TextAreaState *state = &g_text_area_states[g_text_area_state_count++];
    memset(state, 0, sizeof(TextAreaState));
    state->id = id;
    state->selection_start = -1;
    state->selection_end = -1;
    state->undo_index = -1;
    state->cursor_visible = TRUE;
    state->is_initialized = TRUE;
    return state;
}

static void GetLineAndColumn(const char *text, int pos, int *out_line, int *out_column) {
    int line = 0;
    int column = 0;
    for (int i = 0; i < pos && text[i] != '\0'; i++) {
        if (text[i] == '\n') {
            line++;
            column = 0;
        } else {
            column++;
        }
    }
    *out_line = line;
    *out_column = column;
}

static int GetPosFromLineColumn(const char *text, int line, int column) {
    int current_line = 0;
    int current_col = 0;
    int i = 0;

    while (text[i] != '\0') {
        if (current_line == line && current_col == column) {
            return i;
        }
        if (text[i] == '\n') {
            if (current_line == line) {
                return i;
            }
            current_line++;
            current_col = 0;
        } else {
            current_col++;
        }
        i++;
    }
    return i;
}

static int GetLineStart(const char *text, int pos) {
    int i = pos;
    while (i > 0 && text[i - 1] != '\n') {
        i--;
    }
    return i;
}

static int GetLineEnd(const char *text, int pos) {
    int i = pos;
    int len = strlen(text);
    while (i < len && text[i] != '\n') {
        i++;
    }
    return i;
}

static int CountLines(const char *text) {
    int count = 1;
    for (int i = 0; text[i] != '\0'; i++) {
        if (text[i] == '\n') count++;
    }
    return count;
}

static int MeasureTextRange(const char *text, int start, int end, int font_size) {
    if (start >= end) return 0;

    char temp[1024];
    int len = TA_Min_Int(end - start, 1023);
    strncpy(temp, text + start, len);
    temp[len] = '\0';

    return MeasureTextEx(GuiGetFont(), temp, font_size, TEXT_SIZE_DEFAULT/GuiGetStyle(DEFAULT, TEXT_SIZE)).x;
}

static int GetCharIndexFromPos(const char *text, Rectangle bounds, Vector2 pos,
                                boolean wrap, float scroll_y, int font_size, int line_height) {
    if (!text || strlen(text) == 0) return 0;

    float content_x = bounds.x + TEXT_AREA_PADDING;
    float content_y = bounds.y + TEXT_AREA_PADDING - scroll_y;
    float content_width = bounds.width - TEXT_AREA_PADDING * 2;

    int text_len = strlen(text);

    float click_line_y = (pos.y - content_y) / line_height;
    int target_visual_line = (int)click_line_y;
    if (target_visual_line < 0) target_visual_line = 0;

    int current_visual_line = 0;
    int i = 0;
    int line_char_start = 0;

    while (i <= text_len) {
        boolean is_newline = (i < text_len && text[i] == '\n');

        if (wrap && i > line_char_start) {
            int line_width = MeasureTextRange(text, line_char_start, i, font_size);
            if (line_width > content_width && i > line_char_start + 1) {
                int wrap_pos = i - 1;
                for (int j = i - 1; j > line_char_start; j--) {
                    if (text[j] == ' ') {
                        wrap_pos = j;
                        break;
                    }
                }

                if (current_visual_line == target_visual_line) {
                    float click_x = pos.x - content_x;
                    int best_pos = line_char_start;
                    float best_dist = 99999;

                    for (int k = line_char_start; k <= wrap_pos; k++) {
                        int char_x = MeasureTextRange(text, line_char_start, k, font_size);
                        float dist = (float)(click_x - char_x);
                        if (dist < 0) dist = -dist;
                        if (dist < best_dist) {
                            best_dist = dist;
                            best_pos = k;
                        }
                    }
                    return best_pos;
                }

                current_visual_line++;
                line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos;
                i = line_char_start;
                continue;
            }
        }

        if (is_newline || i == text_len) {
            if (current_visual_line == target_visual_line || i == text_len) {
                float click_x = pos.x - content_x;
                int line_end = i;
                int best_pos = line_char_start;
                float best_dist = 99999;

                for (int k = line_char_start; k <= line_end; k++) {
                    int char_x = MeasureTextRange(text, line_char_start, k, font_size);
                    float dist = (float)(click_x - char_x);
                    if (dist < 0) dist = -dist;
                    if (dist < best_dist) {
                        best_dist = dist;
                        best_pos = k;
                    }
                }
                return TA_Min_Int(best_pos, text_len);
            }

            current_visual_line++;
            line_char_start = i + 1;
        }

        i++;
    }

    return text_len;
}

static Vector2 GetCursorScreenPos(const char *text, int cursor_pos, Rectangle bounds,
                                   boolean wrap, float scroll_y, int font_size, int line_height) {
    float content_x = bounds.x + TEXT_AREA_PADDING;
    float content_y = bounds.y + TEXT_AREA_PADDING - scroll_y;
    float content_width = bounds.width - TEXT_AREA_PADDING * 2;

    if (!text || cursor_pos == 0) {
        return (Vector2){content_x, content_y};
    }

    int text_len = strlen(text);
    cursor_pos = TA_Min_Int(cursor_pos, text_len);

    int visual_line = 0;
    int line_char_start = 0;

    for (int i = 0; i <= cursor_pos; i++) {
        if (i == cursor_pos) {
            float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size);
            float y = content_y + visual_line * line_height;
            return (Vector2){x, y};
        }

        if (text[i] == '\n') {
            visual_line++;
            line_char_start = i + 1;
        } else if (wrap) {
            int line_width = MeasureTextRange(text, line_char_start, i + 1, font_size);
            if (line_width > content_width && i > line_char_start) {
                int wrap_pos = i;
                for (int j = i; j > line_char_start; j--) {
                    if (text[j] == ' ') {
                        wrap_pos = j;
                        break;
                    }
                }

                if (cursor_pos <= wrap_pos) {
                    float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size);
                    float y = content_y + visual_line * line_height;
                    return (Vector2){x, y};
                }

                visual_line++;
                line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos;
            }
        }
    }

    float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size);
    float y = content_y + visual_line * line_height;
    return (Vector2){x, y};
}

static float GetContentHeight(const char *text, Rectangle bounds, boolean wrap,
                               int font_size, int line_height) {
    if (!text || strlen(text) == 0) return line_height;

    float content_width = bounds.width - TEXT_AREA_PADDING * 2;
    int visual_lines = 1;
    int line_char_start = 0;
    int text_len = strlen(text);

    for (int i = 0; i <= text_len; i++) {
        if (i == text_len || text[i] == '\n') {
            visual_lines++;
            line_char_start = i + 1;
        } else if (wrap) {
            int line_width = MeasureTextRange(text, line_char_start, i + 1, font_size);
            if (line_width > content_width && i > line_char_start) {
                int wrap_pos = i;
                for (int j = i; j > line_char_start; j--) {
                    if (text[j] == ' ') {
                        wrap_pos = j;
                        break;
                    }
                }
                visual_lines++;
                line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos;
            }
        }
    }

    return visual_lines * line_height;
}

static void PushUndoState(TextAreaState *state, const char *text, Dowa_Arena *arena) {
    TextAreaUndoEntry entry;
    entry.text = Dowa_String_Copy_Arena((char*)text, arena);
    entry.cursor_pos = state->cursor_pos;
    entry.selection_start = state->selection_start;
    entry.selection_end = state->selection_end;

    if (state->undo_index < (int)Dowa_Array_Length(state->undo_stack) - 1) {
        dowa__header(state->undo_stack)->length = state->undo_index + 1;
    }

    Dowa_Array_Push_Arena(state->undo_stack, entry, arena);
    state->undo_index = Dowa_Array_Length(state->undo_stack) - 1;

    if (Dowa_Array_Length(state->undo_stack) > TEXT_AREA_MAX_UNDO_STATES) {
        for (int i = 0; i < (int)Dowa_Array_Length(state->undo_stack) - 1; i++) {
            state->undo_stack[i] = state->undo_stack[i + 1];
        }
        dowa__header(state->undo_stack)->length--;
        state->undo_index--;
    }
}

static boolean PerformUndo(TextAreaState *state, char *text, int text_size) {
    if (state->undo_index <= 0) return FALSE;

    state->undo_index--;
    TextAreaUndoEntry *entry = &state->undo_stack[state->undo_index];

    strncpy(text, entry->text, text_size - 1);
    text[text_size - 1] = '\0';
    state->cursor_pos = entry->cursor_pos;
    state->selection_start = entry->selection_start;
    state->selection_end = entry->selection_end;

    return TRUE;
}

static boolean PerformRedo(TextAreaState *state, char *text, int text_size) {
    if (state->undo_index >= (int)Dowa_Array_Length(state->undo_stack) - 1) return FALSE;

    state->undo_index++;
    TextAreaUndoEntry *entry = &state->undo_stack[state->undo_index];

    strncpy(text, entry->text, text_size - 1);
    text[text_size - 1] = '\0';
    state->cursor_pos = entry->cursor_pos;
    state->selection_start = entry->selection_start;
    state->selection_end = entry->selection_end;

    return TRUE;
}

static void InsertTextAtCursor(char *text, int text_size, int cursor_pos,
                                const char *insert_text, int *new_cursor_pos) {
    int text_len = strlen(text);
    int insert_len = strlen(insert_text);

    if (text_len + insert_len >= text_size - 1) {
        insert_len = text_size - 1 - text_len;
        if (insert_len <= 0) return;
    }

    memmove(text + cursor_pos + insert_len,
            text + cursor_pos,
            text_len - cursor_pos + 1);

    memcpy(text + cursor_pos, insert_text, insert_len);

    *new_cursor_pos = cursor_pos + insert_len;
}

static void DeleteTextRange(char *text, int start, int end) {
    if (start >= end) return;
    int text_len = strlen(text);
    if (start < 0) start = 0;
    if (end > text_len) end = text_len;

    memmove(text + start, text + end, text_len - end + 1);
}

static char* GetSelectedText(const char *text, int sel_start, int sel_end, Dowa_Arena *arena) {
    if (sel_start < 0 || sel_end < 0 || sel_start >= sel_end) return NULL;

    int len = sel_end - sel_start;
    char *result = Dowa_Arena_Allocate(arena, len + 1);
    strncpy(result, text + sel_start, len);
    result[len] = '\0';
    return result;
}

boolean GuiTextArea(int id, Rectangle bounds, char *text, int text_size, boolean is_edit_mode,
                    boolean should_text_wrap, Dowa_Arena *arena) {
    boolean should_toggle_edit_mode = FALSE;

    // Get or create state for this text area
    TextAreaState *state = GetTextAreaState(id);
    if (!state) {
        state = CreateTextAreaState(id);
        state->cursor_pos = strlen(text);
        state->last_blink_time = GetTime();
        PushUndoState(state, text, arena);
    }

    int text_len = strlen(text);
    Vector2 mouse_pos = GetMousePosition();
    boolean mouse_in_bounds = CheckCollisionPointRec(mouse_pos, bounds);

    // Handle click to enter/exit edit mode
    if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
        if (mouse_in_bounds && !is_edit_mode) {
            should_toggle_edit_mode = TRUE;
        } else if (!mouse_in_bounds && is_edit_mode) {
            should_toggle_edit_mode = TRUE;
        }
    }

    // Content area
    float content_height = GetContentHeight(text, bounds, should_text_wrap,
                                            GuiGetStyle(DEFAULT, TEXT_SIZE), TEXT_AREA_LINE_HEIGHT);
    float visible_height = bounds.height - TEXT_AREA_PADDING * 2;
    float max_scroll = TA_Max_Float(0, content_height - visible_height);

    // Handle scrolling
    float wheel = GetMouseWheelMove();
    if (mouse_in_bounds && wheel != 0) {
        state->scroll_offset_y -= wheel * TEXT_AREA_LINE_HEIGHT * 3;
        state->scroll_offset_y = TA_Max_Float(0, TA_Min_Float(state->scroll_offset_y, max_scroll));
    }

    if (is_edit_mode) {
        boolean ctrl_pressed = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) ||
                               IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_RIGHT_SUPER);
        boolean shift_pressed = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
        boolean text_changed = FALSE;

        double current_time = GetTime();
        if (current_time - state->last_blink_time > 0.5) {
            state->cursor_visible = !state->cursor_visible;
            state->last_blink_time = current_time;
        }

        // Mouse Selection
        if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && mouse_in_bounds) {
            int click_pos = GetCharIndexFromPos(text, bounds, mouse_pos, should_text_wrap,
                                                 state->scroll_offset_y, GuiGetStyle(DEFAULT, TEXT_SIZE),
                                                 TEXT_AREA_LINE_HEIGHT);
            state->cursor_pos = click_pos;
            state->selection_start = -1;
            state->selection_end = -1;
            state->is_selecting = TRUE;
            state->cursor_visible = TRUE;
            state->last_blink_time = current_time;
        }

        if (state->is_selecting && IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {
            int drag_pos = GetCharIndexFromPos(text, bounds, mouse_pos, should_text_wrap,
                                                state->scroll_offset_y, GuiGetStyle(DEFAULT, TEXT_SIZE),
                                                TEXT_AREA_LINE_HEIGHT);
            if (drag_pos != state->cursor_pos) {
                if (state->selection_start < 0) {
                    state->selection_start = state->cursor_pos;
                }
                state->selection_end = drag_pos;
            }
        }

        if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) {
            state->is_selecting = FALSE;
            if (state->selection_start >= 0 && state->selection_end >= 0) {
                if (state->selection_start > state->selection_end) {
                    int temp = state->selection_start;
                    state->selection_start = state->selection_end;
                    state->selection_end = temp;
                }
                if (state->selection_start == state->selection_end) {
                    state->selection_start = -1;
                    state->selection_end = -1;
                }
            }
        }

        // Ctrl+A: Select All
        if (ctrl_pressed && IsKeyPressed(KEY_A)) {
            state->selection_start = 0;
            state->selection_end = text_len;
            state->cursor_pos = text_len;
        }

        // Ctrl+C: Copy
        if (ctrl_pressed && IsKeyPressed(KEY_C)) {
            if (state->selection_start >= 0 && state->selection_end >= 0 &&
                state->selection_start != state->selection_end) {
                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
                char *selected = GetSelectedText(text, sel_min, sel_max, arena);
                if (selected) {
                    if (g_clipboard_text) free(g_clipboard_text);
                    g_clipboard_text = strdup(selected);
                    SetClipboardText(g_clipboard_text);
                }
            }
        }

        // Ctrl+X: Cut
        if (ctrl_pressed && IsKeyPressed(KEY_X)) {
            if (state->selection_start >= 0 && state->selection_end >= 0 &&
                state->selection_start != state->selection_end) {
                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
                char *selected = GetSelectedText(text, sel_min, sel_max, arena);
                if (selected) {
                    if (g_clipboard_text) free(g_clipboard_text);
                    g_clipboard_text = strdup(selected);
                    SetClipboardText(g_clipboard_text);
                }

                PushUndoState(state, text, arena);
                DeleteTextRange(text, sel_min, sel_max);
                state->cursor_pos = sel_min;
                state->selection_start = -1;
                state->selection_end = -1;
                text_changed = TRUE;
            }
        }

        // Ctrl+V: Paste
        if (ctrl_pressed && IsKeyPressed(KEY_V)) {
            const char *clipboard = GetClipboardText();
            if (clipboard && strlen(clipboard) > 0) {
                if (state->selection_start >= 0 && state->selection_end >= 0 &&
                    state->selection_start != state->selection_end) {
                    int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
                    int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
                    PushUndoState(state, text, arena);
                    DeleteTextRange(text, sel_min, sel_max);
                    state->cursor_pos = sel_min;
                    state->selection_start = -1;
                    state->selection_end = -1;
                } else {
                    PushUndoState(state, text, arena);
                }

                int new_cursor;
                InsertTextAtCursor(text, text_size, state->cursor_pos, clipboard, &new_cursor);
                state->cursor_pos = new_cursor;
                text_changed = TRUE;
            }
        }

        // Ctrl+Z: Undo / Ctrl+Shift+Z: Redo
        if (ctrl_pressed && IsKeyPressed(KEY_Z)) {
            if (shift_pressed) {
                PerformRedo(state, text, text_size);
            } else {
                PerformUndo(state, text, text_size);
            }
        }

        // Arrow Keys Navigation
        if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) {
            if (state->selection_start >= 0 && !shift_pressed) {
                state->cursor_pos = TA_Min_Int(state->selection_start, state->selection_end);
                state->selection_start = -1;
                state->selection_end = -1;
            } else if (state->cursor_pos > 0) {
                if (shift_pressed) {
                    if (state->selection_start < 0) {
                        state->selection_start = state->cursor_pos;
                        state->selection_end = state->cursor_pos;
                    }
                    state->cursor_pos--;
                    state->selection_end = state->cursor_pos;
                } else {
                    state->cursor_pos--;
                }
            }
            state->cursor_visible = TRUE;
            state->last_blink_time = current_time;
        }

        if (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) {
            if (state->selection_start >= 0 && !shift_pressed) {
                state->cursor_pos = TA_Max_Int(state->selection_start, state->selection_end);
                state->selection_start = -1;
                state->selection_end = -1;
            } else if (state->cursor_pos < text_len) {
                if (shift_pressed) {
                    if (state->selection_start < 0) {
                        state->selection_start = state->cursor_pos;
                        state->selection_end = state->cursor_pos;
                    }
                    state->cursor_pos++;
                    state->selection_end = state->cursor_pos;
                } else {
                    state->cursor_pos++;
                }
            }
            state->cursor_visible = TRUE;
            state->last_blink_time = current_time;
        }

        if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) {
            int line, col;
            GetLineAndColumn(text, state->cursor_pos, &line, &col);
            if (line > 0) {
                int new_pos = GetPosFromLineColumn(text, line - 1, col);
                if (shift_pressed) {
                    if (state->selection_start < 0) {
                        state->selection_start = state->cursor_pos;
                        state->selection_end = state->cursor_pos;
                    }
                    state->selection_end = new_pos;
                } else {
                    state->selection_start = -1;
                    state->selection_end = -1;
                }
                state->cursor_pos = new_pos;
            }
            state->cursor_visible = TRUE;
            state->last_blink_time = current_time;
        }

        if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) {
            int line, col;
            GetLineAndColumn(text, state->cursor_pos, &line, &col);
            int total_lines = CountLines(text);
            if (line < total_lines - 1) {
                int new_pos = GetPosFromLineColumn(text, line + 1, col);
                if (shift_pressed) {
                    if (state->selection_start < 0) {
                        state->selection_start = state->cursor_pos;
                        state->selection_end = state->cursor_pos;
                    }
                    state->selection_end = new_pos;
                } else {
                    state->selection_start = -1;
                    state->selection_end = -1;
                }
                state->cursor_pos = new_pos;
            }
            state->cursor_visible = TRUE;
            state->last_blink_time = current_time;
        }

        // Home/End keys
        if (IsKeyPressed(KEY_HOME)) {
            int line_start = GetLineStart(text, state->cursor_pos);
            if (shift_pressed) {
                if (state->selection_start < 0) {
                    state->selection_start = state->cursor_pos;
                    state->selection_end = state->cursor_pos;
                }
                state->selection_end = line_start;
            } else {
                state->selection_start = -1;
                state->selection_end = -1;
            }
            state->cursor_pos = line_start;
        }

        if (IsKeyPressed(KEY_END)) {
            int line_end = GetLineEnd(text, state->cursor_pos);
            if (shift_pressed) {
                if (state->selection_start < 0) {
                    state->selection_start = state->cursor_pos;
                    state->selection_end = state->cursor_pos;
                }
                state->selection_end = line_end;
            } else {
                state->selection_start = -1;
                state->selection_end = -1;
            }
            state->cursor_pos = line_end;
        }

        // Text Input
        if (!ctrl_pressed) {
            int key = GetCharPressed();
            while (key > 0) {
                if (key >= 32 && key <= 126) {
                    if (state->selection_start >= 0 && state->selection_end >= 0 &&
                        state->selection_start != state->selection_end) {
                        int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
                        int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
                        PushUndoState(state, text, arena);
                        DeleteTextRange(text, sel_min, sel_max);
                        state->cursor_pos = sel_min;
                        state->selection_start = -1;
                        state->selection_end = -1;
                        text_changed = TRUE;
                    } else if (!text_changed) {
                        PushUndoState(state, text, arena);
                    }

                    char insert_str[2] = {(char)key, '\0'};
                    int new_cursor;
                    InsertTextAtCursor(text, text_size, state->cursor_pos, insert_str, &new_cursor);
                    state->cursor_pos = new_cursor;
                    text_changed = TRUE;
                }
                key = GetCharPressed();
            }
        }

        // Enter key
        if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) {
            if (state->selection_start >= 0 && state->selection_end >= 0 &&
                state->selection_start != state->selection_end) {
                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
                PushUndoState(state, text, arena);
                DeleteTextRange(text, sel_min, sel_max);
                state->cursor_pos = sel_min;
                state->selection_start = -1;
                state->selection_end = -1;
            } else {
                PushUndoState(state, text, arena);
            }

            int new_cursor;
            InsertTextAtCursor(text, text_size, state->cursor_pos, "\n", &new_cursor);
            state->cursor_pos = new_cursor;
            text_changed = TRUE;
        }

        // Backspace
        if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) {
            if (state->selection_start >= 0 && state->selection_end >= 0 &&
                state->selection_start != state->selection_end) {
                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
                PushUndoState(state, text, arena);
                DeleteTextRange(text, sel_min, sel_max);
                state->cursor_pos = sel_min;
                state->selection_start = -1;
                state->selection_end = -1;
                text_changed = TRUE;
            } else if (state->cursor_pos > 0) {
                PushUndoState(state, text, arena);
                DeleteTextRange(text, state->cursor_pos - 1, state->cursor_pos);
                state->cursor_pos--;
                text_changed = TRUE;
            }
        }

        // Delete key
        if (IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) {
            text_len = strlen(text);
            if (state->selection_start >= 0 && state->selection_end >= 0 &&
                state->selection_start != state->selection_end) {
                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
                PushUndoState(state, text, arena);
                DeleteTextRange(text, sel_min, sel_max);
                state->cursor_pos = sel_min;
                state->selection_start = -1;
                state->selection_end = -1;
                text_changed = TRUE;
            } else if (state->cursor_pos < text_len) {
                PushUndoState(state, text, arena);
                DeleteTextRange(text, state->cursor_pos, state->cursor_pos + 1);
                text_changed = TRUE;
            }
        }

        // Tab key
        if (IsKeyPressed(KEY_TAB)) {
            if (state->selection_start >= 0 && state->selection_end >= 0 &&
                state->selection_start != state->selection_end) {
                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
                PushUndoState(state, text, arena);
                DeleteTextRange(text, sel_min, sel_max);
                state->cursor_pos = sel_min;
                state->selection_start = -1;
                state->selection_end = -1;
            } else {
                PushUndoState(state, text, arena);
            }

            int new_cursor;
            InsertTextAtCursor(text, text_size, state->cursor_pos, "    ", &new_cursor);
            state->cursor_pos = new_cursor;
            text_changed = TRUE;
        }

        // Auto-scroll to keep cursor visible
        Vector2 cursor_screen = GetCursorScreenPos(text, state->cursor_pos, bounds,
                                                    should_text_wrap, state->scroll_offset_y,
                                                    GuiGetStyle(DEFAULT, TEXT_SIZE), TEXT_AREA_LINE_HEIGHT);

        float visible_top = bounds.y + TEXT_AREA_PADDING;
        float visible_bottom = bounds.y + bounds.height - TEXT_AREA_PADDING - TEXT_AREA_LINE_HEIGHT;

        if (cursor_screen.y < visible_top) {
            state->scroll_offset_y -= visible_top - cursor_screen.y;
        } else if (cursor_screen.y > visible_bottom) {
            state->scroll_offset_y += cursor_screen.y - visible_bottom;
        }

        state->scroll_offset_y = TA_Max_Float(0, TA_Min_Float(state->scroll_offset_y, max_scroll));
    }

    // Drawing
    DrawRectangleRounded(bounds, 0.2, 1, is_edit_mode ? DARKGRAY : (Color){40, 40, 40, 255});
    // DrawRectangleRec(bounds, is_edit_mode ? DARKGRAY : (Color){40, 40, 40, 255});
    // DrawRectangleLinesEx(bounds, 1, is_edit_mode ? WHITE : GRAY);
    DrawRectangleRoundedLines(bounds, 0.2, 1, is_edit_mode ? WHITE : GRAY);

    BeginScissorMode((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height);

    float content_x = bounds.x + TEXT_AREA_PADDING;
    float content_y = bounds.y + TEXT_AREA_PADDING - state->scroll_offset_y;
    float content_width = bounds.width - TEXT_AREA_PADDING * 2;

    text_len = strlen(text);

    // Draw selection highlight
    if (state->selection_start >= 0 && state->selection_end >= 0 &&
        state->selection_start != state->selection_end) {
        int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
        int sel_max = TA_Max_Int(state->selection_start, state->selection_end);

        int line_char_start = 0;
        int visual_line = 0;

        for (int i = 0; i <= text_len; i++) {
            boolean is_end = (i == text_len);
            boolean is_newline = (!is_end && text[i] == '\n');
            boolean should_draw_line = is_end || is_newline;

            if (should_text_wrap && !is_end && !is_newline) {
                int line_width = MeasureTextRange(text, line_char_start, i + 1, GuiGetStyle(DEFAULT, TEXT_SIZE));
                if (line_width > content_width && i > line_char_start) {
                    should_draw_line = TRUE;
                }
            }

            if (should_draw_line) {
                int line_end = i;

                if (sel_min < line_end && sel_max > line_char_start) {
                    int highlight_start = TA_Max_Int(sel_min, line_char_start);
                    int highlight_end = TA_Min_Int(sel_max, line_end);

                    float x1 = content_x + MeasureTextRange(text, line_char_start, highlight_start, GuiGetStyle(DEFAULT, TEXT_SIZE));
                    float x2 = content_x + MeasureTextRange(text, line_char_start, highlight_end, GuiGetStyle(DEFAULT, TEXT_SIZE));
                    float y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT;

                    DrawRectangle((int)x1, (int)y, (int)(x2 - x1), TEXT_AREA_LINE_HEIGHT,
                                  Fade(SKYBLUE, 0.5f));
                }

                visual_line++;
                line_char_start = i + 1;
            }
        }
    }

    // Draw text with wrapping support
    if (should_text_wrap) {
        int line_char_start = 0;
        int visual_line = 0;

        for (int i = 0; i <= text_len; i++) {
            boolean is_end = (i == text_len);
            boolean is_newline = (!is_end && text[i] == '\n');
            boolean should_draw_line = is_end || is_newline;

            if (!is_end && !is_newline) {
                int line_width = MeasureTextRange(text, line_char_start, i + 1, GuiGetStyle(DEFAULT, TEXT_SIZE));
                if (line_width > content_width && i > line_char_start) {
                    should_draw_line = TRUE;
                }
            }

            if (should_draw_line && i > line_char_start) {
                char line_buffer[1024];
                int line_len = TA_Min_Int(i - line_char_start, 1023);
                strncpy(line_buffer, text + line_char_start, line_len);
                line_buffer[line_len] = '\0';

                Vector2 draw_text_vector = {
                  .x = content_x,
                  .y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT
                };
                DrawTextEx(GuiGetFont(), line_buffer, draw_text_vector,
                        GuiGetStyle(DEFAULT, TEXT_SIZE), TEXT_SIZE_DEFAULT/GuiGetStyle(DEFAULT, TEXT_SIZE), WHITE);

                visual_line++;
                line_char_start = is_newline ? i + 1 : i;
            } else if (is_newline) {
                visual_line++;
                line_char_start = i + 1;
            }
        }
    } else {
        int line_start = 0;
        int visual_line = 0;

        for (int i = 0; i <= text_len; i++) {
            if (i == text_len || text[i] == '\n') {
                if (i > line_start) {
                    char line_buffer[1024];
                    int line_len = TA_Min_Int(i - line_start, 1023);
                    strncpy(line_buffer, text + line_start, line_len);
                    line_buffer[line_len] = '\0';

                    Vector2 draw_text_vector = {
                      .x = content_x,
                      .y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT
                    };
                    DrawTextEx(GuiGetFont(), line_buffer, draw_text_vector ,
                            GuiGetStyle(DEFAULT, TEXT_SIZE), TEXT_SIZE_DEFAULT/GuiGetStyle(DEFAULT, TEXT_SIZE), WHITE);
                }
                visual_line++;
                line_start = i + 1;
            }
        }
    }

    // Draw cursor
    if (is_edit_mode && state->cursor_visible) {
        Vector2 cursor_pos = GetCursorScreenPos(text, state->cursor_pos, bounds,
                                                 should_text_wrap, state->scroll_offset_y,
                                                 GuiGetStyle(DEFAULT, TEXT_SIZE), TEXT_AREA_LINE_HEIGHT);

        DrawRectangle((int)cursor_pos.x, (int)cursor_pos.y,
                      TEXT_AREA_CURSOR_WIDTH, TEXT_AREA_LINE_HEIGHT, WHITE);
    }

    EndScissorMode();

    // Draw scrollbar if needed
    if (max_scroll > 0) {
        float scrollbar_height = (visible_height / content_height) * visible_height;
        float scrollbar_y = bounds.y + TEXT_AREA_PADDING +
                           (state->scroll_offset_y / max_scroll) * (visible_height - scrollbar_height);

        DrawRectangle((int)(bounds.x + bounds.width - 8), (int)scrollbar_y,
                      6, (int)scrollbar_height, Fade(WHITE, 0.3f));
    }

    return should_toggle_edit_mode;
}

void GuiTextAreaResetState(int id) {
    TextAreaState *state = GetTextAreaState(id);
    if (state) {
        if (state->undo_stack) {
            Dowa_Array_Free(state->undo_stack);
            state->undo_stack = NULL;
        }
        state->is_initialized = FALSE;
    }
}

void GuiTextAreaResetAllStates(void) {
    for (int i = 0; i < g_text_area_state_count; i++) {
        if (g_text_area_states[i].undo_stack) {
            Dowa_Array_Free(g_text_area_states[i].undo_stack);
            g_text_area_states[i].undo_stack = NULL;
        }
        g_text_area_states[i].is_initialized = FALSE;
    }
    g_text_area_state_count = 0;
}

// ============================================================================
// End TextArea Component
// ============================================================================

typedef Dowa_KV(char*, char*) INPUT_HASHMAP;

typedef struct {
  char *data;
  size_t size;
} ResponseBuffer;

typedef struct {
  char *filename;
  char *title;
  Rectangle rect;
  long time_modified;
  boolean deleted;
} HistoryItem;

typedef struct {
  Rectangle rectangle;
  char *label;
  bool active;
} TabItem;

typedef enum {
  TAB_HEADER = 0,
  TAB_BODY,
  TAB_GET_PARAMS,
  TAB_WEBSOCKET,
  TAB_LENGTH
} PostDog_Tab_Enum;

// Text area IDs
#define TEXT_AREA_ID_INPUT_HEADER   1
#define TEXT_AREA_ID_INPUT_BODY     2
#define TEXT_AREA_ID_INPUT_PARAMS   3
#define TEXT_AREA_ID_INPUT_WS       4
#define TEXT_AREA_ID_RESULT         5

static uint32 counter = 0;
static uv_mutex_t history_mutex;
static uv_loop_t *main_loop = NULL;
HistoryItem *history_items = NULL;
HistoryItem *new_history_items = NULL;

// Global UI state
char *url_input_text = NULL;
char *result_text = NULL;
char **input_body_array = NULL;
int active_method_dropdown = 0;
int active_input_tab = 0;
Seobeo_WebSocket *ws = NULL;
boolean WS_BREAK = FALSE;
uv_thread_t websocket_thread_id;
Color TEXT_COLOR = BLACK;
boolean LOADING = FALSE;

int CompareHistoryItemsByDate(const void *a, const void *b) {
  HistoryItem *itemA = (HistoryItem *)a;
  HistoryItem *itemB = (HistoryItem *)b;
  return (itemB->time_modified - itemA->time_modified);
}

char *PostDog_Extract_Title(const char *filename)
{
  char full_file_path[512] = {0};
  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
  FILE *file = fopen(full_file_path, "r");
  if (!file)
    return strdup(filename);

  char *title = malloc(sizeof(char) * 512);
  if (!fgets(title, 512, file)) {
    fclose(file);
    free(title);
    return strdup(filename);
  }
  fclose(file);

  // Strip trailing newline
  title[strcspn(title, "\n")] = '\0';

  return title;
}

// TODO: Make this into generic fucntion so I can use it across different thing.
void PostDog_List_Directory(const char *path, HistoryItem **p_file_arr)
{
  HistoryItem *file_arr = *p_file_arr;
#ifdef _WIN32
  struct _finddata_t fileinfo;
  intptr_t handle;
  char search_path[256];
  sprintf(search_path, "%s\\*", path);

  if ((handle = _findfirst(search_path, &fileinfo)) == -1L) {
    printf("Directory is empty or cannot be read.\n");
  } else {
    do {
      HistoryItem item = {0};
      item.filename = strdup(fileinfo.name);
      item.title = PostDog_Extract_Title(fileinfo.name);
      item.time_modified = fileinfo.time_write;
      item.deleted = FALSE;
      Dowa_Array_Push(file_arr, item);
    } while (_findnext(handle, &fileinfo) == 0);
    _findclose(handle);
  }
#else
  struct dirent *entry;
  struct stat file_stat;
  DIR *dp = opendir(path);
  if (dp == NULL) return;

  char full_path[256];
  while ((entry = readdir(dp)))
  {
    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
      continue;
    snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
    if (stat(full_path, &file_stat) == 0)
    {
      HistoryItem item = {0};
      item.filename = strdup(entry->d_name);
      item.title = PostDog_Extract_Title(entry->d_name);
      item.time_modified = file_stat.st_mtime;
      item.deleted = FALSE;
      Dowa_Array_Push(file_arr, item);
    }
  }
  closedir(dp);
#endif

  // Update the caller's pointer in case array was reallocated
  *p_file_arr = file_arr;

  int count = Dowa_Array_Length(file_arr);
  if (count > 1) {
    qsort(file_arr, count, sizeof(HistoryItem), CompareHistoryItemsByDate);
  }
}

int PostDog_History_Load(HistoryItem **p_history_files)
{
  if (access(POSTDOG_PATHS, F_OK) == -1)
  {
    printf("Directory '%s' not found. Creating it...\n", POSTDOG_PATHS);
    if (mkdir(POSTDOG_PATHS, 0777) != 0)
      return -1;
    return 0;
  }
  printf("Directory '%s' already exists.\n", POSTDOG_PATHS);
  PostDog_List_Directory(POSTDOG_PATHS, p_history_files);
  return 0;
}

bool InArea(Vector2 mouse_position, Rectangle area)
{
  return (
    mouse_position.x >= area.x &&
    mouse_position.x < area.x + area.width &&
    mouse_position.y >= area.y &&
    mouse_position.y < area.y + area.height
  );
}

bool Clicked(Vector2 mouse_position, Rectangle area)
{
  return (InArea(mouse_position, area) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
}

// -------- END of UI ---- //

char *PostDog_Enum_To_String(int active_enum)
{
  switch(active_enum)
  {
    case 0: return "GET";
    case 1: return "POST";
    case 2: return "PUT";
    case 3: return "DELETE";
  }
  return 0;
}

char *PostDog_Construct_URL(char *filename, char *out_buffer, size_t buffer_size)
{
  snprintf(out_buffer, buffer_size, "%s/%s", POSTDOG_PATHS, filename);
  return out_buffer;
}

boolean PostDog_History_CreateFile(char *filename, char* values)
{
  char full_file_path[512] = {0};
  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
  FILE *file = fopen(full_file_path, "w");
  if (!file)
  {
    fprintf(stderr, "Failed to create a file: %s\n", full_file_path);
    return FALSE;
  }
  fwrite(values, 1, strlen(values), file);
  fclose(file);
  return TRUE;
}

void PostDog_Request_SaveFile(void)
{
  const char *method = PostDog_Enum_To_String(active_method_dropdown);
  size_t new_file_size = 1024 * 1024;
  Dowa_Arena *arena = Dowa_Arena_Create(1024 * 1024 * 2);
  char *new_file = Dowa_Arena_Allocate(arena, 1024 * 1024);
  char *title = Dowa_Arena_Allocate(arena, strlen(method) + strlen(url_input_text) + 2);
  sprintf(title, "%s %s", method, url_input_text);
  snprintf(
      new_file,
      new_file_size,
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n",
      title,
      url_input_text,
      method,
      input_body_array[TAB_HEADER],
      input_body_array[TAB_BODY],
      input_body_array[TAB_GET_PARAMS],
      input_body_array[TAB_WEBSOCKET],
      result_text
  );
  char *filename = Dowa_Arena_Allocate(arena, 1024);
  if (!filename)
  {
    perror("Error opening file");
    exit(EXIT_FAILURE);
  }
  char *uuid4 = (char *)Dowa_Arena_Allocate(arena, 37);
  if (!uuid4)
  {
    perror("Error uuid");
    exit(EXIT_FAILURE);
  }

  uv_mutex_lock(&history_mutex);

  int32 seed = (uint32)time(NULL) ^ counter++;
  Dowa_String_UUID(seed, uuid4);
  snprintf(filename, 1024, "%s.txt", uuid4);

  if (PostDog_History_CreateFile(filename, new_file))
  {
    HistoryItem item = {0};
    item.filename = strdup(filename);
    item.title = strdup(title);
    item.deleted = FALSE;

    Dowa_Array_Push(new_history_items, item);
  }

  uv_mutex_unlock(&history_mutex);
  Dowa_Arena_Free(arena);
}

void PostDog_Websocket_Listen(void)
{
  while (TRUE)
  {
    if (WS_BREAK) break;

    Seobeo_WebSocket_Message *p_msg = Seobeo_WebSocket_Receive(ws);
    if (p_msg)
    {
      if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
      {
        printf("Response: %.*s\n", (int)p_msg->length, (char*)p_msg->data);
        snprintf(result_text + strlen(result_text),  RESULT_BUFFER_LENGTH - strlen(result_text),
            "\n%s", (char*)p_msg->data);
      }
      Seobeo_WebSocket_Message_Destroy(p_msg);
    }
    usleep(10000);
    printf("Listening\n");
  }
  return;
}

void PostDog_Websocket_Connect(void)
{
  ws = Seobeo_WebSocket_Connect(url_input_text);
  memset(result_text, 0, strlen(result_text));
}

int PostDog_Websocket_Send(void)
{
  if (Seobeo_WebSocket_Send_Text(ws, input_body_array[active_input_tab]) < 0)
  {
    snprintf(result_text + strlen(result_text),  RESULT_BUFFER_LENGTH - strlen(result_text),
        "Failed to send message\n");
    return -1;
  }
  snprintf(result_text + strlen(result_text),  RESULT_BUFFER_LENGTH - strlen(result_text),
      "\n%s", input_body_array[active_input_tab]);
  return 0;
}

void PostDog_Websocket_Destroy(uv_thread_t thread_id)
{
  Seobeo_WebSocket_Destroy(ws);
  uv_thread_join(&thread_id);
}

void PostDog_Websocket_Start(void *arg)
{
  PostDog_Websocket_Connect();
  PostDog_Websocket_Listen();
}

uv_thread_t PostDog_Websocket_Start_Thread()
{
  uv_thread_t thread_id;

  if (uv_thread_create(&thread_id, PostDog_Websocket_Start, NULL) != 0)
  {
    perror("Failed to create thread");
    memset(&thread_id, 0, sizeof(thread_id));
    return thread_id;
  }

  return thread_id;
}

int PostDog_Http_Request(void)
{
  Seobeo_Client_Request *req = Seobeo_Client_Request_Create(url_input_text);
  Seobeo_Client_Response *res;
  switch (active_method_dropdown)
  {
    case 0:
    {
      Seobeo_Client_Request_Set_Method(req, "GET");
      break;
    }
    case 1:
    {
      Seobeo_Client_Request_Set_Method(req, "POST");
      break;
    }
    case 2:
    {
      Seobeo_Client_Request_Set_Method(req, "PUT");
      break;
    }
    case 3:
    {
      Seobeo_Client_Request_Set_Method(req, "DELETE");
      break;
    }
  }

  {
    if (input_body_array[TAB_HEADER] && strlen(input_body_array[TAB_HEADER]) > 0)
    {
      char *headersCopy = strdup(input_body_array[TAB_HEADER]);
      char *line = strtok(headersCopy, "\n");
      while (line != NULL)
      {
        while (*line == ' ' || *line == '\t') line++;
        if (strlen(line) > 0)
          Seobeo_Client_Request_Add_Header_Array(req, line);
        line = strtok(NULL, "\n");
      }

    }
    Seobeo_Client_Request_Set_Follow_Redirects(req, TRUE, 5); // TODO: remove magic number;
    res = Seobeo_Client_Request_Execute(req);

    if (res == NULL)
      snprintf(result_text, RESULT_BUFFER_LENGTH, "Error: Failed to send the request\n");
    else
      snprintf(result_text, RESULT_BUFFER_LENGTH, "HTTP Status: %d\n\n%s",
               res->status_code, res->body ? res->body : "");
  }
  Seobeo_Client_Request_Destroy(req);
  Seobeo_Client_Response_Destroy(res);
  PostDog_Request_SaveFile();
  return 0;
}

void PostDog_Http_Work(uv_work_t *req)
{
  PostDog_Http_Request();
  printf("HTTP request finished.\n");
}

void PostDog_Http_Work_Done(uv_work_t *req, int status)
{
  LOADING = FALSE;
  free(req);
}

void PostDog_Http_Thread_Request()
{
  uv_work_t *work_req = malloc(sizeof(uv_work_t));
  if (!work_req)
  {
    perror("Failed to allocate work request");
    return;
  }
  LOADING = TRUE;
  if (uv_queue_work(main_loop, work_req, PostDog_Http_Work, PostDog_Http_Work_Done) != 0)
  {
    perror("Failed to queue work");
    free(work_req);
    LOADING = FALSE;
  }
}

void PostDog_Update_URL(void)
{
  // Save existing query string if present
  char *question_mark = strchr(url_input_text, '?');
  if (question_mark)
    *question_mark = '\0';

  int get_params_length = (int)strlen(input_body_array[TAB_GET_PARAMS]);
  if (get_params_length == 0)
    return;

  char *separator = "?";

  Dowa_Arena *arena = Dowa_Arena_Create(1024*1024);
  char **lines = Dowa_String_Split(input_body_array[TAB_GET_PARAMS], "\n", get_params_length, 1, arena);
  for (int i = 0; i < Dowa_Array_Length(lines); i++)
  {
    char *line = lines[i];
    char **key_value = Dowa_String_Split(line, " ", (int)strlen(line), 1, arena);

    if (Dowa_Array_Length(key_value) < 2)
      break;

    strcat(url_input_text, separator);
    strcat(url_input_text, key_value[0]);
    strcat(url_input_text, "=");
    for (int i = 1; i < Dowa_Array_Length(key_value); i++)
    {
      if (!key_value[i] || key_value[i][0] == '\0')
        break;
      if (i > 1) strcat(url_input_text, "%20");
      strcat(url_input_text, key_value[i]);
    }
    separator = "&";
  }

  Dowa_Arena_Free(arena);
}

int PostDog_String_To_MethodEnum(char *value)
{
  if (strstr(value, "GET"))
    return 0;
  if (strstr(value, "POST"))
    return 1;
  if (strstr(value, "PUT"))
    return 2;
  if (strstr(value, "DELETE"))
    return 3;
  return 0;
}

void PostDog_Params_Reset(void)
{
  url_input_text[0] = '\0';
  result_text[0] = '\0';
  active_method_dropdown = 0;
  for (int i = 0; i < Dowa_Array_Length(input_body_array); i++)
    input_body_array[i][0] = '\0';

  // Reset text area states when clearing
  GuiTextAreaResetAllStates();
}

void PostDog_Load_File(const char *filename)
{
  char full_file_path[512] = {0};
  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
  FILE *file = fopen(full_file_path, "r");
  if (!file)
    return;

  fseek(file, 0, SEEK_END);
  size_t file_size = ftell(file);
  fseek(file, 0, SEEK_SET);

  Dowa_Arena *init_arena = Dowa_Arena_Create(file_size + 2);
  Dowa_Arena *split_arena = Dowa_Arena_Create(file_size * 2);
  char *file_buffer = Dowa_Arena_Allocate(init_arena, file_size+1);
  fread(file_buffer, 1, file_size, file);
  fclose(file);

  char **values = Dowa_String_Split(file_buffer, "---\n", file_size, 4, split_arena);

  for (int i = 0; i < Dowa_Array_Length(values); i++)
  {
    switch (i)
    {
      case 0:  // Title - skip
        break;

      case 1:  // URL
        snprintf(url_input_text, strlen(values[i]) + 1, "%s", values[i]);
        url_input_text[strcspn(url_input_text, "\n")] = '\0';
        break;

      case 2:  // Method
        active_method_dropdown = PostDog_String_To_MethodEnum(values[i]);
        break;

      case 3:  // Headers (TAB_HEADER)
      case 4:  // Body (TAB_BODY)
      case 5:  // Get Params (TAB_GET_PARAMS)
      case 6:  // Websocket (TAB_WEBSOCKET)
      {
        int map_index = i - 3;  // 3->0, 4->1, 5->2, 6->3
        snprintf(input_body_array[map_index], strlen(values[i]) + 1, "%s", values[i]);
        // Trim trailing newlines
        for (int j = strlen(values[i]); j > 0; j--)
        {
          if (input_body_array[map_index][j] == '\n')
          {
            input_body_array[map_index][j] = '\0';
            break;
          }
        }
        break;
      }

      default:  // Response (index 7+)
        snprintf(result_text, strlen(values[i]) + 1, "%s", values[i]);
        break;
    }
  }

  // Reset text area states when loading new file
  GuiTextAreaResetAllStates();

  Dowa_Arena_Free(init_arena);
  Dowa_Arena_Free(split_arena);
}

int main()
{
  // -- initialize libuv --//
  main_loop = uv_default_loop();
  uv_mutex_init(&history_mutex);

  // -- initizlied --//
  InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "PostDog");
  SetWindowState(FLAG_WINDOW_RESIZABLE);
  SetTargetFPS(60);

  Font customFont = LoadFontEx("postdog/Roboto-Regular.ttf", 20, 0, 0);
  GuiSetFont(customFont);
  GuiSetStyle(DEFAULT, TEXT_SIZE, 15);
  Image logo_original = LoadImage("postdog/logo_bigger.png");
  ImageResize(&logo_original, 200, 600);
  SetWindowIcon(logo_original);
  Texture2D logo_texture = LoadTextureFromImage(logo_original);
  UnloadImage(logo_original);

  // Arena for text area undo states
  g_text_area_arena = Dowa_Arena_Create(1024 * 1024 * 4);

  // -- Starting pos ---//
  Rectangle full_screen = { 0 };
  Rectangle history_sidebar_rect = { 0 };
  Dowa_Array_Reserve(history_items, 10);
  Dowa_Array_Reserve(new_history_items, 10);
  PostDog_History_Load(&history_items);
  int32 *history_deleted_items = NULL;

  Rectangle url_area_rect = { 0 };
  Rectangle url_input_bounds_rect = { 0 };
  bool url_input_edit = false;
  Rectangle url_text_bounds_rect = { 0 };
  Rectangle url_enter_button_rect = { 0 };
  Rectangle method_dropdown_rect = { 0 };


  // Initialize global UI state
  url_input_text = (char *)malloc(sizeof(char) * URL_TEXT_BUFFER_LENGTH);
  snprintf(url_input_text, URL_TEXT_BUFFER_LENGTH, URL_TEXT_DEFAULT);

  Dowa_Array_Push(input_body_array, (char *)malloc(sizeof(char) * HEADER_BUFFER_LENGTH));
  Dowa_Array_Push(input_body_array, (char *)malloc(sizeof(char) * BODY_BUFFER_LENGTH));
  Dowa_Array_Push(input_body_array, (char *)malloc(sizeof(char) * DEFAULT_TEXT_BUFFER_LENGTH));
  Dowa_Array_Push(input_body_array, (char *)malloc(sizeof(char) * DEFAULT_TEXT_BUFFER_LENGTH));

  snprintf(input_body_array[TAB_HEADER], HEADER_BUFFER_LENGTH, HEADER_TEXT_DEFAULT);
  snprintf(input_body_array[TAB_BODY], HEADER_BUFFER_LENGTH, BODY_TEXT_DEFAULT);
  snprintf(input_body_array[TAB_GET_PARAMS], HEADER_BUFFER_LENGTH, GET_PARAM_TEXT_DEFAULT);

  result_text = (char *)malloc(sizeof(char) * RESULT_BUFFER_LENGTH);
  result_text[0] = '\0';

  bool method_edit = false;

  int sendRequest;

  // -- input --//
  Rectangle input_area_rect = { 0 };
  Rectangle input_tab_rect = { 0 };
  Rectangle input_tab_item_rect = { 0 };
  Rectangle input_body_rect = { 0 };
  bool input_body_edit_mode = false;

  // -- result --//
  Rectangle result_area_rect = { 0 };
  Rectangle result_body_rect = { 0 };
  bool result_body_edit_mode = false;

  // General styling.
  float padding = 10; // TODO make it % based?
  int prev_input_tab = 0;

  // Scroll offsets
  float history_scroll_offset = 0;

  while (!WindowShouldClose())
  {
    // Process libuv events (non-blocking)
    uv_run(main_loop, UV_RUN_NOWAIT);

    int screen_width = GetScreenWidth();
    int screen_height = GetScreenHeight();

    if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_EQUAL))
      GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) + 1);

    if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_MINUS))
      GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) - 1);

    Rectangle screen_rect = { 0, 0, screen_width, screen_height };

    // -- Side bar --//
    history_sidebar_rect = LeftColumn(screen_rect, 0.20, padding);
    Rectangle content_area_rect = RightColumn(screen_rect, history_sidebar_rect, padding);
    Rectangle logo_area_rect = (Rectangle){
      .x = history_sidebar_rect.x,
      .y = history_sidebar_rect.y,
      .width = history_sidebar_rect.width,
      .height = 300
    };


    DrawRectangleSelectiveRounded(history_sidebar_rect, 10, 10, ORANGE, TRUE, FALSE, FALSE, TRUE);
    DrawRectangleSelectiveRounded(content_area_rect, 10, 10, WHITE, FALSE, TRUE, TRUE, FALSE);

    Rectangle history_list_area_rect = Below(logo_area_rect, padding);
    history_list_area_rect.x += padding;
    history_list_area_rect.width = history_sidebar_rect.width -  (2 * padding);
    history_list_area_rect.height = history_sidebar_rect.height - logo_area_rect.height - padding;

    int32 new_history_items_length = Dowa_Array_Length(new_history_items);
    int32 history_item_length = Dowa_Array_Length(history_items);
    int32 total = new_history_items_length + history_item_length;
    float item_height = history_list_area_rect.height * 0.10;

    int32 number_of_skipped_items = 0;
    for (int i = 0; i < total; i++)
    {
      HistoryItem *curr_history_items = i < new_history_items_length ?
        &new_history_items[new_history_items_length - i - 1] : &history_items[i - new_history_items_length];

      if (curr_history_items->deleted)
      {
        number_of_skipped_items++;
        continue;
      }

      curr_history_items->rect = (Rectangle){
        .x = history_list_area_rect.x,
        .y = history_list_area_rect.y + (padding + item_height) * (i - number_of_skipped_items) + history_scroll_offset,
        .width = history_list_area_rect.width,
        .height = item_height
      };
    }

    // --- URL --- //
    url_area_rect = (Rectangle){
      .x = content_area_rect.x,
      .y = content_area_rect.y,
      .width = content_area_rect.width,
      .height = content_area_rect.height * 0.1
    };

    float url_control_y = url_area_rect.y + (url_area_rect.height - TEXT_SIZE * 2) / 2;

    url_text_bounds_rect = (Rectangle){
      .x = url_area_rect.x + padding,
      .y = url_control_y,
      .width = 10 * (TEXT_SIZE / 2),
      .height = TEXT_SIZE * 2
    };

    url_input_bounds_rect = RightOf(url_text_bounds_rect, padding);
    url_input_bounds_rect.width = url_area_rect.width * 0.7;
    url_input_bounds_rect.height = TEXT_SIZE * 2.5;

    url_enter_button_rect = RightOf(url_input_bounds_rect, padding);
    url_enter_button_rect.width = url_area_rect.width * 0.1;
    url_enter_button_rect.height = TEXT_SIZE * 2;

    method_dropdown_rect = RightOf(url_enter_button_rect, padding);
    method_dropdown_rect.width = url_area_rect.width * 0.1;
    method_dropdown_rect.height = TEXT_SIZE * 2;

    // -- Body -- //
    Rectangle body_area_rect = Below(url_area_rect, 0);
    body_area_rect.height = content_area_rect.height - url_area_rect.height;

    input_area_rect = HorizontalSplit(body_area_rect, 0.5);
    result_area_rect = RightOf(input_area_rect, 0);
    result_area_rect.width = body_area_rect.width - input_area_rect.width;

    input_tab_rect = (Rectangle){
      .x = input_area_rect.x + padding,
      .y = input_area_rect.y + padding,
      .width = input_area_rect.width - (2 * padding),
      .height = input_area_rect.height * 0.1
    };

    input_tab_item_rect = input_tab_rect;
    input_tab_item_rect.width = input_tab_rect.width / 4;

    input_body_rect = Below(input_tab_rect, 0);
    input_body_rect.width = input_tab_rect.width;
    input_body_rect.height = input_area_rect.height - input_tab_rect.height - (2 * padding);

    // -- Result -- /
    result_body_rect = (Rectangle){
      .x = result_area_rect.x + padding,
      .y = input_body_rect.y,
      .width = result_area_rect.width - (2 * padding),
      .height = input_body_rect.height
    };

    Vector2 mouse_position = GetMousePosition();
    float mouse_wheel = GetMouseWheelMove();

    // Reset text area state when tab changes
    if (prev_input_tab != active_input_tab)
    {
      prev_input_tab = active_input_tab;
    }

    // Handle scroll wheel for history
    if (InArea(mouse_position, history_list_area_rect) && mouse_wheel != 0) {
      history_scroll_offset += mouse_wheel * 30;  // 30 pixels per wheel tick
      // Clamp scroll offset
      float max_scroll = (total * (item_height + padding)) - history_list_area_rect.height;
      if (history_scroll_offset > 0) history_scroll_offset = 0;
      if (history_scroll_offset < -max_scroll && max_scroll > 0) history_scroll_offset = -max_scroll;
    }

    // Reset
    SetMouseCursor(MOUSE_CURSOR_DEFAULT);

    // TODO: Move all for loop rect up here so it does not flicker.
    if (
        InArea(mouse_position, result_area_rect) ||
        InArea(mouse_position, input_tab_rect) ||
        InArea(mouse_position, url_input_bounds_rect) ||
        InArea(mouse_position, url_enter_button_rect) ||
        InArea(mouse_position, method_dropdown_rect) ||
        InArea(mouse_position, logo_area_rect)
    )
      SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);

    if (Clicked(mouse_position, logo_area_rect))
      PostDog_Params_Reset();
    
    // --- Begin Drawing --- //
    BeginDrawing();
      ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)));

      // DrawRectangleRec(history_sidebar_rect, Fade(GRAY, 0.1f));
      DrawRectangleRounded(history_sidebar_rect, 0.5, 1, Fade(BLUE, 0.1f));

      // DrawRectangleRec(logo_area_rect, Fade(BLUE, 0.2f));
      Rectangle logo_image_rect = AddPadding(logo_area_rect, padding);
      // Fit logo to area while maintaining aspect ratio
      float logo_size = logo_image_rect.height < logo_image_rect.width ? logo_image_rect.height : logo_image_rect.width;
      Rectangle dest_rect = {
        .x = logo_image_rect.x + (logo_image_rect.width - logo_size) / 2,  // Center horizontally
        .y = logo_image_rect.y,
        .width = logo_size,
        .height = logo_size
      };

      Rectangle source_rect = { 0, 0, logo_texture.width, logo_texture.height };
      DrawTexturePro(logo_texture, source_rect, dest_rect, (Vector2){0, 0}, 0.0f, WHITE);

      BeginScissorMode(history_list_area_rect.x, history_list_area_rect.y, history_list_area_rect.width, history_list_area_rect.height);
        for (int i = 0; i < total; i++)
        {
          HistoryItem *curr_history_items = i < new_history_items_length ?
            &new_history_items[new_history_items_length - i - 1] : &history_items[i - new_history_items_length];

          if (curr_history_items->deleted)
            continue;

          float diff = curr_history_items->rect.height*0.6;
          DrawRectangleRounded(curr_history_items->rect, 0.5, 1, Fade(BLACK, 0.1f));
          Rectangle filename_area_rect = curr_history_items->rect;

          filename_area_rect.height -= diff;
          Rectangle icon_area = Below(filename_area_rect, 0);
          icon_area.height = diff;

          // DrawRectangleRec(filename_area_rect, Fade(BLUE, 0.1f));
          // DrawRectangleRec(icon_area, Fade(YELLOW, 0.1f));
          // DrawRectangleRec(AddPadding(filename_area_rect, 5), Fade(BLUE, 0.1f));

          Rectangle icon_area_left_column = LeftColumn(icon_area, 0.5, 0);
          Rectangle icon_area_right_column = RightColumn(icon_area, icon_area_left_column, 0);

          Rectangle temp = AddPadding(filename_area_rect, padding);

          // ADD this back
          // GuiDrawText(curr_history_items->title, temp, TEXT_ALIGN_CENTER, BLACK);
          if (
            InArea(mouse_position, icon_area_left_column) ||
            InArea(mouse_position, icon_area_right_column)
          )
            SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);

          if (GuiButton(AddPadding(icon_area_left_column, padding), "view"))
            PostDog_Load_File(curr_history_items->filename);
          if (GuiButton(AddPadding(icon_area_right_column, padding), "delete"))
          {
            char delete_path[512];
            if (!remove(PostDog_Construct_URL(curr_history_items->filename, delete_path, sizeof(delete_path))))
               curr_history_items->deleted = TRUE;
            else
              fprintf(stderr, "Wasn't able to delete file: %s \n", curr_history_items->filename);
          }
        }
      EndScissorMode();

      if (total > 0)
      {
        float content_height = total * (item_height + padding);
        if (content_height > history_list_area_rect.height)
        {
          float scrollbar_height = (history_list_area_rect.height / content_height) * history_list_area_rect.height;
          float scrollbar_y = history_list_area_rect.y - (history_scroll_offset / content_height) * history_list_area_rect.height;
          Rectangle scrollbar_rect = {
            history_list_area_rect.x + history_list_area_rect.width - 5,
            scrollbar_y,
            5,
            scrollbar_height
          };
          DrawRectangleRec(scrollbar_rect, Fade(WHITE, 0.5f));
        }
      }

      // URL area Rect
      DrawRectangleRec(url_area_rect, Fade(BLACK, 0.1f));

      if (GuiTextArea(21, url_input_bounds_rect, url_input_text, DEFAULT_TEXT_BUFFER_LENGTH, url_input_edit, FALSE, g_text_area_arena))
        url_input_edit = !url_input_edit;

      if (url_input_edit)
      {
        if (IsKeyPressed(KEY_ENTER))
        {
          PostDog_Http_Thread_Request();
          url_input_edit = !url_input_edit;
        }
      }

      if (GuiButtonRounded(url_enter_button_rect, "New Request", 0.2, 1))
        PostDog_Http_Thread_Request();

      DrawRectangleSelectiveRounded(body_area_rect, 10, 10, ORANGE, TRUE, FALSE, FALSE, TRUE);
      DrawRectangleSelectiveRounded(result_area_rect, 10, 10, WHITE, FALSE, TRUE, TRUE, FALSE);

      // Input Tabs Rect
      DrawRectangleRec(input_area_rect, Fade(BLUE, 0.1f));
      DrawRectangleRec(input_tab_rect,  Fade(DARKBLUE, 0.1f));
      GuiSetStyle(TOGGLE, GROUP_PADDING, 0);

      int text_area_id = TEXT_AREA_ID_INPUT_HEADER + active_input_tab;
      if (GuiTextArea(text_area_id, input_body_rect, input_body_array[active_input_tab],
                                   BODY_BUFFER_LENGTH, input_body_edit_mode, TRUE, g_text_area_arena))
        input_body_edit_mode = !input_body_edit_mode;

      if (active_input_tab != TAB_WEBSOCKET)
      {
        WS_BREAK = TRUE;
        if (websocket_thread_id)
          PostDog_Websocket_Destroy(websocket_thread_id);
      }
      else
      {
        WS_BREAK = FALSE;
        Rectangle temp = {
          .x = input_body_rect.x + input_body_rect.width - (3 * padding) - 100,
          .y = input_body_rect.y + input_body_rect.height - (3 * padding) - 20,
          .width = 100,
          .height = 40
        };
        if (GuiButton(temp, "Send") || (input_body_edit_mode && IsKeyDown(KEY_LEFT_SHIFT) && IsKeyDown(KEY_ENTER)))
        {
          if (!ws)
            websocket_thread_id = PostDog_Websocket_Start_Thread();
          usleep(10000);
          PostDog_Websocket_Send();
        }
      }


      GuiToggleGroup(input_tab_item_rect, "Header;Body;Get Param;Websocket", &active_input_tab);
      PostDog_Update_URL();

      // TODO: Add animations.
      DrawRectangleRec(result_area_rect, LOADING ? Fade(RED, 0.1f) : Fade(GREEN, 0.1f));
      boolean result_toggle = GuiTextArea(TEXT_AREA_ID_RESULT, result_body_rect, result_text,
                                          RESULT_BUFFER_LENGTH, result_body_edit_mode, TRUE, g_text_area_arena);

      if (result_toggle)
        result_body_edit_mode = !result_body_edit_mode;

      if (GuiDropdownBoxRounded(url_text_bounds_rect, "GET;POST;PUT;DELETE", &active_method_dropdown, method_edit, 0.2, 1))
        method_edit = !method_edit;
    EndDrawing();
  }

  GuiTextAreaResetAllStates();
  Dowa_Arena_Free(g_text_area_arena);

  // Cleanup libuv
  uv_mutex_destroy(&history_mutex);
  uv_loop_close(main_loop);

  CloseWindow();
  return 0;
}
