#include "basic/basic.h"
#include "basic/basic.cpp"
#include "profiler.h"

#include "SDL3/SDL.h"
#include "external/glad/glad.c"
#include "external/glad/glad.h"
#include "external/stb_truetype.h"
#include "external/stb_truetype.c"
#if OS_LINUX
#define MCO_USE_UCONTEXT
#endif
#define MINICORO_IMPL
#include "external/minicoro.h"

#include "render/generated_font.cpp"
#include "render/font.cpp"
#include "render/opengl.cpp"

#include "buffer.h"
#include "view.h"
#include "window.h"
#include "text_editor.h"

#include "globals.cpp"
#include "coroutines.cpp"
#include "buffer.cpp"
#include "view.cpp"
#include "window.cpp"
#include "window_command.cpp"
#include "window_debug.cpp"
#include "window_status.cpp"
#include "window_search.cpp"

#include "process.cpp"
#include "event.cpp"
#include "config.cpp"

#include "commands.cpp"
#include "commands_clipboard.cpp"

#include "scratch.cpp"
#include "draw.cpp"
#include "test/tests.cpp"

#if OS_WASM
EM_JS(void, JS_SetMouseCursor, (const char *cursor_str), {
    document.getElementById("canvas").style.cursor = UTF8ToString(cursor_str);
})

void SetMouseCursor(SDL_SystemCursor id) {
    if (id == SDL_SYSTEM_CURSOR_EW_RESIZE) {
        JS_SetMouseCursor("ew-resize");
    } else if (id == SDL_SYSTEM_CURSOR_NS_RESIZE) {
        JS_SetMouseCursor("ns-resize");
    } else if (id == SDL_SYSTEM_CURSOR_DEFAULT) {
        JS_SetMouseCursor("default");
    } else if (id == SDL_SYSTEM_CURSOR_TEXT) {
        JS_SetMouseCursor("text");
    } else {
        InvalidCodepath();
    }
}
#else
void SetMouseCursor(SDL_SystemCursor id) {
    static SDL_Cursor *SDL_MouseCursor;
    static SDL_SystemCursor last_id;

    if (SDL_MouseCursor == NULL || last_id != id) {
        if (SDL_MouseCursor != NULL) {
            SDL_DestroyCursor(SDL_MouseCursor);
        }
        SDL_MouseCursor = SDL_CreateSystemCursor(id);
        SDL_SetCursor(SDL_MouseCursor);
        last_id = id;
    }
}
#endif

