/*
 * Lister base
 */

// TOP

function Vec2_f32
panel_space_from_screen_space(Vec2_f32 p, Vec2_f32 file_region_p0){
    return(p - file_region_p0);
}

function Vec2_f32
get_mouse_position_in_panel_space(Mouse_State mouse, Vec2_f32 file_region_p0){
    return(panel_space_from_screen_space(V2f32(mouse.p), file_region_p0));
}

function Vec2_f32
get_mouse_position_in_panel_space(Application_Links *app, Vec2_f32 file_region_p0){
    return(get_mouse_position_in_panel_space(get_mouse_state(app), file_region_p0));
}

////////////////////////////////

function f32
lister_get_text_field_height(f32 line_height){
    return(line_height);
}

function f32
lister_get_block_height(f32 line_height){
    return(line_height*2);
}

function Rect_f32_Pair
lister_get_top_level_layout(Rect_f32 rect, f32 text_field_height){
    return(rect_split_top_bottom(rect, text_field_height));
}

////////////////////////////////

function Lister*
view_get_lister(Application_Links *app, View_ID view){
    Managed_Scope scope = view_get_managed_scope(app, view);
    Lister **ptr = scope_attachment(app, scope, view_lister_loc, Lister*);
    Lister *result = 0;
    if (ptr != 0){
        result = *ptr;
    }
    return(result);
}

function Lister*
view_set_lister(Application_Links *app, View_ID view, Lister *lister){
    Managed_Scope scope = view_get_managed_scope(app, view);
    Lister **ptr = scope_attachment(app, scope, view_lister_loc, Lister*);
    Lister *result = 0;
    if (ptr != 0){
        result = *ptr;
        *ptr = lister;
    }
    return(result);
}

function void
lister_set_map(Lister *lister, Mapping *mapping, Command_Map *map){
    lister->mapping = mapping;
    lister->map = map;
}

function void
lister_set_map(Lister *lister, Mapping *mapping, Command_Map_ID map){
    lister->mapping = mapping;
    lister->map = mapping_get_map(mapping, map);
}

function Lister_Prev_Current
begin_lister(Application_Links *app, Arena *arena){
    Lister_Prev_Current result = {};
    Lister *lister = push_array_zero(arena, Lister, 1);
    lister->arena = arena;
    lister->query = Su8(lister->query_space, 0, sizeof(lister->query_space));
    lister->text_field = Su8(lister->text_field_space, 0, sizeof(lister->text_field_space));
    lister->key_string = Su8(lister->key_string_space, 0, sizeof(lister->key_string_space));
    View_ID view = get_this_ctx_view(app, Access_Always);
    result.prev = view_set_lister(app, view, lister);
    result.current = lister;
    lister->restore_all_point = begin_temp(lister->arena);
    View_Context ctx = view_current_context(app, view);
    lister_set_map(lister, ctx.mapping, ctx.map_id);
    return(result);
}

Lister_Block::Lister_Block(Application_Links *a, Arena *arena){
    Lister_Prev_Current new_lister = begin_lister(a, arena);
    this->app = a;
    this->lister = new_lister;
}

Lister_Block::~Lister_Block(){
    View_ID view = get_this_ctx_view(app, Access_Always);
    view_set_lister(this->app, view, this->lister.prev);
}

Lister_Block::operator Lister *(){
    return(this->lister.current);
}

function void
lister_set_string(String_Const_u8 string, String_u8 *target){
    target->size = 0;
    string_append(target, string);
}
function void
lister_append_string(String_Const_u8 string, String_u8 *target){
    string_append(target, string);
}

function void
lister_set_query(Lister *lister, String_Const_u8 string){
    lister_set_string(string, &lister->query);
}
function void
lister_set_query(Lister *lister, char *string){
    lister_set_string(SCu8(string), &lister->query);
}
function void
lister_set_text_field(Lister *lister, String_Const_u8 string){
    lister_set_string(string, &lister->text_field);
}
function void
lister_set_text_field(Lister *lister, char *string){
    lister_set_string(SCu8(string), &lister->text_field);
}
function void
lister_set_key(Lister *lister, String_Const_u8 string){
    lister_set_string(string, &lister->key_string);
}
function void
lister_set_key(Lister *lister, char *string){
    lister_set_string(SCu8(string), &lister->key_string);
}

