import { z } from 'zod';

import { coerceYear, parsePartialIsoDate } from '/@/shared/api/partial-iso-date';
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
import {
    Album,
    AlbumArtist,
    ExplicitStatus,
    Folder,
    Genre,
    InternetRadioStation,
    LibraryItem,
    Playlist,
    RelatedArtist,
    ServerListItemWithCredential,
    ServerType,
    Song,
} from '/@/shared/types/domain-types';

const getArtistList = (
    artists?: typeof ssType._response.song._type.artists,
    artistId?: number | string,
    artistName?: string,
    participants?: null | Record<string, RelatedArtist[]>,
) => {
    if (!artists && !participants) {
        return [
            {
                id: artistId?.toString() || '',
                imageId: null,
                imageUrl: null,
                name: artistName || '',
                userFavorite: false,
                userRating: null,
            },
        ];
    }

    const result: RelatedArtist[] = [];

    artists?.forEach((item) => {
        result.push({
            id: item.id.toString(),
            imageId: null,
            imageUrl: null,
            name: item.name,
            userFavorite: false,
            userRating: null,
        });
    });

    if (participants?.['remixer']) {
        const existingIds = new Set(result.map((artist) => artist.id));
        for (const participant of participants['remixer']) {
            if (!existingIds.has(participant.id)) {
                result.push(participant);
            }
        }
    }

    return result;
};

const getParticipants = (
    item:
        | z.infer<typeof ssType._response.album>
        | z.infer<typeof ssType._response.albumListEntry>
        | z.infer<typeof ssType._response.song>,
) => {
    let participants: null | Record<string, RelatedArtist[]> = null;

    if (item.contributors) {
        participants = {};

        for (const contributor of item.contributors) {
            const artist = {
                id: contributor.artist.id?.toString() || '',
                imageId: null,
                imageUrl: null,
                name: contributor.artist.name || '',
                userFavorite: false,
                userRating: null,
            };

            const role = contributor.subRole
                ? `${contributor.role} (${contributor.subRole})`
                : contributor.role;

            if (role in participants) {
                participants[role].push(artist);
            } else {
                participants[role] = [artist];
            }
        }
    }

    return participants;
};

const getGenres = (
    item:
        | z.infer<typeof ssType._response.album>
        | z.infer<typeof ssType._response.albumListEntry>
        | z.infer<typeof ssType._response.song>,
    server?: null | ServerListItemWithCredential,
): Genre[] => {
    return item.genres
        ? item.genres.map((genre) => ({
              _itemType: LibraryItem.GENRE,
              _serverId: server?.id || 'unknown',
              _serverType: ServerType.SUBSONIC,
              albumCount: null,
              id: genre.name,
              imageId: null,
              imageUrl: null,
              name: genre.name,
              songCount: null,
          }))
        : item.genre
          ? [
                {
                    _itemType: LibraryItem.GENRE,
                    _serverId: server?.id || 'unknown',
                    _serverType: ServerType.SUBSONIC,
                    albumCount: null,
                    id: item.genre,
                    imageId: null,
                    imageUrl: null,
                    name: item.genre,
                    songCount: null,
                },
            ]
          : [];
};

const pad2 = (n: number) => String(n).padStart(2, '0');

const subsonicReleaseFields = (item: {
    releaseDate?: { day?: number; month?: number; year?: number };
    year?: number;
}): { releaseDate: null | string; releaseYear: null | number } => {
    const rd = item.releaseDate;
    if (
        rd &&
        typeof rd.year === 'number' &&
        typeof rd.month === 'number' &&
        typeof rd.day === 'number'
    ) {
        const iso = `${rd.year}-${pad2(rd.month)}-${pad2(rd.day)}`;
        const parsed = parsePartialIsoDate(iso);
        return { releaseDate: parsed.date, releaseYear: parsed.year };
    }

    const y = coerceYear(item.year);
    if (y > 0) {
        return { releaseDate: String(y), releaseYear: y };
    }

    return { releaseDate: null, releaseYear: null };
};
const subsonicTrackYearField = (item: {
    year?: number;
}): { date: null | string; year: null | number } => {
    const y = coerceYear(item.year);
    if (y > 0) {
        return { date: String(y), year: y };
    }

    return { date: null, year: null };
};

