/**
 * Real-time metadata enrichment for tracks requested by WAVE
 * Enriches metadata on-demand when tracks are accessed
 */ import { getAggressiveMetadata } from "./aggressiveMetadataService.js";
import { getDatabase } from "./database.js";
import { serverLogger } from "../utils/logger.js";
import { getArtworkService } from "./artworkService.js";
import { EventEmitter } from "events";
class RealtimeEnrichmentService extends EventEmitter {
    enrichmentQueue = new Map();
    recentlyEnriched = new Map();
    CACHE_DURATION = 24 * 60 * 60 * 1000;
    MAX_CONCURRENT = 5;
    currentProcessing = 0;
    constructor(){
        super();
        // Clean up old cache entries every hour
        setInterval(()=>this.cleanupCache(), 60 * 60 * 1000);
    }
    /**
   * Check if a track needs enrichment
   */ needsEnrichment(track) {
        // Check if recently enriched
        const lastEnriched = this.recentlyEnriched.get(track.path);
        if (lastEnriched && Date.now() - lastEnriched < this.CACHE_DURATION) {
            return false;
        }
        // Check for missing critical metadata
        const missingFields = [];
        if (!track.artist || track.artist.match(/^(Artist \d+|#\d+|\d\d|Unknown Artist)$/)) {
            missingFields.push("artist");
        }
        if (!track.title || track.title.match(/^(Track \d+|Unknown Title)$/)) {
            missingFields.push("title");
        }
        if (!track.album || track.album.match(/^(Album \d+|Unknown Album)$/)) {
            missingFields.push("album");
        }
        if (!track.year || track.year === 0) {
            missingFields.push("year");
        }
        if (!track.genre || track.genre.length === 0 || track.genre.length === 1 && (track.genre[0] === "Unknown" || track.genre[0] === "Unknown Genre")) {
            missingFields.push("genre");
        }
        // Check for artwork hash
        if (!track.artwork_hash) {
            missingFields.push("artwork");
        }
        return missingFields.length > 0;
    }
    /**
   * Calculate metadata quality score
   */ calculateQualityScore(track) {
        let score = 0;
        const weights = {
            artist: 20,
            title: 20,
            album: 15,
            year: 10,
            genre: 10,
            albumArt: 10,
            trackNumber: 5,
            duration: 5,
            bitrate: 5
        };
        for (const [field, weight] of Object.entries(weights)){
            const value = track[field];
            if (value && value !== "Unknown" && value !== 0) {
                score += weight;
            }
        }
        return score / 100;
    }
    /**
   * Enrich a single track's metadata
   */ async enrichTrack(track, options = {}) {
        try {
            // Check if enrichment is needed
            if (!options.forceRefresh && !this.needsEnrichment(track)) {
                return {
                    enriched: false,
                    fields: [],
                    track
                };
            }
            // Check if already being processed
            const existingProcess = this.enrichmentQueue.get(track.path);
            if (existingProcess) {
                const enrichedTrack = await existingProcess;
                return {
                    enriched: true,
                    fields: [
                        "cached"
                    ],
                    track: enrichedTrack
                };
            }
            // Wait if too many concurrent enrichments
            while(this.currentProcessing >= this.MAX_CONCURRENT){
                await new Promise((resolve)=>setTimeout(resolve, 100));
            }
            // Start enrichment
            this.currentProcessing++;
            // Make a deep copy of the original track for comparison
            const originalTrack = JSON.parse(JSON.stringify(track));
            const enrichmentPromise = this.performEnrichment(track, options);
            this.enrichmentQueue.set(track.path, enrichmentPromise);
            const enrichedTrack = await enrichmentPromise;
            // Mark as recently enriched
            this.recentlyEnriched.set(track.path, Date.now());
            // Clean up
            this.enrichmentQueue.delete(track.path);
            this.currentProcessing--;
            // Determine which fields were enriched by comparing to original
            const enrichedFields = this.getEnrichedFields(originalTrack, enrichedTrack);
            // If we detected a filename pattern and cleaned it, always consider it enriched
            // This ensures the WebSocket broadcast happens
            const wasFilenameParsed = originalTrack.title && originalTrack.title.match(/^.+?\s*-\s*.+?\s*-\s*\d+\s*-\s*.+$/) || enrichedTrack.title !== originalTrack.title && options.forceRefresh;
            // Emit event if track was enriched OR if filename was parsed
            if (enrichedFields.length > 0 || wasFilenameParsed) {
                this.emit("track-enriched", {
                    trackPath: track.path,
                    enriched: true,
                    fields: enrichedFields,
                    track: enrichedTrack
                });
                serverLogger.info("RealtimeEnrichmentService emitted track-enriched event", {
                    path: track.path,
                    fields: enrichedFields
                });
            }
            return {
                enriched: enrichedFields.length > 0 || wasFilenameParsed,
                fields: enrichedFields.length > 0 ? enrichedFields : wasFilenameParsed ? [
                    "title"
                ] : [],
                track: enrichedTrack
            };
        } catch (error) {
            serverLogger.error("Real-time enrichment failed", {
                path: track.path,
                error: error instanceof Error ? error.message : error
            });
            this.currentProcessing--;
            this.enrichmentQueue.delete(track.path);
            return {
                enriched: false,
                fields: [],
                track
            };
        }
    }
    /**
   * Perform the actual enrichment
   */ async performEnrichment(track, options) {
        const aggressiveMetadata = getAggressiveMetadata();
        const db = await getDatabase();
        const artworkService = getArtworkService();
        const path = await import("path");
        try {
            // First, if track has NULL metadata OR title looks like a filename, try to parse
            const basename = path.default.basename(track.path, path.default.extname(track.path));
            const needsParsing = !track.title || !track.artist || !track.album || track.title && track.title === basename || track.title && track.title.match(/^.+?\s*-\s*.+?\s*-\s*\d+\s*-\s*.+$/);
            if (needsParsing) {
                // Try common filename patterns
                // Pattern: Artist - Album - TrackNumber - Title
                const pattern1 = /^(.+?)\s*-\s*(.+?)\s*-\s*(\d+)\s*-\s*(.+)$/;
                // Check if title contains the pattern (was incorrectly stored as title)
                const titleToCheck = track.title || basename;
                const match1 = titleToCheck.match(pattern1);
                if (match1) {
                    track.artist = match1[1].trim();
                    track.album = track.album || match1[2].trim();
                    track.track_no = track.track_no || parseInt(match1[3]);
                    track.title = match1[4].trim(); // Always use the parsed title
                    serverLogger.info("Parsed/cleaned metadata from filename pattern", {
                        path: track.path,
                        originalTitle: titleToCheck,
                        artist: track.artist,
                        album: track.album,
                        trackNo: track.track_no,
                        title: track.title
                    });
                } else {
                    // Pattern: Artist - Title
                    const pattern2 = /^(.+?)\s*-\s*(.+)$/;
                    const match2 = titleToCheck.match(pattern2);
                    if (match2 && !track.title) {
                        track.artist = track.artist || match2[1].trim();
                        track.title = match2[2].trim();
                    }
                }
            }
            // Use aggressive metadata service for enrichment
            const enrichedMetadata = await aggressiveMetadata.scanAndFix(track.path, {
                force: options.forceRefresh || false,
                deepScan: true,
                autoFix: true
            });
            // Build updated track object - preserve parsed metadata over file metadata
            const updatedTrack = {
                ...track,
                // If we parsed the title from filename, keep it. Otherwise use enriched metadata
                title: needsParsing && track.title ? track.title : enrichedMetadata.title || track.title,
                artist: needsParsing && track.artist ? track.artist : enrichedMetadata.artist || track.artist,
                album: needsParsing && track.album ? track.album : enrichedMetadata.album || track.album,
                year: enrichedMetadata.year || track.year,
                genre: enrichedMetadata.genre ? Array.isArray(enrichedMetadata.genre) ? enrichedMetadata.genre : [
                    enrichedMetadata.genre
                ] : track.genre,
                track_no: enrichedMetadata.trackNumber || track.track_no
            };
            // Add additional metadata if available
            if (enrichedMetadata.albumArtist) {
                updatedTrack.albumartist = enrichedMetadata.albumArtist;
            }
            if (enrichedMetadata.musicBrainzId) {
                updatedTrack.musicbrainz_id = enrichedMetadata.musicBrainzId;
            }
            // Handle album artwork
            if (!track.artwork_hash || options.forceRefresh) {
                // First try to extract embedded artwork, with fallback to folder artwork (cover.jpg, folder.jpg)
                const artworkResult = await artworkService.extractArtworkWithFallback(track.path);
                let artworkHash = artworkResult.hash;
                // If no embedded artwork and we have MusicBrainz ID, fetch from Cover Art Archive
                if (!artworkHash && enrichedMetadata.musicBrainzAlbumId) {
                    serverLogger.info("Fetching artwork from Cover Art Archive", {
                        artist: updatedTrack.artist,
                        album: updatedTrack.album,
                        releaseId: enrichedMetadata.musicBrainzAlbumId
                    });
                    artworkHash = await artworkService.fetchAlbumArtwork(enrichedMetadata.musicBrainzAlbumId);
                    if (artworkHash) {
                        serverLogger.info("Artwork fetched successfully", {
                            artist: updatedTrack.artist,
                            album: updatedTrack.album,
                            hash: artworkHash
                        });
                    }
                }
                if (artworkHash) {
                    updatedTrack.artwork_hash = artworkHash;
                }
            }
            // Save to database
            db.upsertTrack(updatedTrack);
            serverLogger.info("Track enriched in real-time", {
                path: track.path,
                artist: updatedTrack.artist,
                title: updatedTrack.title,
                artwork: updatedTrack.artwork_hash ? "found" : "missing",
                qualityScore: this.calculateQualityScore(updatedTrack)
            });
            return updatedTrack;
        } catch (error) {
            serverLogger.error("Enrichment processing failed", {
                path: track.path,
                error: error instanceof Error ? error.message : error
            });
            return track;
        }
    }
    /**
   * Get list of fields that were enriched
   */ getEnrichedFields(original, enriched) {
        const fields = [];
        if (original.artist !== enriched.artist) fields.push("artist");
        if (original.title !== enriched.title) fields.push("title");
        if (original.album !== enriched.album) fields.push("album");
        if (original.year !== enriched.year) fields.push("year");
        if (JSON.stringify(original.genre) !== JSON.stringify(enriched.genre)) fields.push("genre");
        if (original.artwork_hash !== enriched.artwork_hash) fields.push("artwork");
        if (original.track_no !== enriched.track_no) fields.push("track_no");
        return fields;
    }
    /**
   * Batch enrich multiple tracks
   */ async enrichTracks(tracks, options = {}) {
        // Process in batches to avoid overwhelming the system
        const batchSize = 10;
        const enrichedTracks = [];
        for(let i = 0; i < tracks.length; i += batchSize){
            const batch = tracks.slice(i, i + batchSize);
            const promises = batch.map((track)=>this.enrichTrack(track, options).then((result)=>result.track).catch(()=>track));
            const enrichedBatch = await Promise.all(promises);
            enrichedTracks.push(...enrichedBatch);
        }
        return enrichedTracks;
    }
    /**
   * Clean up old cache entries
   */ cleanupCache() {
        const now = Date.now();
        const expired = [];
        for (const [path, timestamp] of this.recentlyEnriched.entries()){
            if (now - timestamp > this.CACHE_DURATION) {
                expired.push(path);
            }
        }
        for (const path of expired){
            this.recentlyEnriched.delete(path);
        }
        if (expired.length > 0) {
            serverLogger.debug("Cleaned up enrichment cache", {
                count: expired.length
            });
        }
    }
    /**
   * Get enrichment statistics
   */ getStatistics() {
        return {
            queueSize: this.enrichmentQueue.size,
            currentProcessing: this.currentProcessing,
            cacheSize: this.recentlyEnriched.size,
            maxConcurrent: this.MAX_CONCURRENT
        };
    }
}
// Singleton instance
let enrichmentService = null;
export function getRealtimeEnrichment() {
    if (!enrichmentService) {
        enrichmentService = new RealtimeEnrichmentService();
        serverLogger.info("Real-time enrichment service initialized");
    }
    return enrichmentService;
}

//# sourceMappingURL=realtimeEnrichment.js.map