/**
 * Bulk Artwork Scanner
 *
 * Scans tracks in the database and updates artwork_hash for tracks with no
 * existing hash. Uses the embedded → folder fallback chain via
 * `artworkService.extractArtworkWithFallback`. Tracks are grouped by parent
 * folder; one extraction per folder, hash applied to all tracks in that
 * folder.
 *
 * Why folder-grouped instead of per-track:
 *   - For albums with folder cover.jpg, the per-folder result is correct for
 *     all tracks (they share the cover).
 *   - For albums where tracks have consistent embedded artwork (the common
 *     case for ripped CDs and downloaded albums), extracting from any one
 *     track produces a hash that's content-identical for all tracks. Sharing
 *     the same hash is correct.
 *   - For the rare case of tracks-with-different-embedded-artwork in the
 *     same folder, the sample track's hash is used as the album-level
 *     fallback. This is the same trade-off the legacy folder-only scanner
 *     made and is acceptable for a backfill (per-track precision can be a
 *     follow-up pass).
 *   - 10× faster than per-track for libraries with O(20) tracks/folder.
 */ import { getDatabase } from "./database.js";
import { getArtworkService } from "./artworkService.js";
import { serverLogger } from "../utils/serverLogger.js";
export class BulkArtworkScanner {
    isRunning = false;
    stats = {
        totalTracks: 0,
        tracksWithoutArtwork: 0,
        artworkFound: 0,
        artworkCached: 0,
        errors: 0,
        startTime: 0
    };
    constructor(){}
    /**
   * Scan all tracks in the database for folder artwork
   */ async scanAllTracks(onProgress) {
        if (this.isRunning) {
            throw new Error("Bulk artwork scan is already running");
        }
        this.isRunning = true;
        this.stats = {
            totalTracks: 0,
            tracksWithoutArtwork: 0,
            artworkFound: 0,
            artworkCached: 0,
            errors: 0,
            startTime: Date.now()
        };
        try {
            const db = getDatabase();
            const artworkService = getArtworkService();
            // Get all tracks that don't have artwork_hash
            const tracksWithoutArtwork = db.db.prepare(`
        SELECT path, artist, album
        FROM tracks
        WHERE artwork_hash IS NULL OR artwork_hash = ''
        ORDER BY album, artist, path
      `).all();
            this.stats.totalTracks = tracksWithoutArtwork.length;
            this.stats.tracksWithoutArtwork = tracksWithoutArtwork.length;
            serverLogger.info(`[BulkArtworkScanner] Starting scan of ${this.stats.totalTracks} tracks without artwork`);
            // Group tracks by folder to avoid scanning the same folder multiple times
            const folderGroups = new Map();
            for (const track of tracksWithoutArtwork){
                const folderPath = track.path.substring(0, track.path.lastIndexOf('/'));
                if (!folderGroups.has(folderPath)) {
                    folderGroups.set(folderPath, []);
                }
                folderGroups.get(folderPath).push(track);
            }
            serverLogger.info(`[BulkArtworkScanner] Processing ${folderGroups.size} unique folders`);
            let processed = 0;
            // Process each folder group
            for (const [folderPath, tracks] of folderGroups){
                try {
                    // Use the first track in the folder as the sample for extraction.
                    // extractArtworkWithFallback runs embedded → folder, so a track
                    // whose file has embedded art will return that hash; if the file
                    // has no embedded art, it falls back to scanning the parent
                    // folder for cover.jpg / folder.jpg / etc. — which is the
                    // behavior the legacy scanFolderArtwork-only path provided.
                    const sampleTrack = tracks[0];
                    // extractArtworkWithFallback returns { hash, isEmbedded } — destructure
                    // like scanAlbum does. Passing the whole object made `if (artworkHash)`
                    // always-truthy AND fed an object to better-sqlite3 ("Too few parameter
                    // values"), so bulk scans cached artwork but never wrote a single hash.
                    const { hash: artworkHash } = await artworkService.extractArtworkWithFallback(sampleTrack.path);
                    if (artworkHash) {
                        this.stats.artworkFound++;
                        this.stats.artworkCached++;
                        // Update all tracks in this folder with the artwork hash
                        const updateStmt = db.db.prepare(`
              UPDATE tracks
              SET artwork_hash = ?
              WHERE path = ?
            `);
                        for (const track of tracks){
                            try {
                                updateStmt.run(artworkHash, track.path);
                                serverLogger.debug(`[BulkArtworkScanner] Updated artwork for: ${track.path}`);
                            } catch (error) {
                                serverLogger.error(`[BulkArtworkScanner] Failed to update track ${track.path}:`, error);
                                this.stats.errors++;
                            }
                        }
                        serverLogger.info(`[BulkArtworkScanner] Found artwork for ${tracks.length} tracks in folder: ${folderPath}`);
                    } else {
                        serverLogger.debug(`[BulkArtworkScanner] No artwork found in folder: ${folderPath}`);
                    }
                } catch (error) {
                    serverLogger.error(`[BulkArtworkScanner] Error processing folder ${folderPath}:`, error);
                    this.stats.errors++;
                }
                processed += tracks.length;
                // Report progress
                if (onProgress) {
                    onProgress(processed, this.stats.totalTracks, {
                        ...this.stats
                    });
                }
                // Small delay to prevent overwhelming the system
                if (processed % 100 === 0) {
                    await new Promise((resolve)=>setTimeout(resolve, 10));
                }
            }
            this.stats.endTime = Date.now();
            const durationMs = this.stats.endTime - this.stats.startTime;
            const durationSeconds = Math.round(durationMs / 1000);
            serverLogger.info(`[BulkArtworkScanner] Scan completed in ${durationSeconds}s`, {
                totalTracks: this.stats.totalTracks,
                artworkFound: this.stats.artworkFound,
                artworkCached: this.stats.artworkCached,
                errors: this.stats.errors
            });
            return {
                ...this.stats
            };
        } finally{
            this.isRunning = false;
        }
    }
    /**
   * Scan tracks for a specific album/artist combination
   */ async scanAlbum(artist, album) {
        const db = getDatabase();
        const artworkService = getArtworkService();
        // Get tracks for this album that don't have artwork
        const tracks = db.db.prepare(`
      SELECT path
      FROM tracks
      WHERE artist = ? AND album = ? AND (artwork_hash IS NULL OR artwork_hash = '')
      LIMIT 1
    `).all(artist, album);
        if (tracks.length === 0) {
            return {
                found: false,
                tracksUpdated: 0
            };
        }
        // Scan for artwork using the first track. extractArtworkWithFallback
        // tries embedded then folder — same chain the bulk scanner uses, so
        // the per-album endpoint is consistent with the bulk path.
        const { hash: artworkHash } = await artworkService.extractArtworkWithFallback(tracks[0].path);
        if (artworkHash) {
            // Update all tracks for this album
            const result = db.db.prepare(`
        UPDATE tracks
        SET artwork_hash = ?
        WHERE artist = ? AND album = ? AND (artwork_hash IS NULL OR artwork_hash = '')
      `).run(artworkHash, artist, album);
            serverLogger.info(`[BulkArtworkScanner] Updated ${result.changes} tracks for album: ${artist} - ${album}`);
            return {
                found: true,
                hash: artworkHash,
                tracksUpdated: result.changes
            };
        }
        return {
            found: false,
            tracksUpdated: 0
        };
    }
    /**
   * Get current scan statistics
   */ getStats() {
        return {
            ...this.stats,
            isRunning: this.isRunning
        };
    }
    /**
   * Check if a scan is currently running
   */ isScanning() {
        return this.isRunning;
    }
}
// Export singleton instance
let bulkArtworkScanner = null;
export function getBulkArtworkScanner() {
    if (!bulkArtworkScanner) {
        bulkArtworkScanner = new BulkArtworkScanner();
    }
    return bulkArtworkScanner;
}

//# sourceMappingURL=bulkArtworkScanner.js.map