function void
lister_append_query(Lister *lister, String_Const_u8 string){
    lister_append_string(string, &lister->query);
}
function void
lister_append_query(Lister *lister, char *string){
    lister_append_string(SCu8(string), &lister->query);
}
function void
lister_append_text_field(Lister *lister, String_Const_u8 string){
    lister_append_string(string, &lister->text_field);
}
function void
lister_append_text_field(Lister *lister, char *string){
    lister_append_string(SCu8(string), &lister->text_field);
}
function void
lister_append_key(Lister *lister, String_Const_u8 string){
    lister_append_string(string, &lister->key_string);
}
function void
lister_append_key(Lister *lister, char *string){
    lister_append_string(SCu8(string), &lister->key_string);
}

function void
lister_set_handlers(Lister *lister, Lister_Handlers *handlers){
    block_copy_struct(&lister->handlers, handlers);
}

function void
lister_zero_scroll(Lister *lister){
    block_zero_struct(&lister->scroll);
}

function void
lister_render(Application_Links *app, Frame_Info frame_info, View_ID view){
    Scratch_Block scratch(app);
    
    Lister *lister = view_get_lister(app, view);
    if (lister == 0){
        return;
    }
    
    Rect_f32 region = draw_background_and_margin(app, view);
    Rect_f32 prev_clip = draw_set_clip(app, region);
    
    Face_ID face_id = get_face_id(app, 0);
    Face_Metrics metrics = get_face_metrics(app, face_id);
    f32 line_height = metrics.line_height;
    f32 block_height = lister_get_block_height(line_height);
    f32 text_field_height = lister_get_text_field_height(line_height);
    
    // NOTE(allen): file bar
    // TODO(allen): What's going on with 'showing_file_bar'? I found it like this.
    b64 showing_file_bar = false;
    b32 hide_file_bar_in_ui = def_get_config_b32(vars_save_string_lit("hide_file_bar_in_ui"));
    if (view_get_setting(app, view, ViewSetting_ShowFileBar, &showing_file_bar) &&
        showing_file_bar && !hide_file_bar_in_ui){
        Rect_f32_Pair pair = layout_file_bar_on_top(region, line_height);
        Buffer_ID buffer = view_get_buffer(app, view, Access_Always);
        draw_file_bar(app, view, buffer, face_id, pair.min);
        region = pair.max;
    }
    
    Mouse_State mouse = get_mouse_state(app);
    Vec2_f32 m_p = V2f32(mouse.p);
    
    lister->visible_count = (i32)((rect_height(region)/block_height)) - 3;
    lister->visible_count = clamp_bot(1, lister->visible_count);
    
    Rect_f32 text_field_rect = {};
    Rect_f32 list_rect = {};
    {
        Rect_f32_Pair pair = lister_get_top_level_layout(region, text_field_height);
        text_field_rect = pair.min;
        list_rect = pair.max;
    }
    
    {
        Vec2_f32 p = V2f32(text_field_rect.x0 + 3.f, text_field_rect.y0);
        Fancy_Line text_field = {};
        push_fancy_string(scratch, &text_field, fcolor_id(defcolor_pop1),
                          lister->query.string);
        push_fancy_stringf(scratch, &text_field, " ");
        p = draw_fancy_line(app, face_id, fcolor_zero(), &text_field, p);
        
        // TODO(allen): This is a bit of a hack. Maybe an upgrade to fancy to focus
        // more on being good at this and less on overriding everything 10 ways to sunday
        // would be good.
        block_zero_struct(&text_field);
        push_fancy_string(scratch, &text_field, fcolor_id(defcolor_text_default),
                          lister->text_field.string);
        f32 width = get_fancy_line_width(app, face_id, &text_field);
        f32 cap_width = text_field_rect.x1 - p.x - 6.f;
        if (cap_width < width){
            Rect_f32 prect = draw_set_clip(app, Rf32(p.x, text_field_rect.y0, p.x + cap_width, text_field_rect.y1));
            p.x += cap_width - width;
            draw_fancy_line(app, face_id, fcolor_zero(), &text_field, p);
            draw_set_clip(app, prect);
        }
        else{
            draw_fancy_line(app, face_id, fcolor_zero(), &text_field, p);
        }
    }
    
    
    Range_f32 x = rect_range_x(list_rect);
    draw_set_clip(app, list_rect);
    
    // NOTE(allen): auto scroll to the item if the flag is set.
    f32 scroll_y = lister->scroll.position.y;
    
    if (lister->set_vertical_focus_to_item){
        lister->set_vertical_focus_to_item = false;
        Range_f32 item_y = If32_size(lister->item_index*block_height, block_height);
        f32 view_h = rect_height(list_rect);
        Range_f32 view_y = If32_size(scroll_y, view_h);
        if (view_y.min > item_y.min || item_y.max > view_y.max){
            f32 item_center = (item_y.min + item_y.max)*0.5f;
            f32 view_center = (view_y.min + view_y.max)*0.5f;
            f32 margin = view_h*.3f;
            margin = clamp_top(margin, block_height*3.f);
            if (item_center < view_center){
                lister->scroll.target.y = item_y.min - margin;
            }
            else{
                f32 target_bot = item_y.max + margin;
                lister->scroll.target.y = target_bot - view_h;
            }
        }
    }
    
    // NOTE(allen): clamp scroll target and position; smooth scroll rule
    i32 count = lister->filtered.count;
    Range_f32 scroll_range = If32(0.f, clamp_bot(0.f, count*block_height - block_height));
    lister->scroll.target.y = clamp_range(scroll_range, lister->scroll.target.y);
    lister->scroll.target.x = 0.f;
    
    Vec2_f32_Delta_Result delta = delta_apply(app, view,
                                              frame_info.animation_dt, lister->scroll);
    lister->scroll.position = delta.p;
    if (delta.still_animating){
        animate_in_n_milliseconds(app, 0);
    }
    
    lister->scroll.position.y = clamp_range(scroll_range, lister->scroll.position.y);
    lister->scroll.position.x = 0.f;
    
    scroll_y = lister->scroll.position.y;
    f32 y_pos = list_rect.y0 - scroll_y;
    
    i32 first_index = (i32)(scroll_y/block_height);
    y_pos += first_index*block_height;
    
    for (i32 i = first_index; i < count; i += 1){
        Lister_Node *node = lister->filtered.node_ptrs[i];
        
        Range_f32 y = If32(y_pos, y_pos + block_height);
        y_pos = y.max;
        
        Rect_f32 item_rect = Rf32(x, y);
        Rect_f32 item_inner = rect_inner(item_rect, 3.f);
        
        b32 hovered = rect_contains_point(item_rect, m_p);
        UI_Highlight_Level highlight = UIHighlight_None;
        if (node == lister->highlighted_node){
            highlight = UIHighlight_Active;
        }
        else if (node->user_data == lister->hot_user_data){
            if (hovered){
                highlight = UIHighlight_Active;
            }
            else{
                highlight = UIHighlight_Hover;
            }
        }
        else if (hovered){
            highlight = UIHighlight_Hover;
        }
        
        u64 lister_roundness_100 = def_get_config_u64(app, vars_save_string_lit("lister_roundness"));
        f32 roundness = block_height*lister_roundness_100*0.01f;
        draw_rectangle_fcolor(app, item_rect, roundness, get_item_margin_color(highlight));
        draw_rectangle_fcolor(app, item_inner, roundness, get_item_margin_color(highlight, 1));
        
        Fancy_Line line = {};
        push_fancy_string(scratch, &line, fcolor_id(defcolor_text_default), node->string);
        push_fancy_stringf(scratch, &line, " ");
        push_fancy_string(scratch, &line, fcolor_id(defcolor_pop2), node->status);
        
        Vec2_f32 p = item_inner.p0 + V2f32(3.f, (block_height - line_height)*0.5f);
        draw_fancy_line(app, face_id, fcolor_zero(), &line, p);
    }
    
    draw_set_clip(app, prev_clip);
}