void SetMouseCursor(Event event) {
    ProfileFunction();
    Scratch scratch;
    Array<Window *> order = GetWindowZOrder(scratch);
    Vec2I mouse = MouseVec2I();

    if (ResizerSelected.id != -1) {
        Window *window = GetWindow(ResizerSelected);
        if (window->layout) {
            SetMouseCursor(SDL_SYSTEM_CURSOR_EW_RESIZE);
        } else {
            SetMouseCursor(SDL_SYSTEM_CURSOR_NS_RESIZE);
        }
        return;
    }

    if (ResizerHover.id != -1) {
        Window *window = GetWindow(ResizerHover);
        if (window->layout) {
            SetMouseCursor(SDL_SYSTEM_CURSOR_EW_RESIZE);
        } else {
            SetMouseCursor(SDL_SYSTEM_CURSOR_NS_RESIZE);
        }
        return;
    }

    For(order) {
        if (!it->visible) continue;
        bool mouse_in_total     = AreOverlapping(mouse, it->total_rect);
        bool mouse_in_scrollbar = AreOverlapping(mouse, it->scrollbar_rect);

        if (!IsDocumentSelectionValid() && (mouse_in_scrollbar || IsScrollbarSelectionValid())) {
            SetMouseCursor(SDL_SYSTEM_CURSOR_DEFAULT);
            return;
        } else if (mouse_in_total || IsDocumentSelectionValid()) {
            SetMouseCursor(SDL_SYSTEM_CURSOR_TEXT);
            return;
        }
    }
    SetMouseCursor(SDL_SYSTEM_CURSOR_DEFAULT);
}
void UpdateScroll(Window *window, bool update_caret_scrolling) {
    ProfileFunction();
    BSet set = GetBSet(window);

    // Scrolling with caret
    if (update_caret_scrolling) {
        Caret c     = set.view->carets[0];
        Int   front = GetFront(c);
        XY    xy    = PosToXY(set.buffer, front);

        Rect2I visible       = GetVisibleCells(window);
        Vec2I  visible_cells = GetSize(visible);
        Vec2I  visible_size  = visible_cells * Vec2I{window->font->char_spacing, window->font->line_spacing};
        Vec2I  rect_size     = GetSize(window->document_rect);

        if (xy.line >= visible.max.y - 2) {
            Int set_view_at_line = xy.line - (visible_cells.y - 1);
            Int cut_off_y        = Max((Int)0, visible_size.y - rect_size.y);
            set.view->scroll.y   = (set_view_at_line * window->font->line_spacing) + cut_off_y;
        }

        if (xy.line < visible.min.y + 1) {
            set.view->scroll.y = xy.line * window->font->line_spacing;
        }

        if (xy.col >= visible.max.x - 1) {
            Int set_view_at_line = xy.col - (visible_cells.x - 1);
            Int cut_off_x        = Max((Int)0, visible_size.x - rect_size.x);
            set.view->scroll.x   = (set_view_at_line * window->font->char_spacing) + cut_off_x;
        }

        if (xy.col <= visible.min.x) {
            set.view->scroll.x = xy.col * window->font->char_spacing;
        }
    }

    // Clip scroll
    {
        Int last_line      = LastLine(set.buffer);
        set.view->scroll.y = Clamp(set.view->scroll.y, (Int)0, Max((Int)0, (last_line - 1) * window->font->line_spacing));

        // @note:
        // GetCharCountOfLongestLine is a bottleneck, there is probably an algorithm for
        // calculating this value incrementally but do we even need X scrollbar or x clipping?
        set.view->scroll.x = ClampBottom(set.view->scroll.x, (Int)0);
    }
}

