# BookShelf Module Overview

The `BookShelf` module is the bridge between the data model (`BookList`) and the
visual grid of book tiles. It packages a full GTK widget tree that can be
embedded into any window: the widget takes care of subscribing to model
updates, populating the `Gtk::GridView`, handling selection state, and exposing
activation + filtering hooks back to the caller.

## Responsibilities

- Mirror the in-memory book collection into a `Gio::ListStore` suitable for a
  `Gtk::GridView`.
- Build and bind `BookTile` widgets on demand using a `Gtk::SignalListItemFactory`.
- Provide user interaction glue:
  - Emits `signalBookActivated()` when a tile is clicked.
  - Tracks selection state via `Gtk::SingleSelection`.
  - Clears selection when the user clicks outside the grid or moves the mouse
    into empty space.
- Implement a lightweight fuzzy filter so the grid only shows matches for the
  active query.

## Structure

```
BookShelf
 ├── Gio::ListStore<BookObject>   // backing data model
 ├── Gtk::SingleSelection         // selection controller passed to the grid
 ├── Gtk::GridView                // renders BookTile cells
 ├── Gtk::SignalListItemFactory   // setup / bind / unbind callbacks
 ├── BookObject                   // small wrapper storing Book for the model
 └── Filtering helpers            // scoring & refresh logic
```

### BookObject wrapper

`Gio::ListStore` expects items that derive from `Glib::Object`. `BookObject`
wraps a `Book` so we can put books inside the GTK list store while still
using value semantics when refreshing the grid.

## Data flow

1. Construction:
   - `BookShelf` creates the list store, selection model, signal factory, and
     sets up the GridView.
   - Background gestures/controllers are added so clicking empty space clears
     the selection.

2. Synchronisation:
   - `reload()`, `addTile()`, `updateTile()`, and `removeTile()` all forward to
     `refreshVisible()`, which rebuilds the list store from `BookList` (using
     the current filter, if any).
   - `BookList` signals are connected to these entry points in the constructor
     so UI stays in sync with database changes.

3. Rendering:
   - `onFactorySetup()` creates a `BookTile` and stores it in each `Gtk::ListItem`.
   - `onFactoryBind()` pulls the corresponding `BookObject` from the store and
     feeds it into the tile via `BookTile::setBook()`.
   - `onFactoryUnbind()` resets the tile to a blank Book so stale metadata isn’t
     shown when the item is recycled.

4. Interaction:
   - When the grid emits `signal_activate(position)`, the selection model is
     updated and `signalBookActivated()` is forwarded to the outside world.
   - Background gestures reset the selection so keyboard focus isn’t stuck.

## Filtering algorithm

`setFilter(query)` stores a lowercase copy of the query and calls
`refreshVisible()`. The refresh routine:

1. Clears the current selection and list store.
2. If the query is empty, re-populates the grid with every book.
3. Otherwise evaluates a fuzzy score for each book using:
   - substring match (higher score for earlier matches), and
   - subsequence match (reward tightly clustered characters).
4. Keeps only books with a non-negative score, sorts descending by score, and
   repopulates the store so the grid shows the ranked results.

The helper functions `ascii_lower`, `substring_score`, `subsequence_score`, and
`book_score` live inside the module to keep the fuzzy logic confined.

## Extension points

- **Custom tile content**: tweak `BookTile` (e.g., add badges or context menus);
  the factory setup already exposes the widget instance.
- **Filtering strategy**: adjust scoring or add new fields (tags, notes) inside
  `book_score()`.
- **Selection behaviour**: change the background event controllers if you want
  multi-select or persistent selection.

## Expected usage

The module is designed to be embedded like so:

```cpp
m_shelf = std::make_unique<BookShelf>(m_bookList, 180);
m_shelf->signalBookActivated().connect(...);
container.append(*m_shelf);
```

Call `setFilter(query)` from your search UI, and `reload()` if you ever perform
bulk operations that bypass the standard BookList signals.