function void*
lister_get_user_data(Lister *lister, i32 index){
    void *result = 0;
    if (0 <= index && index < lister->options.count){
        i32 counter = 0;
        for (Lister_Node *node = lister->options.first;
             node != 0;
             node = node->next, counter += 1){
            if (counter == index){
                result = node->user_data;
                break;
            }
        }
    }
    return(result);
}

function Lister_Filtered
lister_get_filtered(Arena *arena, Lister *lister){
    i32 node_count = lister->options.count;
    
    Lister_Filtered filtered = {};
    filtered.exact_matches.node_ptrs = push_array(arena, Lister_Node*, 1);
    filtered.before_extension_matches.node_ptrs = push_array(arena, Lister_Node*, node_count);
    filtered.substring_matches.node_ptrs = push_array(arena, Lister_Node*, node_count);
    
    Temp_Memory_Block temp(arena);
    
    String_Const_u8 key = lister->key_string.string;
    key = push_string_copy(arena, key);
    string_mod_replace_character(key, '_', '*');
    string_mod_replace_character(key, ' ', '*');
    
    List_String_Const_u8 absolutes = {};
    string_list_push(arena, &absolutes, string_u8_litexpr(""));
    List_String_Const_u8 splits = string_split(arena, key, (u8*)"*", 1);
    b32 has_wildcard = (splits.node_count > 1);
    string_list_push(&absolutes, &splits);
    string_list_push(arena, &absolutes, string_u8_litexpr(""));
    
    for (Lister_Node *node = lister->options.first;
         node != 0;
         node = node->next){
        String_Const_u8 node_string = node->string;
        if (key.size == 0 || string_wildcard_match_insensitive(absolutes, node_string)){
            if (string_match_insensitive(node_string, key) && filtered.exact_matches.count == 0){
                filtered.exact_matches.node_ptrs[filtered.exact_matches.count++] = node;
            }
            else if (key.size > 0 &&
                     !has_wildcard &&
                     string_match_insensitive(string_prefix(node_string, key.size), key) &&
                     node->string.size > key.size &&
                     node->string.str[key.size] == '.'){
                filtered.before_extension_matches.node_ptrs[filtered.before_extension_matches.count++] = node;
            }
            else{
                filtered.substring_matches.node_ptrs[filtered.substring_matches.count++] = node;
            }
        }
    }
    
    return(filtered);
}