const normalizeSong = (
    item: z.infer<typeof ssType._response.song>,
    server?: null | ServerListItemWithCredential,
    playlistIndex?: number,
    discTitleMap?: Map<number, string>,
): Song => {
    const participants = getParticipants(item);
    const albumArtistsList = getArtistList(item.albumArtists, item.artistId, item.artist);
    const albumArtistName =
        item.albumArtists?.length > 0
            ? item.albumArtists.map((a) => a.name).join(', ')
            : item.artist || '';

    const { releaseDate, releaseYear } = subsonicReleaseFields(item);
    const { date, year } = subsonicTrackYearField(item);

    return {
        _itemType: LibraryItem.SONG,
        _serverId: server?.id || 'unknown',
        _serverType: ServerType.SUBSONIC,
        album: item.album || '',
        albumArtistName,
        albumArtists: albumArtistsList,
        albumId: item.albumId?.toString() || '',
        artistName: item.artist || '',
        artists: getArtistList(item.artists, item.artistId, item.artist, participants),
        bitDepth: item.bitDepth || null,
        bitRate: item.bitRate || 0,
        bpm: item.bpm || null,
        channels: item.channelCount || null,
        comment: null,
        compilation: null,
        container: item.contentType.startsWith('audio/') ? item.contentType.split('/')[1] : null,
        createdAt: item.created,
        date,
        discNumber: item.discNumber || 1,
        discSubtitle: discTitleMap?.get(item.discNumber ?? 1) ?? null,
        duration: item.duration ? item.duration * 1000 : 0,
        explicitStatus:
            item.explicitStatus === 'explicit'
                ? ExplicitStatus.EXPLICIT
                : item.explicitStatus === 'clean'
                  ? ExplicitStatus.CLEAN
                  : null,
        gain:
            item.replayGain && (item.replayGain.albumGain || item.replayGain.trackGain)
                ? {
                      album: item.replayGain.albumGain,
                      track: item.replayGain.trackGain,
                  }
                : null,
        genres: getGenres(item, server),
        id: item.id.toString(),
        imageId: item.coverArt?.toString() || null,
        imageUrl: null,
        lastPlayedAt: null,
        lyrics: null,
        mbzRecordingId: item.musicBrainzId || null,
        mbzTrackId: null,
        name: item.title,
        participants,
        path: item.path || '',
        peak:
            item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak)
                ? {
                      album: item.replayGain.albumPeak,
                      track: item.replayGain.trackPeak,
                  }
                : null,
        playCount: item?.playCount || 0,
        playlistItemId: playlistIndex !== undefined ? playlistIndex.toString() : undefined,
        releaseDate,
        releaseYear,
        sampleRate: item.samplingRate || null,
        size: item.size,
        sortName: item.title,
        tags: null,
        trackNumber: item.track || 1,
        trackSubtitle: null,
        updatedAt: '',
        userFavorite: Boolean(item.starred) || false,
        userRating: item.userRating || null,
        year,
    };
};

const normalizeAlbumArtist = (
    item:
        | (z.infer<typeof ssType._response.albumArtist> & {
              similarArtists?: z.infer<
                  typeof ssType._response.artistInfo
              >['artistInfo']['similarArtist'];
          })
        | (z.infer<typeof ssType._response.artistListEntry> & {
              similarArtists?: z.infer<
                  typeof ssType._response.artistInfo
              >['artistInfo']['similarArtist'];
          }),
    server?: null | ServerListItemWithCredential,
): AlbumArtist => {
    return {
        _itemType: LibraryItem.ALBUM_ARTIST,
        _serverId: server?.id || 'unknown',
        _serverType: ServerType.SUBSONIC,
        albumCount: item.albumCount ? Number(item.albumCount) : 0,
        biography: null,
        duration: null,
        genres: [],
        id: item.id.toString(),
        imageId: item.coverArt?.toString() || null,
        imageUrl: null,
        lastPlayedAt: null,
        mbz: null,
        name: item.name,
        playCount: null,
        similarArtists:
            item.similarArtists?.map((artist) => ({
                id: artist.id,
                imageId: artist.coverArt ?? artist.id,
                imageUrl: null,
                name: artist.name,
                userFavorite: Boolean(artist.starred) || false,
                userRating: artist.userRating || null,
            })) || [],
        songCount: null,
        userFavorite: Boolean(item.starred) || false,
        userRating: null,
    };
};

const PRIMARY_RELEASE_TYPES = ['album', 'ep', 'single', 'broadcast', 'other'];

const getReleaseType = (
    item: z.infer<typeof ssType._response.album> | z.infer<typeof ssType._response.albumListEntry>,
) => {
    if (!item.releaseTypes) {
        return null;
    }

    // Return the first primary release type
    return item.releaseTypes.find((type) => PRIMARY_RELEASE_TYPES.includes(type)) || null;
};

