/**
 * Generic entity write-through (docs/architecture/01 §5).
 *
 * Every per-record store (snippets, datasets, custom themes, user fonts) persists
 * the same way: watch its entity array, and on each change diff it against the
 * previous snapshot by id reference — save what's new or changed, delete what's
 * gone. Because store actions produce fresh object references for what they touch,
 * a reference diff catches exactly the changed records (spec §09E one-record-per-
 * entity tiers; a whole-store serializer like Zustand's `persist` middleware would
 * rewrite every record on any change, so it doesn't fit).
 *
 * The per-entity `wire*Persistence` modules supply the store, the array selector,
 * and the IndexedDB adapter + error toast; this owns the diff loop they shared.
 */

type Unsubscribe = () => void;

/** Minimal store surface this needs — satisfied by any Zustand store. */
interface StoreLike<S> {
  getState: () => S;
  subscribe: (listener: (state: S) => void) => Unsubscribe;
}

export interface EntityWriteThrough<T extends { id: string | number }> {
  /** Persist a new or changed record. */
  save: (entity: T) => Promise<void>;
  /** Remove a record that's no longer in the array. Id type follows the entity's. */
  remove: (id: T['id']) => Promise<void>;
  /**
   * Surface a failed write — these promises are otherwise fire-and-forget, so a
   * rejection becomes a toast rather than silent data loss (spec §10 → "told when
   * a save fails rather than losing data silently").
   */
  onError: (op: 'save' | 'delete', err: unknown) => void;
}

/**
 * Persist upserts and deletions whenever a store's entity array changes. Returns
 * a teardown that detaches the subscriber. Wire it AFTER the store is hydrated so
 * the baseline is the loaded set — otherwise the first diff re-saves everything.
 */
export function wireEntityWriteThrough<S, T extends { id: string | number }>(
  store: StoreLike<S>,
  select: (state: S) => readonly T[],
  io: EntityWriteThrough<T>,
): Unsubscribe {
  let prev = select(store.getState());
  return store.subscribe((state) => {
    const next = select(state);
    if (next === prev) return;
    const before = prev;
    prev = next;

    for (const old of before) {
      if (!next.some((n) => n.id === old.id)) {
        io.remove(old.id).catch((err) => io.onError('delete', err));
      }
    }
    for (const n of next) {
      const old = before.find((p) => p.id === n.id);
      if (old !== n) io.save(n).catch((err) => io.onError('save', err)); // new or changed ref
    }
  });
}
