// filepath: /workspaces/monochrome/js/taglib.worker.ts
declare var self: DedicatedWorkerGlobalScope;

import { TagLib, type PictureType } from 'taglib-wasm';
import { doTimed, doTimedAsync } from './doTimed';
import type {
    AddMetadataMessage,
    GetMetadataMessage,
    TagLibFileResponse,
    TagLibMetadata,
    TagLibMetadataResponse,
    TagLibReadMetadata,
    TagLibWorkerMessage,
    TagLibWorkerResponse,
} from './taglib.types';

const PICTURE_TYPE_VALUES = {
    FrontCover: 3,
};

async function addMetadataToAudio(message: AddMetadataMessage): Promise<Uint8Array> {
    const {
        wasmUrl,
        audioData,
        title,
        artist,
        albumTitle,
        albumArtist,
        trackNumber,
        totalTracks,
        discNumber,
        totalDiscs,
        bpm,
        replayGain,
        cover,
        releaseDate,
        copyright,
        isrc,
        explicit,
        lyrics,
    } = message;

    const file = await doTimedAsync('Open file with taglib', async () => {
        const tagLib = await TagLib.initialize({
            wasmUrl: wasmUrl,
        });
        return await tagLib.open(audioData);
    });

    try {
        doTimed('Tagging file', () => {
            const isMp4 = file.isMP4();
            const media = file.audioProperties();
            const needsCombinedTrackDisc = isMp4 || media.containerFormat.toLowerCase() === 'mp3';

            if (title) {
                file.setProperty('TITLE', title);
            }

            if (artist) {
                file.setProperty('ARTIST', artist);
            }

            if (albumTitle) {
                file.setProperty('ALBUM', albumTitle);
            }

            const _albumArtist = albumArtist || artist;
            if (_albumArtist) {
                file.setProperty('ALBUMARTIST', _albumArtist);
            }

            if (trackNumber) {
                let trackString = String(trackNumber);

                if (needsCombinedTrackDisc && trackNumber && totalTracks) {
                    trackString = `${trackNumber}/${totalTracks}`;
                }

                if (needsCombinedTrackDisc) {
                    file.setProperty('TRACKNUMBER', trackString);
                } else {
                    file.setProperty('TRACKNUMBER', String(trackNumber));
                }
            }

            if (!needsCombinedTrackDisc && totalTracks) {
                file.setProperty('TRACKTOTAL', String(totalTracks));
            }

            if (discNumber) {
                let discString = String(discNumber);

                if (needsCombinedTrackDisc && discNumber && totalDiscs) {
                    discString = `${discNumber}/${totalDiscs}`;
                }

                if (needsCombinedTrackDisc) {
                    file.setProperty('DISCNUMBER', discString);
                } else {
                    file.setProperty('DISCNUMBER', String(discNumber));
                }
            }

            if (!needsCombinedTrackDisc && totalDiscs) {
                file.setProperty('DISCTOTAL', String(totalDiscs));
            }

            if (bpm != null && Number.isFinite(bpm)) {
                file.setProperty('BPM', String(Math.round(bpm)));
            }

            if (replayGain) {
                const { albumReplayGain, albumPeakAmplitude, trackReplayGain, trackPeakAmplitude } = replayGain;
                if (albumReplayGain) file.setProperty('REPLAYGAIN_ALBUM_GAIN', String(albumReplayGain));
                if (albumPeakAmplitude) file.setProperty('REPLAYGAIN_ALBUM_PEAK', String(albumPeakAmplitude));
                if (trackReplayGain) file.setProperty('REPLAYGAIN_TRACK_GAIN', String(trackReplayGain));
                if (trackPeakAmplitude) file.setProperty('REPLAYGAIN_TRACK_PEAK', String(trackPeakAmplitude));
            }

            if (releaseDate) {
                try {
                    const year = Number(releaseDate.split('-')[0]);
                    if (!isNaN(year)) {
                        file.setProperty('DATE', String(year));
                    }
                } catch {
                    // Invalid date, skip
                }
            }

            if (copyright) {
                file.setProperty('COPYRIGHT', copyright);
            }

            if (isrc) {
                file.setProperty('ISRC', isrc);

                if (isMp4) {
                    file.setMP4Item('xid ', `:isrc:${isrc}`);
                }
            }

            if (explicit) {
                if (isMp4) {
                    file.setMP4Item('rtng', '1');
                } else {
                    file.setProperty('ITUNESADVISORY', '1');
                }
            }

            if (lyrics) {
                file.setProperty('LYRICS', lyrics.replace(/\r/g, '').replace(/\n/g, '\r\n'));
            }

            if (cover) {
                file.setPictures([
                    {
                        mimeType: cover.type,
                        data: cover.data,
                        type: 'FrontCover',
                        description: 'Cover Art',
                    },
                ]);
            }
        });

        await doTimedAsync('Saving in-memory buffer', () => file.save());

        return file.getFileBuffer();
    } catch (err) {
        console.error(err);
    } finally {
        file.dispose();
    }

    return audioData;
}

