void CommandWindowInit() {
    Window *window = CreateWind();
    CommandWindowID = window->id;
    Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "command_bar"));
    buffer->special = true;
    View *view = CreateView(buffer->id);
    view->special = true;
    window->active_view = view->id;
    window->draw_line_numbers = false;
    window->draw_scrollbar = false;
    window->draw_darker = true;
    window->draw_line_highlight = true;
    window->layout = false;
    window->visible = false;
    window->sync_visibility_with_focus = true;
    window->lose_focus_on_escape = true;
    window->jump_history = false;
}

void CommandWindowLayout(Rect2I *rect, Int wx, Int wy) {
    Window *n = GetWindow(CommandWindowID);
    Rect2I copy_rect = *rect;
    if (!n->visible) {
        rect = &copy_rect;
    }
    Int barsize = Clamp((Int)n->font->line_spacing*10, (Int)0, (Int)wx - 100);
    n->document_rect = n->total_rect = CutBottom(rect, barsize);
}

Int FuzzyRate(String16 string, String16 with) {
    ProfileFunction();
    if (with.len == 0) return 0;
    Int points      = 0;
    Int consecutive = 0;
    Int with_i      = 0;
    for (Int i = 0; i < string.len; i++) {
        if (ToLowerCase(string.data[i]) == ToLowerCase(with[with_i])) {
            consecutive += 1;
            with_i += 1;
        } else {
            with_i = 0;
            points += consecutive * consecutive;
            consecutive = 0;
        }

        if (with_i >= with.len) with_i = 0;
    }
    points += consecutive * consecutive;
    return points;
}

Array<FuzzyPair> FuzzySearchLines(Allocator allocator, Buffer *buffer, Int line_min, Int line_max, String16 needle) {
    ProfileFunction();
    if (line_min < 0 || line_min >= buffer->line_starts.len) return {};
    if (line_max < 0 || line_min > buffer->line_starts.len) return {};
    Array<FuzzyPair> ratings = {allocator};
    for (Int i = line_min; i < line_max; i += 1) {
        String16 s = GetLineStringWithoutNL(buffer, i);
        Int idx = 0;
        if (Seek(s, u" || ", &idx)) {
            s.len = idx;
        }
        s = Trim(s);
        Int  rating = FuzzyRate(s, needle);
        Add(&ratings, {i, rating});
    }

    // bubble sort
    for (Int i = 0; i < ratings.len - 1; i++) {
        for (Int j = 0; j < ratings.len - 1; j++) {
            if (ratings[j].rating > ratings[j + 1].rating) {
                FuzzyPair temp = ratings[j];
                ratings[j]     = ratings[j + 1];
                ratings[j + 1] = temp;
            }
        }
    }

    return ratings;
}

void CommandWindowUpdate() {
    ProfileFunction();
    BSet active = GetBSet(ActiveWindowID);
    if (active.window->id == CommandWindowID) {
        if (!ProcessIsActive(active.view->id)) {
            Scratch scratch;
            String16 line_string = GetLineStringWithoutNL(active.buffer, 0);
            if (active.view->prev_search_line != line_string) {
                active.view->prev_search_line = line_string;
                Array<FuzzyPair> ratings = FuzzySearchLines(scratch, active.buffer, 1, active.buffer->line_starts.len, line_string);

                Buffer *temp_buffer = CreateTempBuffer(scratch, active.buffer->cap);
                RawAppend(temp_buffer, line_string);
                For(IterateInReverse(&ratings)) {
                    String16 s = GetLineStringWithoutNL(active.buffer, it.index);
                    if (s.len == 0) continue;
                    RawAppend(temp_buffer, u"\n");
                    RawAppend(temp_buffer, s);
                }

                Caret caret = active.view->carets[0];
                SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets);
                SelectEntireBuffer(active.view);
                Replace(active.view, GetString(temp_buffer));
                active.view->carets[0] = caret;
            }
        }
    }
}

void Command_ShowCommands() {
    if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == Command_ShowCommands) {
        NextActiveWindowID = LastActiveLayoutWindowID;
        return;
    }
    ProfileFunction();

    BSet command_bar = GetBSet(CommandWindowID);
    command_bar.window->visible = true;
    NextActiveWindowID = command_bar.window->id;
    ResetBuffer(command_bar.buffer);
    For (CommandFunctions) {
        if (it.name == "OpenCommand") {
            continue;
        }
        RawAppendf(command_bar.buffer, "\n:%S", it.name);
    }
    command_bar.view->update_scroll = true;
    SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer));
} RegisterCommand(Command_ShowCommands, "ctrl-shift-p");

void Command_ShowDebugBufferList() {
    if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == Command_ShowDebugBufferList) {
        NextActiveWindowID = LastActiveLayoutWindowID;
        return;
    }
    ProfileFunction();

    BSet command_bar = GetBSet(CommandWindowID);
    command_bar.window->visible = true;
    NextActiveWindowID = command_bar.window->id;
    ResetBuffer(command_bar.buffer);
    For (Buffers) {
        RawAppendf(command_bar.buffer, "\n%S", it->name);
    }
    command_bar.view->update_scroll = true;
    SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer));
} RegisterCommand(Command_ShowDebugBufferList, "");

void Command_ShowBufferList() {
    if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == Command_ShowBufferList) {
        NextActiveWindowID = LastActiveLayoutWindowID;
        return;
    }
    ProfileFunction();

    BSet command_bar = GetBSet(CommandWindowID);
    command_bar.window->visible = true;
    NextActiveWindowID = command_bar.window->id;
    ResetBuffer(command_bar.buffer);
    For (Buffers) {
        if (it->special || it->garbage) {
            continue;
        }
        RawAppendf(command_bar.buffer, "\n%S", it->name);
    }
    command_bar.view->update_scroll = true;
    SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer));
} RegisterCommand(Command_ShowBufferList, "ctrl-p");

void OpenCommand(BSet active) {
    ProfileFunction();
    Range range = active.view->carets[0].range;
    String16 string = FetchLoadWord(active.view);
    if (GetSize(range) == 0) {
        Int line = PosToLine(active.buffer, range.min);
        if (line == 0) {
            line = ClampTop(1ll, active.buffer->line_starts.len - 1ll);
        }
        string = GetLineStringWithoutNL(active.buffer, line);
    }

    Open(string);
}

void Command_OpenCommand() {
    if (ActiveWindowID != CommandWindowID) {
        return;
    }
    BSet active = GetBSet(ActiveWindowID);
    BSet main = GetBSet(LastActiveLayoutWindowID);
    NextActiveWindowID = main.window->id;
    OpenCommand(active);
} RegisterCommand(Command_OpenCommand, "ctrl-q | enter");
