import type { DB } from './db.js' import { isConfidentMatch, normalize } from './matcher.js' export type RipStatus = 'ripped' | 'not_ripped' /** * Resolution order per spec: * 1. manual rip_override (non-null) wins * 2. else a stored match_link means ripped * 3. else confident fuzzy match against the user's digital_albums */ export function resolveRipStatus(db: DB, userId: number, itemId: number): RipStatus { const item = db .prepare('SELECT id, title, artist, rip_override FROM collection_items WHERE id = ? AND user_id = ?') .get(itemId, userId) as | { id: number; title: string; artist: string; rip_override: number | null } | undefined if (!item) return 'not_ripped' if (item.rip_override !== null && item.rip_override !== undefined) { return item.rip_override === 1 ? 'ripped' : 'not_ripped' } const link = db.prepare('SELECT album_id FROM match_links WHERE item_id = ?').get(itemId) if (link) return 'ripped' const albums = db .prepare('SELECT title, artist FROM digital_albums WHERE user_id = ?') .all(userId) as { title: string; artist: string }[] return albums.some((a) => isConfidentMatch(item, a)) ? 'ripped' : 'not_ripped' } /** * Batch resolution for list views: loads albums and match links once per * user instead of per item (GET /api/collection is otherwise O(items x albums)). * Resolution order matches resolveRipStatus. */ export function resolveRipStatusBatch( db: DB, userId: number, items: { id: number; title: string; artist: string; rip_override: number | null }[] ): RipStatus[] { const albums = db .prepare('SELECT title, artist FROM digital_albums WHERE user_id = ?') .all(userId) as { title: string; artist: string }[] const albumKeys = new Set( albums.map((a) => `${normalize(a.title)}|${normalize(a.artist)}`) ) const linked = new Set( ( db.prepare('SELECT item_id FROM match_links WHERE user_id = ?').all(userId) as { item_id: number }[] ).map((r) => r.item_id) ) return items.map((item) => { if (item.rip_override !== null && item.rip_override !== undefined) { return item.rip_override === 1 ? 'ripped' : 'not_ripped' } if (linked.has(item.id)) return 'ripped' const key = `${normalize(item.title)}|${normalize(item.artist)}` return albumKeys.has(key) ? 'ripped' : 'not_ripped' }) }