WindowID AllocWindowID(Window *window) {
    return {WindowIDs.id++, window};
}

Window *CreateWind() {
    Allocator allocator = GetSystemAllocator();
    Window *w = AllocType(allocator, Window);
    w->font = &PrimaryFont;
    w->visible = true;
    w->layout = true;
    w->draw_scrollbar = ConfigDrawScrollbar;
    w->draw_line_numbers = ConfigDrawLineNumbers;
    w->draw_line_highlight = true;
    w->jump_history = true;
    w->id = AllocWindowID(w);
    w->weight = 1.0;
    Add(&Windows, w);
    return w;
}

Window *GetWindow(WindowID id, Window *default_window = Windows[0]) {
    For(Windows) {
        if (it->id == id) return it;
    }
    return default_window;
}

void Close(WindowID id) {
    Window *window = GetWindow(id, NULL);
    if (window) {
        if (window->id.id == 0) {
            return;
        }
        window->close = true;
        RunGCThisFrame = true;
    }
}

Window *FindWindow(ViewID view_id, Window *default_window = NULL) {
    For(Windows) {
        if (it->active_view == view_id) {
            return it;
        }
    }
    return default_window;
}

Window *FindWindow(String buffer_name, Window *default_window = NULL) {
    For(Windows) {
        View *it_view = GetView(it->active_view);
        Buffer *it_buffer = GetBuffer(it_view->active_buffer);
        if (it_buffer->name == buffer_name) {
            return it;
        }
    }
    return default_window;
}

Window *FindWindow(BufferID buffer_id) {
    For(Windows) {
        View *view = GetView(it->active_view);
        if (view->active_buffer == buffer_id) return it;
    }
    return NULL;
}

Window *GetActiveWind() {
    return GetWindow(ActiveWindowID);
}

bool IsDocumentSelectionValid() {
    if (DocumentSelected.id == -1) return false;
    return true;
}

bool IsScrollbarSelectionValid() {
    if (ScrollbarSelected.id == -1) return false;
    return true;
}

Array<Window *> GetWindowZOrder(Allocator allocator) {
    Array<Window *> order = {allocator};
    For(Windows) if (it->z == 2) Add(&order, it);
    For(Windows) if (it->z == 1) Add(&order, it);
    For(Windows) if (it->z == 0) Add(&order, it);
    return order;
}

Int GetExpandingBarSize(Window *window) {
    View   *view   = GetView(window->active_view);
    Buffer *buffer = GetBuffer(view->active_buffer);
    float   result = (float)buffer->line_starts.len * window->font->line_spacing;
    return (Int)result;
}

View *WindowOpenBufferView(Window *new_parent_window, String name) {
    View *view = FindView(name);
    if (!view) {
        View *result                   = OpenBufferView(name);
        new_parent_window->active_view = result->id;
        return result;
    }

    Window *window = FindWindow(view->id);
    if (!window) {
        new_parent_window->active_view = view->id;
        return view;
    }
    if (window == new_parent_window) {
        return view;
    }

    Assert(window->active_view.id == view->id.id);
    View *result                   = OpenBufferView(name);
    new_parent_window->active_view = result->id;
    return result;
}

void InitWindows() {
    Scratch scratch;

    CreateWind();
    CommandWindowInit();
    StatusWindowInit();
    DebugWindowInit();
    SearchWindowInit();
}

void CalcNiceties(Window *n) {
    float scrollbar_size = (10.f * DPIScale);
    float line_numbers_size = (float)n->font->char_spacing * 10.f;
    if (n->draw_scrollbar) n->scrollbar_rect = CutRight(&n->document_rect, (Int)scrollbar_size);
    if (n->draw_line_numbers) n->line_numbers_rect = CutLeft(&n->document_rect, (Int)line_numbers_size);
}

double WindowCalcEvenResizerValue(Int screen_size_x, Int *out_count = NULL) {
    double w = 0;
    Int c = 0;
    ForItem(n, Windows) {
        if (n->layout) {
            w += n->weight;
            c += 1;
        }
    }
    if (out_count) *out_count = c;
    return (double)screen_size_x / w;
}

void LayoutWindows(int16_t wx, int16_t wy) {
    ProfileFunction();
    Rect2I screen_rect = RectI0Size(wx, wy);

    CommandWindowLayout(&screen_rect, wx, wy);
    StatusWindowLayout(&screen_rect, wx, wy);
    DebugWindowLayout(&screen_rect, wx, wy);
    SearchWindowLayout(&screen_rect, wx, wy);

    // Column layout
    Int c = 0;
    double size = WindowCalcEvenResizerValue(wx, &c);
    if (c == 0) {
        return;
    }

    int i = 0;
    ForItem(n, Windows) {
        if (!n->layout) {
            continue;
        }

        n->total_rect = n->document_rect = CutLeft(&screen_rect, (Int)(size * n->weight));
        if (i != (c - 1)) {
            Int resizer_size = (Int)(PrimaryFont.char_spacing*0.5f);
            n->resizer_rect = CutRight(&n->document_rect, resizer_size);
        } else {
            n->resizer_rect = {};
        }
        CalcNiceties(n);
        i += 1;
    }
}