function void
lister_update_selection_values(Lister *lister){
    lister->raw_item_index = -1;
    lister->highlighted_node = 0;
    i32 count = lister->filtered.count;
    for (i32 i = 0; i < count; i += 1){
        Lister_Node *node = lister->filtered.node_ptrs[i];
        if (lister->item_index == i){
            lister->highlighted_node = node;
            lister->raw_item_index = node->raw_index;
        }
    }
}

function void
lister_update_filtered_list(Application_Links *app, Lister *lister){
    Arena *arena = lister->arena;
    Scratch_Block scratch(app, arena);
    
    Lister_Filtered filtered = lister_get_filtered(scratch, lister);
    
    Lister_Node_Ptr_Array node_ptr_arrays[] = {
        filtered.exact_matches,
        filtered.before_extension_matches,
        filtered.substring_matches,
    };
    
    end_temp(lister->filter_restore_point);
    
    i32 total_count = 0;
    for (i32 array_index = 0; array_index < ArrayCount(node_ptr_arrays); array_index += 1){
        Lister_Node_Ptr_Array node_ptr_array = node_ptr_arrays[array_index];
        total_count += node_ptr_array.count;
    }
    
    Lister_Node **node_ptrs = push_array(arena, Lister_Node*, total_count);
    lister->filtered.node_ptrs = node_ptrs;
    lister->filtered.count = total_count;
    i32 counter = 0;
    for (i32 array_index = 0; array_index < ArrayCount(node_ptr_arrays); array_index += 1){
        Lister_Node_Ptr_Array node_ptr_array = node_ptr_arrays[array_index];
        for (i32 node_index = 0; node_index < node_ptr_array.count; node_index += 1){
            Lister_Node *node = node_ptr_array.node_ptrs[node_index];
            node_ptrs[counter] = node;
            counter += 1;
        }
    }
    
    lister_update_selection_values(lister);
}

function void
lister_call_refresh_handler(Application_Links *app, Lister *lister){
    if (lister->handlers.refresh != 0){
        lister->handlers.refresh(app, lister);
        lister->filter_restore_point = begin_temp(lister->arena);
        lister_update_filtered_list(app, lister);
    }
}

function void
lister_activate(Application_Links *app, Lister *lister, void *user_data, b32 mouse){
    lister->out.activated_by_click = mouse;
    lister->out.text_field = lister->text_field.string;
    lister->out.user_data = user_data;
}

