# BookList Module Overview

`BookList` is the in-memory model of the library. It mirrors the contents of
SQLite (via `DatabaseManager`), exposes thread-safe APIs to mutate the list, and
emits granular signals so UI layers (e.g. `BookShelf`) can stay synchronized
without polling.

## Responsibilities

- Load the full set of books from the database into a `std::vector<Book>`.
- Provide CRUD helpers (`upsert`, `removeById`, `upsertMany`) that keep both the
  vector and the database in sync.
- Broadcast change notifications (`signalBookAdded`, `signalBookUpdated`, etc.).
- Offer `_enqueue` variants that marshal work onto the GTK main loop so helper
  threads can safely update the model.

## Structure

```
BookList
 ├── DatabaseManager& m_database   // owned externally
 ├── std::vector<Book> m_books     // authoritative in-memory copy
 ├── std::mutex m_mutex            // guards access to m_books
 └── Signals (sigc::signal<...>)   // fine-grained notifications
```

## Data flow

1. **Initial load**: `loadAll()` grabs a copy of all rows via the database
   manager, swaps them into `m_books`, and emits `signalReset()` followed by
   `signalBulkChanged()` so views can rebuild.
2. **Insert/Update**: `upsert()` writes the book to SQLite, then updates (or
   appends) the matching entry in `m_books`. Depending on whether the book was
   new, it emits `signalBookAdded()` or `signalBookUpdated()`.
3. **Removal**: `removeById()` deletes the record from the database; if the book
   existed in `m_books` it also erases it and emits `signalBookRemoved()`.
4. **Bulk updates**: `upsertMany()` writes each book to the database, swaps the
   vector entries, and finally emits `signalBulkChanged()` (useful after a batch
   import).

## Thread-safety helpers

- `_enqueue` variants (`upsertEnqueue`, `removeByIdEnqueue`, `loadAllEnqueue`)
  capture the arguments and schedule the corresponding method via
  `Glib::signal_idle().connect_once`. This allows worker threads (e.g. import
  jobs) to request updates without touching `Gtk` APIs off the main thread.
- Internally, `std::mutex m_mutex` protects the vector so multiple calls don’t
  race even if they come from different threads.

## Signals

| Signal                | Emitted when …                                |
|-----------------------|-----------------------------------------------|
| `signalBookAdded`     | A new book (id not previously seen) is added. |
| `signalBookUpdated`   | An existing book’s metadata changes.          |
| `signalBookRemoved`   | A book is erased by id.                       |
| `signalReset`         | The vector is cleared / reloaded wholesale.   |
| `signalBulkChanged`   | Large batch operations mutate the list.       |

Consumers (e.g. `BookShelf`) typically connect to these to update UI widgets.

## Extension points

- **Metadata fields**: When new columns are added to `Book`, extend the CRUD
  methods and signal payloads accordingly.
- **Sorting / ordering**: `m_books` preserves insertion order; consumers are free
  to sort if needed (e.g. `BookShelf`’s fuzzy filter reorders matches).
- **Persistence layer**: swapping the backend (e.g. to a REST API) would mean
  replacing `DatabaseManager` calls but the public `BookList` API could remain
  stable.

## Expected usage

Typical lifecycle inside the app:

```cpp
BookList m_bookList(database);
m_bookList.loadAll();
m_bookList.signalBookAdded().connect(...);
```

Worker thread example:

```cpp
std::thread([&list, book] {
  list.upsertEnqueue(book);  // safe from any thread
}).detach();
```

UI code listens to the signals to stay synchronized, while background import
jobs call the `_enqueue` helpers to avoid GTK threading violations.