void OnCommand(Event event) {
    ProfileFunction();
    //
    // Window cursor setting
    //
    Scratch scratch;
    Array<Window *> order = GetWindowZOrder(scratch);

    // Handle wheel scrolling
    if (event.xwheel || event.ywheel) {
        Vec2I mouse = MouseVec2I();

        For (order) {
            if (!it->visible) {
                continue;
            }

            bool mouse_in_window = AreOverlapping(mouse, it->total_rect);
            if (mouse_in_window) {
                View *view = GetView(it->active_view);
                view->scroll.y -= (Int)(event.ywheel * 48);
                view->scroll.x += (Int)(event.xwheel * 48);
            }
        }
    }

    // Handle selected window scrollbar
    // @note: the order here assumes that we won't run this code on the
    // same event as the scroll was pressed
    if (IsScrollbarSelectionValid() && Mouse(LEFT_UP)) {
        Assert(DocumentSelected.id == -1);
        ScrollbarSelected.id = -1;
    } else if (IsScrollbarSelectionValid()) {
        // :ScrollbarImprovement
        // @todo: it generally works ok but it moves the scrollbar a bit on click
        // when mouse is not even moving
        Assert(DocumentSelected.id == -1);
        Window  *window     = GetWindow(ScrollbarSelected);
        View    *view       = GetView(window->active_view);
        Vec2     mouse_vec2 = MouseVec2();
        Scroller s          = ComputeScrollerRect(window);
        double   size_y     = (double)GetSize(window->scrollbar_rect).y;
        double   p          = mouse_vec2.y - window->scrollbar_rect.min.y;
        double   v          = p / size_y;
        v                   = v + (window->mouse_scroller_offset);
        view->scroll.y      = (Int)(v * (double)s.line_count * (double)window->font->line_spacing);
    }

    if (DocumentSelected != ActiveWindowID) {
        DocumentSelected.id = -1;
    } else if (IsDocumentSelectionValid() && MouseUp()) {
        Assert(ScrollbarSelected.id == -1);
        DocumentSelected.id = -1;
    } else if (IsDocumentSelectionValid()) {
        Assert(ScrollbarSelected.id == -1);
        BSet selected = GetBSet(DocumentSelected);
        Vec2I mouse = MouseVec2I();
        // Special case for full-screen where we can have document
        // aligned with monitor screen in which case mouse cursor cannot
        // be smaller then 0 which means we cannot scroll
        if (mouse.y == 0 && selected.window->document_rect.min.y == 0) {
            float x, y;
            SDL_GetGlobalMouseState(&x, &y);
            x = roundf(DPIScale * x);
            y = roundf(DPIScale * y);
            if (y == 0) {
                mouse.y = -10;
            }
        }

        Int p = ScreenSpaceToBufferPos(selected.window, selected.view, selected.buffer, mouse);
        Caret &caret = selected.view->carets[0];
        caret = SetFrontWithAnchor(caret, DocumentAnchor, p);
    }

    if (ResizerSelected.id != -1 && Mouse(LEFT_UP)) {
        Assert(DocumentSelected.id == -1);
        Assert(ScrollbarSelected.id == -1);
        ResizerSelected.id = {-1};
    } else if (ResizerSelected.id != -1) {
        Window *window = GetWindow(ResizerSelected);
        if (window->layout) {
            Vec2I mouse = MouseVec2I();
            Int offx = mouse.x - window->resizer_rect.min.x;
            window->weight += (double)offx / (double)WindowCalcEvenResizerValue(event.xwindow);
            window->weight = Clamp(window->weight, 0.1, 100.0);
        }
    } else {
        ResizerHover = {-1};
        For(Windows) {
            Vec2I mouse = MouseVec2I();
            bool mouse_in_rect = AreOverlapping(mouse, it->resizer_rect);
            if (mouse_in_rect) {
                ResizerHover = it->id;
                if (Mouse(LEFT)) {
                    ResizerSelected = it->id;
                }
            }
        }
    }

    // Set active window on click
    if (MousePress()) {
        Vec2I mouse = MouseVec2I();
        For(order) {
            if (!it->visible) {
                continue;
            }
            bool mouse_in_document = AreOverlapping(mouse, it->document_rect);
            if (mouse_in_document) {
                NextActiveWindowID = ActiveWindowID = it->id;
                break;
            }
        }
    }

    if (Ctrl() && Shift() && Mouse(RIGHT)) {

    } else if (Alt() && Ctrl() && Mouse(RIGHT)) {
    } else if (Ctrl() && Mouse(RIGHT)) {

    } else if (Alt() && Mouse(RIGHT)) {
    } else if (Mouse(RIGHT)) {
        Vec2I mouse = MouseVec2I();
        BSet  active = GetBSet(ActiveWindowID);
        bool  mouse_in_document = AreOverlapping(mouse, active.window->document_rect);
        if (mouse_in_document) {
            Int p           = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse);
            Int saved_front = -1;

            IterRemove(active.view->carets) {
                IterRemovePrepare(active.view->carets);
                if (InBounds(it.range, p)) {
                    String16 string = GetString(active.buffer, it.range);
                    SaveStringInClipboard(string);

                    remove_item = true;
                    saved_front = GetFront(it);
                }
            }
            if (active.view->carets.len == 0) Add(&active.view->carets, MakeCaret(saved_front));

            if (saved_front == -1) {
                Int      line       = PosToLine(active.buffer, p);
                Range    line_range = GetLineRangeWithoutNL(active.buffer, line);
                String16 string     = GetString(active.buffer, line_range);
                SaveStringInClipboard(string);
            }
        }
    }

    if (Ctrl() && Mouse(LEFT)) {
        MouseLoadWord(event);
    } else if (Mouse(LEFT)) { // Uses Alt and shift
        Vec2I mouse = MouseVec2I();
        {
            Assert(ScrollbarSelected.id == -1);
            Assert(DocumentSelected.id == -1);

            BSet active = GetBSet(ActiveWindowID); // using next to make sure mouse works on first click after switching the window
            bool mouse_in_document = AreOverlapping(mouse, active.window->document_rect);
            bool mouse_in_line_numbers = AreOverlapping(mouse, active.window->line_numbers_rect);
            if (mouse_in_document || mouse_in_line_numbers) {
                DocumentSelected = active.window->id;

                Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse);
                if (Alt()) Insert(&active.view->carets, MakeCaret(p, p), 0);
                if (!Alt() && !Shift()) active.view->carets.len = 1;

                Caret &caret = active.view->carets[0];
                if (Shift()) {
                    if (p <= caret.range.min) {
                        caret.range.min = p;
                        caret.ifront    = 0;
                    } else if (p >= caret.range.max) {
                        caret.range.max = p;
                        caret.ifront    = 1;
                    }
                } else if (event.clicks >= 2 && InBounds({caret.range.min - 1, caret.range.max + 1}, p)) {
                    Range range = EncloseWord(active.buffer, p);
                    if (event.clicks >= 3) {
                        range = EncloseFullLine(active.buffer, p);
                    }
                    caret = MakeCaret(range.max, range.min);
                } else {
                    caret = MakeCaret(p);
                }
                MergeCarets(active.buffer, &active.view->carets);
                DocumentAnchor = caret;
            }
        }

        // Figure out scrollbar click
        // :ScrollbarImprovement
        // @todo: it generally works ok but it moves the scrollbar a bit on click
        // when mouse is not even moving
        For(order) {
            if (!it->visible) continue;
            bool mouse_in_scrollbar = AreOverlapping(mouse, it->scrollbar_rect);
            if (mouse_in_scrollbar) {
                ScrollbarSelected = it->id;

                View *view = GetView(it->active_view);
                Vec2 mouse_vec2 = MouseVec2();
                Scroller s = ComputeScrollerRect(it);
                double size_y = (double)GetSize(it->scrollbar_rect).y;
                double p = mouse_vec2.y - it->scrollbar_rect.min.y;
                if (mouse_vec2.y < s.rect.min.y || mouse_vec2.y > s.rect.max.y) {
                    view->scroll.y = (Int)(p / size_y * (double)s.line_count * (double)it->font->line_spacing);
                    it->mouse_scroller_offset = -(double)GetSize(s.rect).y / 2.0 / size_y;
                } else {
                    it->mouse_scroller_offset = (s.rect.min.y - p) / size_y;
                }
                break;
            }
        }
    }

    BSet main = GetBSet(LastActiveLayoutWindowID);
    BSet active = GetBSet(ActiveWindowID);

    bool executed = false;
    For (active.view->hooks) {
        if (it.trigger && MatchEvent(it.trigger, &event)) {
            ProfileScopeEx(it.name);
            it.function();
            LastExecutedManualCommand = it.function;
            executed = true;
            break;
        }
    }

    if (executed == false) {
        For (CommandFunctions) {
            if (it.trigger && MatchEvent(it.trigger, &event)) {
                ProfileScopeEx(it.name);
                it.function();
                LastExecutedManualCommand = it.function;
            }
        }
    }

    if (event.kind == EVENT_DROP_FILE) {
        WindowOpenBufferView(active.window, event.text);
    }

    if (event.kind == EVENT_TEXT_INPUT) {
        Scratch scratch;
        String16 string16 = ToString16(scratch, event.text);
        Replace(active.view, string16);
    }

    if (event.kind == EVENT_QUIT) {
        Command_Quit();
    }

    IF_DEBUG(AssertRanges(main.view->carets));
    IF_DEBUG(AssertRanges(active.view->carets));
}