function void*
lister_user_data_at_p(Application_Links *app, View_ID view, Lister *lister, Vec2_f32 m_p){
    Rect_f32 region = view_get_screen_rect(app, view);
    // TODO(allen): eliminate this. bad bad bad bad :(
    region = rect_inner(region, 3.f);
    
    Face_ID face_id = get_face_id(app, 0);
    Face_Metrics metrics = get_face_metrics(app, face_id);
    f32 line_height = metrics.line_height;
    f32 block_height = lister_get_block_height(line_height);
    f32 text_field_height = lister_get_text_field_height(line_height);
    
    b64 showing_file_bar = false;
    b32 hide_file_bar_in_ui = def_get_config_b32(vars_save_string_lit("hide_file_bar_in_ui"));
    if (view_get_setting(app, view, ViewSetting_ShowFileBar, &showing_file_bar) &&
        showing_file_bar && hide_file_bar_in_ui){
        Rect_f32_Pair pair = layout_file_bar_on_top(region, line_height);
        region = pair.max;
    }
    
    Rect_f32_Pair pair = lister_get_top_level_layout(region, text_field_height);
    Rect_f32 list_rect = pair.max;
    
    void *result = 0;
    if (rect_contains_point(list_rect, m_p)){
        f32 y = m_p.y - list_rect.y0 + lister->scroll.position.y;
        i32 index = (i32)(y/block_height);
        if (0 <= index && index < lister->filtered.count){
            Lister_Node *node = lister->filtered.node_ptrs[index];
            result = node->user_data;
        }
    }
    
    return(result);
}

