import { getDatabase } from "./database.js";
import { serverLogger } from "../utils/serverLogger.js";
import * as fs from "fs";
export class DuplicateDetectionService {
    /**
   * Detect duplicates using multiple strategies
   */ async detectDuplicates() {
        const db = getDatabase();
        const duplicateGroups = [];
        // Strategy 1: Fingerprint-based detection (highest confidence)
        const fingerprintDupes = await this.findFingerprintDuplicates();
        duplicateGroups.push(...fingerprintDupes);
        // Strategy 2: Metadata-based detection (artist + title + duration)
        const metadataDupes = await this.findMetadataDuplicates();
        duplicateGroups.push(...metadataDupes);
        // Strategy 3: Filename similarity detection (lowest confidence)
        const filenameDupes = await this.findFilenameDuplicates();
        duplicateGroups.push(...filenameDupes);
        // Remove overlapping groups (prefer higher confidence)
        const dedupedGroups = this.deduplicateGroups(duplicateGroups);
        // Rank each group and suggest best track to keep
        return dedupedGroups.map((group)=>this.rankAndSuggest(group));
    }
    /**
   * Find duplicates by audio fingerprint
   */ async findFingerprintDuplicates() {
        const db = getDatabase();
        const groups = [];
        try {
            // Find tracks with same fingerprint
            const duplicates = db.db.prepare(`
        SELECT
          t.path, t.title, t.artist, t.album, t.duration,
          t.bitrate, t.sample_rate, t.codec, t.lossless,
          t.acoustid_fingerprint, t.artwork_hash
        FROM tracks t
        INNER JOIN (
          SELECT acoustid_fingerprint
          FROM tracks
          WHERE acoustid_fingerprint IS NOT NULL
          GROUP BY acoustid_fingerprint
          HAVING COUNT(*) > 1
        ) dupes ON t.acoustid_fingerprint = dupes.acoustid_fingerprint
        ORDER BY t.acoustid_fingerprint, t.bitrate DESC, t.lossless DESC
      `).all();
            // Group by fingerprint
            const groupMap = new Map();
            for (const track of duplicates){
                const fingerprint = track.acoustid_fingerprint;
                if (!groupMap.has(fingerprint)) {
                    groupMap.set(fingerprint, []);
                }
                const fileSize = fs.existsSync(track.path) ? fs.statSync(track.path).size : 0;
                groupMap.get(fingerprint).push({
                    path: track.path,
                    title: track.title,
                    artist: track.artist,
                    album: track.album,
                    duration: track.duration,
                    bitrate: track.bitrate,
                    sampleRate: track.sample_rate,
                    codec: track.codec,
                    lossless: track.lossless === 1,
                    acoustid_fingerprint: track.acoustid_fingerprint,
                    artwork_hash: track.artwork_hash,
                    fileSize
                });
            }
            // Create groups
            for (const [fingerprint, tracks] of groupMap){
                if (tracks.length > 1) {
                    groups.push({
                        id: `fp_${fingerprint.substring(0, 8)}`,
                        matchType: "fingerprint",
                        confidence: 100,
                        tracks
                    });
                }
            }
        } catch (error) {
            serverLogger.error("Error finding fingerprint duplicates", {
                error
            });
        }
        return groups;
    }
    /**
   * Find duplicates by metadata (artist + title + similar duration)
   */ async findMetadataDuplicates() {
        const db = getDatabase();
        const groups = [];
        try {
            // Find tracks with same artist + title + duration within 2 seconds
            const duplicates = db.db.prepare(`
        SELECT
          t1.path, t1.title, t1.artist, t1.album, t1.duration,
          t1.bitrate, t1.sample_rate, t1.codec, t1.lossless,
          t1.acoustid_fingerprint, t1.artwork_hash,
          COUNT(*) OVER (PARTITION BY t1.artist, t1.title) as dupe_count
        FROM tracks t1
        WHERE t1.artist IS NOT NULL
          AND t1.title IS NOT NULL
          AND t1.artist != 'Unknown Artist'
          AND EXISTS (
            SELECT 1 FROM tracks t2
            WHERE t2.artist = t1.artist
              AND t2.title = t1.title
              AND t2.path != t1.path
              AND ABS(t2.duration - t1.duration) < 2
          )
        ORDER BY t1.artist, t1.title, t1.bitrate DESC
      `).all();
            // Group by artist + title
            const groupMap = new Map();
            for (const track of duplicates){
                const key = `${track.artist}::${track.title}`.toLowerCase();
                if (!groupMap.has(key)) {
                    groupMap.set(key, []);
                }
                const fileSize = fs.existsSync(track.path) ? fs.statSync(track.path).size : 0;
                groupMap.get(key).push({
                    path: track.path,
                    title: track.title,
                    artist: track.artist,
                    album: track.album,
                    duration: track.duration,
                    bitrate: track.bitrate,
                    sampleRate: track.sample_rate,
                    codec: track.codec,
                    lossless: track.lossless === 1,
                    acoustid_fingerprint: track.acoustid_fingerprint,
                    artwork_hash: track.artwork_hash,
                    fileSize
                });
            }
            // Create groups
            let groupIndex = 0;
            for (const [key, tracks] of groupMap){
                if (tracks.length > 1) {
                    groups.push({
                        id: `meta_${groupIndex++}`,
                        matchType: "metadata",
                        confidence: 85,
                        tracks
                    });
                }
            }
        } catch (error) {
            serverLogger.error("Error finding metadata duplicates", {
                error
            });
        }
        return groups;
    }
    /**
   * Find duplicates by filename similarity
   */ async findFilenameDuplicates() {
        const db = getDatabase();
        const groups = [];
        try {
            // Find tracks with very similar filenames (exact match without path)
            const duplicates = db.db.prepare(`
        SELECT
          t1.path, t1.title, t1.artist, t1.album, t1.duration,
          t1.bitrate, t1.sample_rate, t1.codec, t1.lossless,
          t1.acoustid_fingerprint, t1.artwork_hash
        FROM tracks t1
        WHERE EXISTS (
          SELECT 1 FROM tracks t2
          WHERE t2.path != t1.path
            AND substr(t2.path, instr(t2.path, '/') + 1) = substr(t1.path, instr(t1.path, '/') + 1)
        )
        ORDER BY t1.path
      `).all();
            // Group by filename
            const groupMap = new Map();
            for (const track of duplicates){
                const filename = track.path.split("/").pop() || track.path;
                if (!groupMap.has(filename)) {
                    groupMap.set(filename, []);
                }
                const fileSize = fs.existsSync(track.path) ? fs.statSync(track.path).size : 0;
                groupMap.get(filename).push({
                    path: track.path,
                    title: track.title,
                    artist: track.artist,
                    album: track.album,
                    duration: track.duration,
                    bitrate: track.bitrate,
                    sampleRate: track.sample_rate,
                    codec: track.codec,
                    lossless: track.lossless === 1,
                    acoustid_fingerprint: track.acoustid_fingerprint,
                    artwork_hash: track.artwork_hash,
                    fileSize
                });
            }
            // Create groups
            let groupIndex = 0;
            for (const [filename, tracks] of groupMap){
                if (tracks.length > 1) {
                    groups.push({
                        id: `file_${groupIndex++}`,
                        matchType: "filename",
                        confidence: 60,
                        tracks
                    });
                }
            }
        } catch (error) {
            serverLogger.error("Error finding filename duplicates", {
                error
            });
        }
        return groups;
    }
    /**
   * Remove overlapping groups (same tracks appear in multiple groups)
   */ deduplicateGroups(groups) {
        // Sort by confidence (highest first)
        const sorted = groups.sort((a, b)=>b.confidence - a.confidence);
        const seen = new Set();
        const result = [];
        for (const group of sorted){
            // Check if any track in this group has been seen
            const trackPaths = group.tracks.map((t)=>t.path);
            const hasOverlap = trackPaths.some((p)=>seen.has(p));
            if (!hasOverlap) {
                // Add all tracks to seen set
                trackPaths.forEach((p)=>seen.add(p));
                result.push(group);
            }
        }
        return result;
    }
    /**
   * Rank tracks in a group and suggest which to keep
   */ rankAndSuggest(group) {
        // Ranking criteria (in order of priority):
        // 1. Lossless format > lossy
        // 2. Higher bitrate
        // 3. Higher sample rate
        // 4. Larger file size (usually indicates better quality)
        // 5. Has artwork
        const scored = group.tracks.map((track)=>{
            let score = 0;
            // Lossless bonus
            if (track.lossless) score += 10000;
            // Bitrate score (normalized)
            if (track.bitrate) score += track.bitrate / 1000;
            // Sample rate score (normalized)
            if (track.sampleRate) score += track.sampleRate / 1000;
            // File size score (normalized to MB)
            score += track.fileSize / (1024 * 1024);
            // Artwork bonus
            if (track.artwork_hash) score += 100;
            return {
                track,
                score
            };
        });
        // Sort by score (highest first)
        scored.sort((a, b)=>b.score - a.score);
        const best = scored[0].track;
        const reasons = [];
        if (best.lossless) reasons.push("lossless");
        if (best.bitrate && best.bitrate === Math.max(...group.tracks.map((t)=>t.bitrate || 0))) {
            reasons.push(`highest bitrate (${best.bitrate}kbps)`);
        }
        if (best.artwork_hash) reasons.push("has artwork");
        return {
            ...group,
            suggestedKeep: best.path,
            reason: reasons.join(", ")
        };
    }
    /**
   * Merge duplicates by keeping the suggested track and removing others
   */ async mergeDuplicates(groupId, keepPath) {
        const db = getDatabase();
        const removed = [];
        try {
            // Get all tracks in the group
            const group = (await this.detectDuplicates()).find((g)=>g.id === groupId);
            if (!group) {
                throw new Error(`Duplicate group not found: ${groupId}`);
            }
            // Remove all tracks except the one to keep
            for (const track of group.tracks){
                if (track.path !== keepPath) {
                    // Remove from database
                    db.db.prepare("DELETE FROM tracks WHERE path = ?").run(track.path);
                    // Delete file if it exists
                    if (fs.existsSync(track.path)) {
                        fs.unlinkSync(track.path);
                    }
                    removed.push(track.path);
                    serverLogger.info("Removed duplicate track", {
                        path: track.path,
                        kept: keepPath
                    });
                }
            }
        } catch (error) {
            serverLogger.error("Error merging duplicates", {
                groupId,
                error
            });
            throw error;
        }
        return {
            removed
        };
    }
}
// Export singleton
export const duplicateDetectionService = new DuplicateDetectionService();

//# sourceMappingURL=duplicateDetectionService.js.map