const normalizeAlbum = (
    item: z.infer<typeof ssType._response.album> | z.infer<typeof ssType._response.albumListEntry>,
    server?: null | ServerListItemWithCredential,
): Album => {
    const discTitleMap = new Map<number, string>();

    (item as z.infer<typeof ssType._response.album>).discTitles?.forEach((discTitle) => {
        discTitleMap.set(discTitle.disc, discTitle.title);
    });

    const { releaseDate, releaseYear } = subsonicReleaseFields(item);

    return {
        _itemType: LibraryItem.ALBUM,
        _serverId: server?.id || 'unknown',
        _serverType: ServerType.SUBSONIC,
        albumArtistName: item.artist,
        albumArtists: getArtistList(item.artists, item.artistId, item.artist),
        artists: [],
        comment: null,
        createdAt: item.created,
        duration: item.duration * 1000,
        explicitStatus:
            item.explicitStatus === 'explicit'
                ? ExplicitStatus.EXPLICIT
                : item.explicitStatus === 'clean'
                  ? ExplicitStatus.CLEAN
                  : null,
        genres: getGenres(item, server),
        id: item.id.toString(),
        imageId: item.coverArt?.toString() || null,
        imageUrl: null,
        isCompilation: null,
        lastPlayedAt: null,
        mbzId: null,
        mbzReleaseGroupId: null,
        name: item.name,
        originalDate: releaseDate,
        originalYear: releaseYear ?? 0,
        participants: getParticipants(item),
        playCount: null,
        recordLabels: item.recordLabels?.map((item) => item.name) || [],
        releaseDate,
        releaseType: getReleaseType(item),
        releaseTypes: item.releaseTypes || [],
        releaseYear,
        size: null,
        songCount: item.songCount,
        songs:
            (item as z.infer<typeof ssType._response.album>).song?.map((song) =>
                normalizeSong(song, server, undefined, discTitleMap),
            ) || [],
        sortName: item.title,
        tags: null,
        trackYearRange: null,
        updatedAt: item.created,
        userFavorite: Boolean(item.starred) || false,
        userRating: item.userRating || null,
        version: item.version || null,
    };
};

const normalizePlaylist = (
    item:
        | z.infer<typeof ssType._response.playlist>
        | z.infer<typeof ssType._response.playlistListEntry>,
    server?: null | ServerListItemWithCredential,
): Playlist => {
    return {
        _itemType: LibraryItem.PLAYLIST,
        _serverId: server?.id || 'unknown',
        _serverType: ServerType.SUBSONIC,
        description: item.comment || null,
        duration: item.duration * 1000,
        genres: [],
        id: item.id.toString(),
        imageId: item.coverArt?.toString() || null,
        imageUrl: null,
        name: item.name,
        owner: item.owner,
        ownerId: item.owner,
        public: item.public,
        size: null,
        songCount: item.songCount,
    };
};

const normalizeGenre = (
    item: z.infer<typeof ssType._response.genre>,
    server: null | ServerListItemWithCredential,
): Genre => {
    return {
        _itemType: LibraryItem.GENRE,
        _serverId: server?.id || 'unknown',
        _serverType: ServerType.SUBSONIC,
        albumCount: item.albumCount,
        id: item.value,
        imageId: null,
        imageUrl: null,
        name: item.value,
        songCount: item.songCount,
    };
};

const normalizeFolder = (
    item: z.infer<typeof ssType._response.directory>,
    server?: null | ServerListItemWithCredential,
): Folder => {
    const results = item.child?.reduce(
        (acc: { folders: Folder[]; songs: Song[] }, item) => {
            const isDirectory = item.isDir === true;

            if (isDirectory) {
                const folder = normalizeFolder(item, server);
                acc.folders.push(folder);
            } else {
                const song = normalizeSong(item, server);
                acc.songs.push(song);
            }

            return acc;
        },
        {
            folders: [],
            songs: [],
        },
    );

    return {
        _itemType: LibraryItem.FOLDER,
        _serverId: server?.id || 'unknown',
        _serverType: ServerType.SUBSONIC,
        children: {
            folders: results?.folders || [],
            songs: results?.songs || [],
        },
        id: item.id.toString(),
        imageId: item.coverArt?.toString() || null,
        imageUrl: null,
        name: item.title,
        parentId: item.parent,
    };
};

const normalizeInternetRadioStation = (
    item: z.infer<typeof ssType._response.internetRadioStation>,
): InternetRadioStation => {
    return {
        homepageUrl: item.homepageUrl || null,
        id: item.id,
        imageId: item.coverArt?.toString() || null,
        imageUrl: null,
        name: item.name,
        streamUrl: item.streamUrl,
    };
};

export const ssNormalize = {
    album: normalizeAlbum,
    albumArtist: normalizeAlbumArtist,
    folder: normalizeFolder,
    genre: normalizeGenre,
    internetRadioStation: normalizeInternetRadioStation,
    playlist: normalizePlaylist,
    song: normalizeSong,
};