function Lister_Result
run_lister(Application_Links *app, Lister *lister){
    lister->filter_restore_point = begin_temp(lister->arena);
    lister_update_filtered_list(app, lister);
    
    View_ID view = get_this_ctx_view(app, Access_Always);
    View_Context ctx = view_current_context(app, view);
    ctx.render_caller = lister_render;
    ctx.hides_buffer = true;
    View_Context_Block ctx_block(app, view, &ctx);
    
    for (;;){
        User_Input in = get_next_input(app, EventPropertyGroup_Any, EventProperty_Escape);
        if (in.abort){
            block_zero_struct(&lister->out);
            lister->out.canceled = true;
            break;
        }
        
        Lister_Activation_Code result = ListerActivation_Continue;
        b32 handled = true;
        switch (in.event.kind){
            case InputEventKind_TextInsert:
            {
                if (lister->handlers.write_character != 0){
                    result = lister->handlers.write_character(app);
                }
            }break;
            
            case InputEventKind_KeyStroke:
            {
                switch (in.event.key.code){
                    case KeyCode_Return:
                    case KeyCode_Tab:
                    {
                        void *user_data = 0;
                        if (0 <= lister->raw_item_index &&
                            lister->raw_item_index < lister->options.count){
                            user_data = lister_get_user_data(lister, lister->raw_item_index);
                        }
                        lister_activate(app, lister, user_data, false);
                        result = ListerActivation_Finished;
                    }break;
                    
                    case KeyCode_Backspace:
                    {
                        if (lister->handlers.backspace != 0){
                            lister->handlers.backspace(app);
                        }
                        else if (lister->handlers.key_stroke != 0){
                            result = lister->handlers.key_stroke(app);
                        }
                        else{
                            handled = false;
                        }
                    }break;
                    
                    case KeyCode_Up:
                    {
                        if (lister->handlers.navigate != 0){
                            lister->handlers.navigate(app, view, lister, -1);
                        }
                        else if (lister->handlers.key_stroke != 0){
                            result = lister->handlers.key_stroke(app);
                        }
                        else{
                            handled = false;
                        }
                    }break;
                    
                    case KeyCode_Down:
                    {
                        if (lister->handlers.navigate != 0){
                            lister->handlers.navigate(app, view, lister, 1);
                        }
                        else if (lister->handlers.key_stroke != 0){
                            result = lister->handlers.key_stroke(app);
                        }
                        else{
                            handled = false;
                        }
                    }break;
                    
                    case KeyCode_PageUp:
                    {
                        if (lister->handlers.navigate != 0){
                            lister->handlers.navigate(app, view, lister,
                                                      -lister->visible_count);
                        }
                        else if (lister->handlers.key_stroke != 0){
                            result = lister->handlers.key_stroke(app);
                        }
                        else{
                            handled = false;
                        }
                    }break;
                    
                    case KeyCode_PageDown:
                    {
                        if (lister->handlers.navigate != 0){
                            lister->handlers.navigate(app, view, lister,
                                                      lister->visible_count);
                        }
                        else if (lister->handlers.key_stroke != 0){
                            result = lister->handlers.key_stroke(app);
                        }
                        else{
                            handled = false;
                        }
                    }break;
                    
                    default:
                    {
                        if (lister->handlers.key_stroke != 0){
                            result = lister->handlers.key_stroke(app);
                        }
                        else{
                            handled = false;
                        }
                    }break;
                }
            }break;
            
            case InputEventKind_MouseButton:
            {
                switch (in.event.mouse.code){
                    case MouseCode_Left:
                    {
                        Vec2_f32 p = V2f32(in.event.mouse.p);
                        void *clicked = lister_user_data_at_p(app, view, lister, p);
                        lister->hot_user_data = clicked;
                    }break;
                    
                    default:
                    {
                        handled = false;
                    }break;
                }
            }break;
            
            case InputEventKind_MouseButtonRelease:
            {
                switch (in.event.mouse.code){
                    case MouseCode_Left:
                    {
                        if (lister->hot_user_data != 0){
                            Vec2_f32 p = V2f32(in.event.mouse.p);
                            void *clicked = lister_user_data_at_p(app, view, lister, p);
                            if (lister->hot_user_data == clicked){
                                lister_activate(app, lister, clicked, true);
                                result = ListerActivation_Finished;
                            }
                        }
                        lister->hot_user_data = 0;
                    }break;
                    
                    default:
                    {
                        handled = false;
                    }break;
                }
            }break;
            
            case InputEventKind_MouseWheel:
            {
                Mouse_State mouse = get_mouse_state(app);
                lister->scroll.target.y += mouse.wheel;
                lister_update_filtered_list(app, lister);
            }break;
            
            case InputEventKind_MouseMove:
            {
                lister_update_filtered_list(app, lister);
            }break;
            
            case InputEventKind_Core:
            {
                switch (in.event.core.code){
                    case CoreCode_Animate:
                    {
                        lister_update_filtered_list(app, lister);
                    }break;
                    
                    default:
                    {
                        handled = false;
                    }break;
                }
            }break;
            
            default:
            {
                handled = false;
            }break;
        }
        
        if (result == ListerActivation_Finished){
            break;
        }
        
        if (!handled){
            Mapping *mapping = lister->mapping;
            Command_Map *map = lister->map;
            
            Fallback_Dispatch_Result disp_result =
                fallback_command_dispatch(app, mapping, map, &in);
            if (disp_result.code == FallbackDispatch_DelayedUICall){
                call_after_ctx_shutdown(app, view, disp_result.func);
                break;
            }
            if (disp_result.code == FallbackDispatch_Unhandled){
                leave_current_input_unhandled(app);
            }
            else{
                lister_call_refresh_handler(app, lister);
            }
        }
    }
    
    return(lister->out);
}

function Lister_Prealloced_String
lister_prealloced(String_Const_u8 string){
    Lister_Prealloced_String result = {};
    result.string = string;
    return(result);
}

function void
lister_begin_new_item_set(Application_Links *app, Lister *lister){
    end_temp(lister->restore_all_point);
    block_zero_struct(&lister->options);
    block_zero_struct(&lister->filtered);
}

function void*
lister_add_item(Lister *lister, Lister_Prealloced_String string, Lister_Prealloced_String status, void *user_data, u64 extra_space){
    void *base_memory = push_array(lister->arena, u8, sizeof(Lister_Node) + extra_space);
    Lister_Node *node = (Lister_Node*)base_memory;
    node->string = string.string;
    node->status = status.string;
    node->user_data = user_data;
    node->raw_index = lister->options.count;
    zdll_push_back(lister->options.first, lister->options.last, node);
    lister->options.count += 1;
    void *result = (node + 1);
    return(result);
}