void EvalCommand(String command) {
    BSet active = GetBSet(ActiveWindowID);
    For (active.view->hooks) {
        if (it.name == command) {
            ProfileScopeEx(it.name);
            it.function();
            return;
        }
    }
    For (CommandFunctions) {
        if (it.name == command) {
            ProfileScopeEx(it.name);
            it.function();
            return;
        }
    }
}

void EvalCommand(String16 command) {
    Scratch scratch;
    EvalCommand(ToString(scratch, command));
}

void GarbageCollect() {
    if (RunGCThisFrame == false) {
        return;
    }
    RunGCThisFrame = false;

    ProfileFunction();
    Allocator sys_allocator = GetSystemAllocator();

    For(Buffers) {
        if (it->file_mod_time) {
            int64_t new_file_mod_time = GetFileModTime(it->name);
            if (it->file_mod_time != new_file_mod_time) {
                it->changed_on_disk = true;
                if (it->dirty == false) ReopenBuffer(it);
            }
        }
    }

    IterRemove(Views) {
        IterRemovePrepare(Views);

        if (it->close && it->id.id == 0) {
            InvalidCodepath();
        }

        Buffer *buffer = GetBuffer(it->active_buffer, NULL);
        if (buffer == NULL || buffer->close) {
            it->close = true;
        }

        if (!it->close) {
            if (!buffer->garbage) {
                continue;
            }

            bool ref = ViewIsReferenced(it->id);
            if (ref) {
                continue;
            }
        }

        RawAppendf(GCInfoBuffer, "View %d %S\n", (int)it->id.id, buffer ? buffer->name : String{"NULL"});
        remove_item = true;
        Dealloc(it);
    }

    IterRemove(Buffers) {
        IterRemovePrepare(Buffers);

        if (it->close && it->id.id == 0) {
            InvalidCodepath();
        }

        if (!it->close) {
            if (!it->garbage) {
                continue;
            }

            bool ref = BufferIsReferenced(it->id);
            if (ref) {
                continue;
            }
        }

        RawAppendf(GCInfoBuffer, "Buff %d %S\n", (int)it->id.id, it->name);
        remove_item = true;
        DeallocBuffer(it);
    }

    IterRemove(Windows) {
        IterRemovePrepare(Windows);
        if (it->close && it->id.id == 0) {
            InvalidCodepath();
        }

        if (it->close) {
            RawAppendf(GCInfoBuffer, "Wind %d %S\n", (int)it->id.id);
            Dealloc(&it->goto_history);
            Dealloc(&it->goto_redo);
            Dealloc(sys_allocator, it);
            remove_item = true;
        } else {
            View *view = FindView(it->active_view, NULL);
            if (!view) {
                JumpToLastValidView(it);
            }
        }
   }
}

