/**
 * Per-table allowlists of columns the HTTP `PUT /artists|albums|tracks/:id`
 * endpoints may write.
 *
 * These endpoints pass `request.body as any` straight through, and the update
 * builders interpolate `Object.keys(body)` into the SET clause as quoted
 * identifiers — so without an allowlist a crafted key both closes the quoted
 * identifier (SQL injection) and mass-assigns columns that are not user-editable.
 *
 * Deliberately excluded:
 *  - `tracks.path` / `tracks.hash` / `tracks.mtime` — owned by the scanner; the
 *    path is the only link to the file on the read-only `/music` bind.
 *  - `tracks.state` / `quarantined_at` / `deleted_at` — owned by the dislike
 *    lifecycle and the integrity sweep, not by metadata editing.
 *  - `play_count` / `skip_count` / `dislike_count` / `last_played_at` — learning
 *    signal; forgeable counters would poison the recommendation engine.
 *  - `source_type` — decides library vs. recommendation semantics.
 *  - `id` / `created_at` / `updated_at` and every generated column
 *    (`normalized_name`, `normalized_artist` — Postgres rejects writes anyway).
 */
export const UPDATABLE_COLUMNS = {
  artists: ['name', 'canonical_name', 'sort_name', 'mbid', 'discogs_id', 'image_path'],
  albums: ['artist_id', 'title', 'year', 'release_date', 'artwork_id', 'mbid'],
  tracks: ['title', 'artist', 'album_id', 'duration', 'release_date'],
} as const;

/**
 * Filter a partial update payload down to the allowlisted, defined columns for
 * `table`. Unknown keys are dropped silently rather than raising: the callers
 * take `Partial<T>` and the routes do no error mapping, so a 500 on an extra
 * key would be worse than a no-op. The subsequent "No fields to update" throw
 * still surfaces a payload that was *entirely* rejected.
 */
export function allowedFields<T extends object>(
  table: keyof typeof UPDATABLE_COLUMNS,
  data: T
): (keyof T & string)[] {
  const allowed = UPDATABLE_COLUMNS[table] as readonly string[];
  return Object.keys(data).filter(
    (k) => allowed.includes(k) && (data as any)[k] !== undefined
  ) as (keyof T & string)[];
}