async function getMetadataFromAudio(message: GetMetadataMessage): Promise<TagLibReadMetadata> {
    const { wasmUrl, audioData } = message;
    const data: TagLibReadMetadata = {
        duration: 0,
    };

    const file = await doTimedAsync('Open file with taglib', async () => {
        const tagLib = await TagLib.initialize({
            wasmUrl: wasmUrl,
        });
        return await tagLib.open(audioData);
    });

    try {
        const pictures = file.getPictures();
        const isMp4 = file.isMP4();
        const media = file.audioProperties();

        data.duration = media.duration;

        data.title = file.getProperty('TITLE') || undefined;
        data.artist = file.getProperty('ARTIST') || undefined;
        data.albumTitle = file.getProperty('ALBUM') || undefined;
        data.albumArtist = file.getProperty('ALBUMARTIST') || undefined;
        const [trackNumber, trackTotal] = file
            .getProperty('TRACKNUMBER')
            ?.split('/')
            .map((t) => Number(t.trim() || 0) || undefined);
        data.trackNumber = trackNumber || undefined;
        data.totalTracks = trackTotal ? trackTotal : Number(file.getProperty('TRACKTOTAL') || 0) || undefined;

        const [discNumber, discTotal] = file
            .getProperty('DISCNUMBER')
            ?.split('/')
            .map((t) => Number(t.trim() || 0) || undefined);
        data.discNumber = Number(file.getProperty('DISCNUMBER') || 0) || undefined;

        data.bpm = Number(file.getProperty('BPM') || 0) || undefined;
        data.copyright = file.getProperty('COPYRIGHT') || undefined;
        data.lyrics = file.getProperty('LYRICS') || undefined;
        data.releaseDate = file.getProperty('DATE') || undefined;

        const [replayGainAlbumGain, replayGainAlbumPeak, replayGainTrackGain, replayGainTrackPeak] = [
            file.getProperty('REPLAYGAIN_ALBUM_GAIN'),
            file.getProperty('REPLAYGAIN_ALBUM_PEAK'),
            file.getProperty('REPLAYGAIN_TRACK_GAIN'),
            file.getProperty('REPLAYGAIN_TRACK_PEAK'),
        ];

        const replayGain: TagLibMetadata['replayGain'] = {};
        if (replayGainAlbumGain) replayGain.albumReplayGain = replayGainAlbumGain;
        if (replayGainAlbumPeak) replayGain.albumPeakAmplitude = Number(replayGainAlbumPeak);
        if (replayGainTrackGain) replayGain.trackReplayGain = replayGainTrackGain;
        if (replayGainTrackPeak) replayGain.trackPeakAmplitude = Number(replayGainTrackPeak);
        if (Object.keys(replayGain).length > 0) {
            data.replayGain = replayGain;
        }

        data.isrc = (isMp4 && file.getMP4Item('xid ')?.split(':').at(-1)) || file.getProperty('ISRC') || undefined;
        data.explicit = (isMp4 && file.getMP4Item('rtng') === '1') || file.getProperty('ITUNESADVISORY') === '1';

        if (pictures.length > 0) {
            const picture = pictures.filter((p) => p.type === 'FrontCover')[0];
            if (picture) {
                data.cover = {
                    data: picture.data,
                    type: picture.mimeType,
                };
            }
        }
    } catch (err) {
        console.error(err);
    } finally {
        file.dispose();
    }

    return data;
}

self.onmessage = async (event: MessageEvent<TagLibWorkerMessage>) => {
    const transfer: Transferable[] = [event.data.audioData.buffer];

    switch (event.data.type) {
        case 'Add':
            try {
                const result = await addMetadataToAudio(event.data as AddMetadataMessage);
                transfer.push(result.buffer);
                self.postMessage(
                    {
                        type: event.data.type,
                        data: result,
                    } satisfies TagLibFileResponse,
                    transfer
                );
            } catch (error) {
                self.postMessage(
                    {
                        type: event.data.type,
                        error: error instanceof Error ? error.message : String(error),
                    } satisfies TagLibWorkerResponse<undefined>,
                    transfer
                );
            }
            break;

        case 'Get':
            try {
                const result = await getMetadataFromAudio(event.data as GetMetadataMessage);
                self.postMessage(
                    {
                        type: event.data.type,
                        data: result,
                    } satisfies TagLibMetadataResponse,
                    transfer
                );
            } catch (error) {
                self.postMessage(
                    {
                        type: event.data.type,
                        error: error instanceof Error ? error.message : String(error),
                    } satisfies TagLibWorkerResponse<undefined>,
                    transfer
                );
            }
            break;
    }
};