Window *GetOverlappingWindow(Vec2I p, Window *default_window = NULL) {
    For(Windows) {
        if (AreOverlapping(p, it->total_rect)) {
            return it;
        }
    }
    return default_window;
}

Vec2I GetSideOfWindow(Window *window, int direction) {
    Vec2I p = {};
    Rect2I rect = window->total_rect;
    float resizer_size = (float)window->font->char_spacing*0.5f; // @check_codebase_when_changing
    if (direction == DIR_LEFT) {
        p.x = rect.min.x - (Int)(resizer_size + window->font->char_spacing);
        p.y = rect.min.y + (rect.max.y / 2);
    } else if (direction == DIR_RIGHT) {
        p.x = rect.max.x + (Int)(resizer_size + window->font->char_spacing);
        p.y = rect.min.y + (rect.max.y / 2);
    } else if (direction == DIR_UP) {
        p.x = rect.min.x + (rect.max.x / 2);
        p.y = rect.min.y - (Int)(resizer_size + window->font->line_spacing);
    } else {
        Assert(direction == DIR_DOWN);
        p.x = rect.min.x + (rect.max.x / 2);
        p.y = rect.max.y + (Int)(resizer_size + window->font->line_spacing);
    }
    return p;
}

Window *SwitchWindow(int direction) {
    Window *window = GetWindow(ActiveWindowID);
    Vec2I p = GetSideOfWindow(window, direction);
    Window *result = GetOverlappingWindow(p, window);
    return result;
}

Int ScreenSpaceToBufferPos(Window *window, View *view, Buffer *buffer, Vec2I mouse) {
    Vec2I mworld = mouse - window->document_rect.min + view->scroll;
    double px = (double)mworld.x / (double)window->font->char_spacing;
    double py = (double)mworld.y / (double)window->font->line_spacing;
    XY    xy     = {(Int)round(px), (Int)floor(py)};
    Int   result = XYToPosWithoutNL(buffer, xy);
    return result;
}

Int ScreenSpaceToBufferPosErrorOutOfBounds(Window *window, View *view, Buffer *buffer, Vec2I mouse) {
    Vec2I mworld = mouse - window->document_rect.min + view->scroll;
    double px = (double)mworld.x / (double)window->font->char_spacing;
    double py = (double)mworld.y / (double)window->font->line_spacing;
    XY    xy     = {(Int)round(px), (Int)floor(py)};
    Int   result = XYToPosErrorOutOfBounds(buffer, xy);
    return result;
}

GotoCrumb PopCrumb(Array<GotoCrumb> *cr) {
    for (; cr->len;) {
        GotoCrumb c = Pop(cr);
        View *view = FindView(c.view_id, NULL);
        if (view) {
            return c;
        }
    }
    return {};
}

View *GetLastValidView(Window *window) {
    For (IterateInReverse(&window->goto_redo)) {
        if (it.view_id == window->active_view) continue;
        View *view = FindView(it.view_id, NULL);
        if (view) return view;
    }
    For (IterateInReverse(&window->goto_history)) {
        if (it.view_id == window->active_view) continue;
        View *view = FindView(it.view_id, NULL);
        if (view) return view;
    }
    return Views[0];
}

View *JumpToLastValidView(Window *window) {
    View *view = GetLastValidView(window);
    window->active_view = view->id;
    return view;
}

void JumpBack(Window *window) {
    if (window->jump_history == false) return;
    if (window->goto_history.len <= 0) return;
    BSet set = GetBSet(window);
    Add(&window->goto_redo, {set.view->id, set.view->carets[0], GetTimeSeconds()});

    GotoCrumb c = PopCrumb(&window->goto_history);
    window->active_view = c.view_id;
    View *view = GetView(c.view_id);
    view->carets[0]  = c.caret;
    UpdateScroll(window, true);

    if (window->goto_history.len) {
        GotoCrumb *next = GetLast(window->goto_history);
        if (c.view_id == next->view_id && c.time - next->time <= ConfigJumpHistoryMergeTimeWindow) {
            JumpBack(window);
        }
    }
}

void JumpForward(Window *window) {
    if (window->jump_history == false) return;
    if (window->goto_redo.len <= 0) return;
    BSet set = GetBSet(window);
    Add(&window->goto_history, {set.view->id, set.view->carets[0], GetTimeSeconds()});

    GotoCrumb c = PopCrumb(&window->goto_redo);
    window->active_view = c.view_id;
    View *view = GetView(c.view_id);
    view->carets[0]  = c.caret;
    UpdateScroll(window, true);

    if (window->goto_redo.len) {
        GotoCrumb *next = GetLast(window->goto_redo);
        if (c.view_id == next->view_id && next->time - c.time <= ConfigJumpHistoryMergeTimeWindow) {
            JumpForward(window);
        }
    }
}