void Update(Event event) {
    ProfileFunction();
    LayoutWindows(event.xwindow, event.ywindow);

    Scratch scratch;
    Array<Window *> order = GetWindowZOrder(scratch);
    For(order) {
        if (!it->visible) {
            continue;
        }
        View *view = GetView(it->active_view);
        view->main_caret_on_begin_frame = view->carets[0];
        view->update_scroll = true;
    }

    For (Windows) {
        if (it->jump_history) {
            View *view = GetView(it->active_view);
            it->begin_frame_crumb = {it->active_view, view->carets[0], GetTimeSeconds()};
        }
    }

    OnCommand(event);
    StatusWindowUpdate();
    DebugWindowUpdate();
    CommandWindowUpdate();
    SearchWindowUpdate();
    UpdateProcesses();
    CoUpdate(&event);

    For(IterateInReverse(&order)) {
        if (!it->visible) continue;
        View *view = GetView(it->active_view);
        UpdateScroll(it, !AreEqual(view->main_caret_on_begin_frame, view->carets[0]) && view->update_scroll);
    }

    // We update it here despite the name to make it sure that all the possible changes are
    // included albeit with delayed response. If we did this at the beginning of the frame
    // and the DebugWindowUpdated we wouldnt get to know that in the OnCommand.
    For (Buffers) {
        it->begin_frame_change_id = it->change_id;
    }

    {
        ProfileScope(UpdateWindows);
        For (Windows) {
            if (it->jump_history) {
                View *view = GetView(it->active_view);
                bool are_equal = it->begin_frame_crumb.view_id == it->active_view && AreEqual(view->carets[0], it->begin_frame_crumb.caret);
                if (!are_equal) {
                    if (!it->skip_checkpoint) {
                        Add(&it->goto_history, it->begin_frame_crumb);
                        it->goto_redo.len = 0;
                    }
                }
                it->skip_checkpoint = false;


                if (it->goto_history.len > ConfigJumpHistorySize) {
                    RemoveByIndex(&it->goto_history, 0);
                }
                if (it->goto_redo.len > ConfigJumpHistorySize) {
                    RemoveByIndex(&it->goto_redo, 0);
                }
            }

            if (it->sync_visibility_with_focus) {
                if (it->id == NextActiveWindowID) {
                    it->visible = true;
                } else {
                    it->visible = false;
                }
            }
        }

        ActiveWindowID = NextActiveWindowID;
        Window *window = GetWindow(ActiveWindowID, NULL);
        if (window == NULL || window->visible == false) {
            ActiveWindowID = NextActiveWindowID = LastActiveLayoutWindowID;
            window = GetWindow(ActiveWindowID);
        }

        if (ActiveWindowID != LastActiveLayoutWindowID) {
            if (window->layout) {
                LastActiveLayoutWindowID = ActiveWindowID;
            }
        }
    }

    GarbageCollect();
}