function void*
lister_add_item(Lister *lister, Lister_Prealloced_String string, String_Const_u8 status,
                void *user_data, u64  extra_space){
    return(lister_add_item(lister, string, lister_prealloced(push_string_copy(lister->arena, status)), user_data, extra_space));
}

function void*
lister_add_item(Lister *lister, String_Const_u8 string, Lister_Prealloced_String status, void *user_data, u64 extra_space){
    return(lister_add_item(lister, lister_prealloced(push_string_copy(lister->arena, string)), status, user_data, extra_space));
}

function void*
lister_add_item(Lister *lister, String_Const_u8 string, String_Const_u8 status, void *user_data, u64 extra_space){
    return(lister_add_item(lister,
                           lister_prealloced(push_string_copy(lister->arena, string)),
                           lister_prealloced(push_string_copy(lister->arena, status)),
                           user_data, extra_space));
}

function Lister_Activation_Code
lister__write_string__default(Application_Links *app){
    Lister_Activation_Code result = ListerActivation_Continue;
    View_ID view = get_active_view(app, Access_Always);
    Lister *lister = view_get_lister(app, view);
    if (lister != 0){
        User_Input in = get_current_input(app);
        String_Const_u8 string = to_writable(&in);
        if (string.str != 0 && string.size > 0){
            lister_append_text_field(lister, string);
            lister_append_key(lister, string);
            lister->item_index = 0;
            lister_zero_scroll(lister);
            lister_update_filtered_list(app, lister);
        }
    }
    return(result);
}

function void
lister__backspace_text_field__default(Application_Links *app){
    View_ID view = get_active_view(app, Access_Always);
    Lister *lister = view_get_lister(app, view);
    if (lister != 0){
        lister->text_field.string = backspace_utf8(lister->text_field.string);
        lister->key_string.string = backspace_utf8(lister->key_string.string);
        lister->item_index = 0;
        lister_zero_scroll(lister);
        lister_update_filtered_list(app, lister);
    }
}

function void
lister__navigate__default(Application_Links *app, View_ID view, Lister *lister, i32 delta){
    i32 new_index = lister->item_index + delta;
    if (new_index < 0 && lister->item_index == 0){
        lister->item_index = lister->filtered.count - 1;
    }
    else if (new_index >= lister->filtered.count &&
             lister->item_index == lister->filtered.count - 1){
        lister->item_index = 0;
    }
    else{
        lister->item_index = clamp(0, new_index, lister->filtered.count - 1);
    }
    lister->set_vertical_focus_to_item = true;
    lister_update_selection_values(lister);
}

function Lister_Handlers
lister_get_default_handlers(void){
    Lister_Handlers handlers = {};
    handlers.write_character = lister__write_string__default;
    handlers.backspace       = lister__backspace_text_field__default;
    handlers.navigate        = lister__navigate__default;
    return(handlers);
}

function void
lister_set_default_handlers(Lister *lister){
    Lister_Handlers handlers = lister_get_default_handlers();
    lister_set_handlers(lister, &handlers);
}

////////////////////////////////

function Lister_Result
run_lister_with_refresh_handler(Application_Links *app, Arena *arena, String_Const_u8 query, Lister_Handlers handlers){
    Lister_Result result = {};
    if (handlers.refresh != 0){
        Lister_Block lister(app, arena);
        lister_set_query(lister, query);
        lister_set_handlers(lister, &handlers);
        handlers.refresh(app, lister);
        result = run_lister(app, lister);
    }
    else{
#define M "ERROR: No refresh handler specified for lister (query_string = \"%.*s\")\n"
        String_Const_u8 str = push_u8_stringf(arena, M, string_expand(query));
#undef M
        print_message(app, str);
        result.canceled = true;
    }
    return(result);
}

function Lister_Result
run_lister_with_refresh_handler(Application_Links *app, String_Const_u8 query,  Lister_Handlers handlers){
    Scratch_Block scratch(app);
    return(run_lister_with_refresh_handler(app, scratch, query, handlers));
}

function Lister_Result
run_lister_with_refresh_handler(Application_Links *app, Arena *arena, char *query, Lister_Handlers handlers){
    return(run_lister_with_refresh_handler(app, arena, SCu8(query), handlers));
}

function Lister_Result
run_lister_with_refresh_handler(Application_Links *app, char *query, Lister_Handlers handlers){
    return(run_lister_with_refresh_handler(app, SCu8(query), handlers));
}

