//! AdvancedTable - Ordenación y Búsqueda //! //! Funciones de ordenación y búsqueda extraídas del archivo principal. const std = @import("std"); const types = @import("types.zig"); const state = @import("state.zig"); pub const Row = types.Row; pub const SortDirection = types.SortDirection; pub const AdvancedTableState = state.AdvancedTableState; // ============================================================================= // Sorting // ============================================================================= /// Sort rows by column value pub fn sortRows( table_state: *AdvancedTableState, column_name: []const u8, direction: SortDirection, ) void { if (direction == .none) return; if (table_state.rows.items.len < 2) return; const len = table_state.rows.items.len; var swapped = true; while (swapped) { swapped = false; for (0..len - 1) |i| { const val_a = table_state.rows.items[i].get(column_name); const val_b = table_state.rows.items[i + 1].get(column_name); const cmp = val_a.compare(val_b); const should_swap = switch (direction) { .ascending => cmp > 0, .descending => cmp < 0, .none => false, }; if (should_swap) { std.mem.swap(Row, &table_state.rows.items[i], &table_state.rows.items[i + 1]); swapRowStates(table_state, i, i + 1); swapped = true; } } } } /// Swap state map entries between two row indices pub fn swapRowStates(table_state: *AdvancedTableState, idx_a: usize, idx_b: usize) void { const swapInMap = struct { fn swap(map: anytype, a: usize, b: usize) void { const val_a = map.get(a); const val_b = map.get(b); if (val_a != null and val_b != null) { // Both exist - no change needed } else if (val_a) |v| { _ = map.remove(a); map.put(b, v) catch {}; } else if (val_b) |v| { _ = map.remove(b); map.put(a, v) catch {}; } } }.swap; swapInMap(&table_state.dirty_rows, idx_a, idx_b); swapInMap(&table_state.new_rows, idx_a, idx_b); swapInMap(&table_state.deleted_rows, idx_a, idx_b); swapInMap(&table_state.validation_errors, idx_a, idx_b); } // ============================================================================= // Search Helpers // ============================================================================= /// Case-insensitive prefix match for incremental search pub fn startsWithIgnoreCase(haystack: []const u8, needle: []const u8) bool { if (needle.len > haystack.len) return false; if (needle.len == 0) return true; for (needle, 0..) |needle_char, i| { const haystack_char = haystack[i]; const needle_lower = if (needle_char >= 'A' and needle_char <= 'Z') needle_char + 32 else needle_char; const haystack_lower = if (haystack_char >= 'A' and haystack_char <= 'Z') haystack_char + 32 else haystack_char; if (needle_lower != haystack_lower) return false; } return true; } // ============================================================================= // Tests // ============================================================================= test "startsWithIgnoreCase" { try std.testing.expect(startsWithIgnoreCase("Hello World", "Hello")); try std.testing.expect(startsWithIgnoreCase("Hello World", "hello")); try std.testing.expect(startsWithIgnoreCase("hello world", "HELLO")); try std.testing.expect(startsWithIgnoreCase("anything", "")); try std.testing.expect(!startsWithIgnoreCase("Hello", "World")); try std.testing.expect(!startsWithIgnoreCase("Hi", "Hello World")); }