void Windows_SetupVCVarsall(mco_coro *co) {
    View *view = NULL;
    {
        Scratch scratch;
        String working_dir = WorkDir;
        String buffer_name = GetUniqueBufferName(working_dir, "vcvarsall-");
        String cmd = Format(scratch, "\"%S\" && set", ConfigVCVarsall);
        view = ExecHidden(buffer_name, cmd, working_dir);
    }
    for (;;) {
        if (!ProcessIsActive(view->id)) {
            break;
        }
        CoYield(co);
    }

    Scratch scratch;
    Buffer *buffer = GetBuffer(view->active_buffer);
    String16 string16 = GetString(buffer);
    String string = ToString(scratch, string16);
    Array<String> lines = Split(scratch, string, "\n");
    For (lines) {
        String s = Trim(it);
        Array<String> values = Split(scratch, s, "=");
        if (values.len == 1) continue;
        Add(&ProcessEnviroment, Copy(GetSystemAllocator(), s));
    }
}

void MainLoop() {
    ProfileFunction();
    Scratch scratch;
    FrameID += 1;
    Array<Event> frame_events = GetEventsForFrame(scratch);
    Serializer ser = {EventBuffer};
    For(frame_events) {
        if (it.kind != 1) {
            if (!Testing) Serialize(&ser, &it);
        }

        if (it.xwindow == 0 || it.ywindow == 0) {
            int xwindow, ywindow;
            SDL_GetWindowSizeInPixels(SDLWindow, &xwindow, &ywindow);
            it.xwindow = xwindow;
            it.ywindow = ywindow;
        }

        Update(it);
    }


    bool dont_wait_until_resolved = false;
    For (ActiveCoroutines) {
        if (it.dont_wait_until_resolved) {
            dont_wait_until_resolved = true;
            break;
        }
    }

    WaitForEvents = ConfigWaitForEvents;
    if (IsDocumentSelectionValid() || IsScrollbarSelectionValid() || ActiveProcesses.len || dont_wait_until_resolved) {
        WaitForEvents = false;
    }

    // This shouldn't matter to the state of the program, only appearance for
    // the user
    {
        ProfileScope(SetWindowTitle);
        Window     *window = GetActiveWind();
        View       *view   = GetView(window->active_view);
        Buffer     *buffer = GetBuffer(view->active_buffer);
        const char *dirty  = buffer->dirty ? " !" : "";
        String      string = Format(scratch, "%S%s", buffer->name, dirty);
        SDL_SetWindowTitle(SDLWindow, string.data);
    }

    Event *event = GetLast(frame_events);
    SetMouseCursor(*event);
    LayoutWindows(event->xwindow, event->ywindow); // This is here to render changes in title bar size without a frame of delay
    BeginFrameRender(event->xwindow, event->ywindow);

    Array<Window *> order = GetWindowZOrder(scratch);
    For(IterateInReverse(&order)) {
        if (!it->visible) continue;
        DrawWindow(it, *GetLast(frame_events));
    }
    EndFrameRender(event->xwindow, event->ywindow, ColorBackground);
    SDL_GL_SwapWindow(SDLWindow);
}