////////////////////////////////

function void
lister_choice(Arena *arena, Lister_Choice_List *list, String_Const_u8 string, String_Const_u8 status, Key_Code code, u64 user_data){
    Lister_Choice *choice = push_array(arena, Lister_Choice, 1);
    sll_queue_push(list->first, list->last, choice);
    choice->string = string;
    choice->status = status;
    choice->key_code = code;
    choice->user_data = user_data;
}

function void
lister_choice(Arena *arena, Lister_Choice_List *list, char *string, String_Const_u8 status, Key_Code code, u64 user_data){
    lister_choice(arena, list, SCu8(string), status, code, (u64)PtrAsInt(user_data));
}

function void
lister_choice(Arena *arena, Lister_Choice_List *list, String_Const_u8 string, char *status, Key_Code code, u64 user_data){
    lister_choice(arena, list, string, SCu8(status), code, (u64)PtrAsInt(user_data));
}

function void
lister_choice(Arena *arena, Lister_Choice_List *list, char *string, char *status, Key_Code code, u64 user_data){
    lister_choice(arena, list, SCu8(string), SCu8(status), code,
                  (u64)PtrAsInt(user_data));
}

function void
lister_choice(Arena *arena, Lister_Choice_List *list, String_Const_u8 string, String_Const_u8 status, Key_Code code, void *user_data){
    lister_choice(arena, list, string, status, code, (u64)PtrAsInt(user_data));
}

function void
lister_choice(Arena *arena, Lister_Choice_List *list, char *string, String_Const_u8 status, Key_Code code, void *user_data){
    lister_choice(arena, list, string, status, code, (u64)PtrAsInt(user_data));
}

function void
lister_choice(Arena *arena, Lister_Choice_List *list, String_Const_u8 string, char *status, Key_Code code, void *user_data){
    lister_choice(arena, list, string, status, code, (u64)PtrAsInt(user_data));
}

function void
lister_choice(Arena *arena, Lister_Choice_List *list, char *string, char *status, Key_Code code, void *user_data){
    lister_choice(arena, list, string, status, code, (u64)PtrAsInt(user_data));
}

function Lister_Activation_Code
lister__key_stroke__choice_list(Application_Links *app){
    Lister_Activation_Code result = ListerActivation_Continue;
    View_ID view = get_active_view(app, Access_Always);
    Lister *lister = view_get_lister(app, view);
    if (lister != 0){
        User_Input in = get_current_input(app);
        if (in.event.kind == InputEventKind_KeyStroke){
            void *user_data = 0;
            b32 did_shortcut_key = false;
            for (Lister_Node *node = lister->options.first;
                 node != 0;
                 node = node->next){
                Key_Code *key_code = (Key_Code*)(node + 1);
                if (*key_code == in.event.key.code){
                    user_data = node->user_data;
                    did_shortcut_key = true;
                    break;
                }
            }
            if (did_shortcut_key){
                lister_activate(app, lister, user_data, false);
                result = ListerActivation_Finished;
            }
        }
    }
    return(result);
}

function Lister_Choice*
get_choice_from_user(Application_Links *app, String_Const_u8 query,
                     Lister_Choice_List list){
    Scratch_Block scratch(app);
    Lister_Block lister(app, scratch);
    for (Lister_Choice *choice = list.first;
         choice != 0;
         choice = choice->next){
        u64 code_size = sizeof(choice->key_code);
        void *extra = lister_add_item(lister, choice->string, choice->status,
                                      choice, code_size);
        block_copy(extra, &choice->key_code, code_size);
    }
    lister_set_query(lister, query);
    Lister_Handlers handlers = {};
    handlers.navigate        = lister__navigate__default;
    handlers.key_stroke      = lister__key_stroke__choice_list;
    lister_set_handlers(lister, &handlers);
    
    Lister_Result l_result = run_lister(app, lister);
    Lister_Choice *result = 0;
    if (!l_result.canceled){
        result = (Lister_Choice*)l_result.user_data;
    }
    return(result);
}

function Lister_Choice*
get_choice_from_user(Application_Links *app, char *query, Lister_Choice_List list){
    return(get_choice_from_user(app, SCu8(query), list));
}

// BOTTOM