#if _WIN32
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
#else
extern char **environ;
int main(int argc, char **argv)
#endif
{
    InitOS((OSErrorReport *)printf);
#if _WIN32
    int    argc = __argc;
    char **argv = __argv;
    AttachConsole(ATTACH_PARENT_PROCESS);
#endif
    BeginProfiler();

    if (1) {
        RunArenaTest();
        For (TestFunctions) {
            it.function();
        }

        // ReportErrorf("Testing DONE\n");
        // return 0;
    }

#if !OS_WINDOWS
    for (int i = 0; environ[i]; i += 1) {
        Add(&ProcessEnviroment, Copy(Perm, environ[i]));
    }
#endif

    WorkDir = GetWorkingDir(Perm);
    {
        String sdl_config_path = SDL_GetPrefPath("krzosa", "text_editor");
        if (sdl_config_path.len && sdl_config_path.data[sdl_config_path.len - 1] == '\\') {
            sdl_config_path = Chop(sdl_config_path, 1); // chop '/'
        }
        if (sdl_config_path.len && sdl_config_path.data[sdl_config_path.len - 1] == '/') {
            sdl_config_path = Chop(sdl_config_path, 1); // chop '/'
        }
        ConfigDir = NormalizePath(Perm, sdl_config_path);
        SDL_free(sdl_config_path.data);
    }

    if (!SDL_Init(SDL_INIT_VIDEO)) {
        ReportErrorf("Couldn't initialize SDL! %s", SDL_GetError());
        return 1;
    }

    SDL_EnableScreenSaver();
    SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
#if OS_WASM
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#else
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
#endif

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);

    SDL_DisplayID primary_display_id = SDL_GetPrimaryDisplay();
    const SDL_DisplayMode *display_mode = SDL_GetCurrentDisplayMode(primary_display_id);

    // int w8 = (int)(display_mode->w * 0.8);
    // int h8 = (int)(display_mode->h * 0.8);

    int whalf = (int)(display_mode->w * 0.5) - 10;
    int hhalf = (int)(display_mode->h) - 120;
    int xhalf = whalf;
    int yhalf = 30;

    Uint32 window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
    SDLWindow = SDL_CreateWindow("Text editor", whalf, hhalf, window_flags);
    if (SDLWindow == NULL) {
        ReportErrorf("Couldn't create window! %s", SDL_GetError());
        return 1;
    }
    SDL_SetWindowPosition(SDLWindow, xhalf, yhalf);

    SDL_GLContext gl_context = SDL_GL_CreateContext(SDLWindow);
    SDL_GL_MakeCurrent(SDLWindow, gl_context);
    SDL_ShowWindow(SDLWindow);

    // Set icon
    {
        uint32_t     data    = 0xddddddff;
        SDL_Surface *surface = SDL_CreateSurfaceFrom(1, 1, SDL_PIXELFORMAT_RGBA8888, &data, sizeof(uint32_t));
        SDL_SetWindowIcon(SDLWindow, surface);
        SDL_DestroySurface(surface);
    }

    if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
        ReportErrorf("Couldn't load OpenGL! %s", SDL_GetError());
        return 1;
    }

    SDL_StartTextInput(SDLWindow);
    SDL_SetEventEnabled(SDL_EVENT_DROP_FILE, true);
    SDL_GL_SetSwapInterval(1); // vsync
    {
        float scale = SDL_GetWindowDisplayScale(SDLWindow);
        if (scale != 1.0f) DPIScale = scale;
    }

    InitBuffers();
    InitRender();
    ReloadFont(ConfigFont, (U32)ConfigFontSize);
    InitWindows();
    InitOS(ReportWarningf);

    For (CommandFunctions) {
        if (it.binding.len != 0) {
            it.trigger = ParseKeyCached(it.binding);
        }
    }

    for (int i = 1; i < argc; i += 1) {
        String it = argv[i];

        if (it == "--testing") {
            Testing = true;
        } else if (IsDir(it)) {
            WorkDir = GetAbsolutePath(Perm, it);
            OpenCode(WorkDir);
        } else {
            if (!FileExists(it)) {
                continue;
            }
            Window *window = GetWindow({0});
            WindowOpenBufferView(window, it);
        }
    }

    ReportConsolef("WorkDir = %S", WorkDir);
    if (Testing) InitTests();
#if OS_WINDOWS
    CoData *co_data = CoAdd(Windows_SetupVCVarsall);
    co_data->dont_wait_until_resolved = true;
#endif
#if OS_WASM
    emscripten_set_main_loop(MainLoop, 0, 1);
#else
    while (AppIsRunning) {
        MainLoop();
    }
#endif

    SDL_DestroyWindow(SDLWindow);
    SDL_Quit();

    EndProfiler();

    return 0;
}
