import { z } from 'zod';

import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import { coerceYear, parsePartialIsoDateFromApi } from '/@/shared/api/partial-iso-date';
import { replacePathPrefix } from '/@/shared/api/utils';
import {
    Album,
    AlbumArtist,
    Folder,
    Genre,
    LibraryItem,
    MusicFolder,
    Playlist,
    RelatedArtist,
    Song,
} from '/@/shared/types/domain-types';
import { ServerListItem, ServerType } from '/@/shared/types/types';

const TICKS_PER_MS = 10000;

type AlbumOrSong = z.infer<typeof jfType._response.album> | z.infer<typeof jfType._response.song>;

const KEYS_TO_OMIT = new Set(['AlbumArtist', 'Artist']);

const getPeople = (item: AlbumOrSong): null | Record<string, RelatedArtist[]> => {
    if (item.People) {
        const participants: Record<string, RelatedArtist[]> = {};

        for (const person of item.People) {
            const key = person.Type || '';
            if (KEYS_TO_OMIT.has(key)) {
                continue;
            }

            const item: RelatedArtist = {
                // for other roles, we just want to display this and not filter.
                // filtering (and links) would require a separate field, PersonIds
                id: '',
                imageId: null,
                imageUrl: null,
                name: person.Name,
                userFavorite: false,
                userRating: null,
            };

            if (key in participants) {
                participants[key].push(item);
            } else {
                participants[key] = [item];
            }
        }

        return participants;
    }

    return null;
};

const getTags = (item: AlbumOrSong): null | Record<string, string[]> => {
    if (item.Tags) {
        const tags: Record<string, string[]> = {};
        for (const tag of item.Tags) {
            tags[tag] = [];
        }

        return tags;
    }

    return null;
};

const getSongImageId = (item: z.infer<typeof jfType._response.song>): null | string => {
    if (item.ImageTags?.Primary) {
        return item.Id;
    }

    if (item.AlbumPrimaryImageTag && item.AlbumId) {
        return item.AlbumId;
    }

    return null;
};

const getAlbumImageId = (item: z.infer<typeof jfType._response.album>): null | string => {
    if (item.ImageTags?.Primary) {
        return item.Id;
    }

    return null;
};

const getAlbumArtistImageId = (
    item: z.infer<typeof jfType._response.albumArtist>,
): null | string => {
    if (item.ImageTags?.Primary) {
        return item.Id;
    }

    return null;
};

const getPlaylistImageId = (item: z.infer<typeof jfType._response.playlist>): null | string => {
    if (item.ImageTags?.Primary) {
        return item.Id;
    }

    return null;
};

const getArtists = (
    item: z.infer<typeof jfType._response.song>,
    participants?: null | Record<string, RelatedArtist[]>,
): RelatedArtist[] => {
    if (!item?.ArtistItems?.length && !item.AlbumArtists && !participants) {
        return [];
    }

    const result: RelatedArtist[] = [];

    (item?.ArtistItems?.length ? item.ArtistItems : item.AlbumArtists)?.forEach((entry) => {
        result.push({
            id: entry.Id,
            imageId: null,
            imageUrl: null,
            name: entry.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 jellyfinPremiereFields = (item: {
    PremiereDate?: string;
    ProductionYear?: number;
}): { originalYear: number; releaseDate: null | string; releaseYear: null | number } => {
    const premiere = parsePartialIsoDateFromApi(item.PremiereDate ?? null);
    const prodYear = coerceYear(item.ProductionYear);
    const releaseYear: null | number =
        premiere.year > 0 ? premiere.year : prodYear > 0 ? prodYear : null;
    const releaseDate = premiere.date ?? (prodYear > 0 ? String(prodYear) : null);
    const originalYear = premiere.year > 0 ? premiere.year : prodYear;
    return { originalYear, releaseDate, releaseYear };
};

const normalizeSong = (
    item: z.infer<typeof jfType._response.song>,
    server: null | ServerListItem,
    pathReplace?: string,
    pathReplaceWith?: string,
): Song => {
    let bitDepth: null | number = null;
    let bitRate = 0;
    let channels: null | number = null;
    let container: null | string = null;
    let path: null | string = null;
    let sampleRate: null | number = null;
    let size = 0;

    if (item.MediaSources?.length) {
        const source = item.MediaSources[0];

        container = source.Container;
        path = source.Path;
        size = source.Size;

        if ((source.MediaStreams?.length || 0) > 0) {
            for (const stream of source.MediaStreams) {
                if (stream.Type === 'Audio') {
                    bitDepth = stream.BitDepth || null;
                    bitRate =
                        stream.BitRate !== undefined
                            ? Number(Math.trunc(stream.BitRate / 1000))
                            : 0;
                    channels = stream.Channels || null;
                    sampleRate = stream.SampleRate || null;
                    break;
                }
            }
        }
    } else {
        console.warn('Jellyfin song retrieved with no media sources', item);
    }

    const participants = getPeople(item);

    const artists = getArtists(item, participants);

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

    return {
        _itemType: LibraryItem.SONG,
        _serverId: server?.id || '',
        _serverType: ServerType.JELLYFIN,
        album: item.Album,
        albumArtistName: item.AlbumArtist || '',
        albumArtists: item.AlbumArtists?.map((entry) => ({
            id: entry.Id,
            imageId: entry.Id,
            imageUrl: null,
            name: entry.Name,
            userFavorite: false,
            userRating: null,
        })),
        albumId: item.AlbumId || `dummy/${item.Id}`,
        artistName: item?.ArtistItems?.map((entry) => entry.Name).join(', ') || '',
        artists,
        bitDepth,
        bitRate,
        bpm: null,
        channels,
        comment: null,
        compilation: null,
        container,
        createdAt: item.DateCreated,
        discNumber: (item.ParentIndexNumber && item.ParentIndexNumber) || 1,
        discSubtitle: null,
        duration: item.RunTimeTicks / TICKS_PER_MS,
        explicitStatus: null,
        gain:
            item.NormalizationGain !== undefined
                ? {
                      track: item.NormalizationGain,
                  }
                : item.LUFS
                  ? {
                        track: -18 - item.LUFS,
                    }
                  : null,
        genres: item.GenreItems?.map((entry) => ({
            _itemType: LibraryItem.GENRE,
            _serverId: server?.id || '',
            _serverType: ServerType.JELLYFIN,
            albumCount: null,
            id: entry.Id,
            imageId: null,
            imageUrl: null,
            name: entry.Name,
            songCount: null,
        })),
        id: item.Id,
        imageId: getSongImageId(item),
        imageUrl: null,
        lastPlayedAt: null,
        lyrics: null,
        mbzRecordingId: null,
        mbzTrackId: item.ProviderIds?.MusicBrainzTrack || null,
        name: item.Name,
        participants,
        path: replacePathPrefix(path || '', pathReplace, pathReplaceWith),
        peak: null,
        playCount: (item.UserData && item.UserData.PlayCount) || 0,
        playlistItemId: item.PlaylistItemId,
        releaseDate,
        releaseYear,
        sampleRate,
        size,
        sortName: item.SortName || item.Name,
        tags: getTags(item),
        trackNumber: item.IndexNumber,
        trackSubtitle: null,
        updatedAt: item.DateCreated,
        userFavorite: (item.UserData && item.UserData.IsFavorite) || false,
        userRating: null,
    };
};

const normalizeAlbum = (
    item: z.infer<typeof jfType._response.album>,
    server: null | ServerListItem,
    pathReplace?: string,
    pathReplaceWith?: string,
): Album => {
    const { originalYear, releaseDate, releaseYear } = jellyfinPremiereFields(item);

    return {
        _itemType: LibraryItem.ALBUM,
        _serverId: server?.id || '',
        _serverType: ServerType.JELLYFIN,
        albumArtistName: item.AlbumArtist,
        albumArtists:
            item.AlbumArtists.map((entry) => ({
                id: entry.Id,
                imageId: entry.Id,
                imageUrl: null,
                name: entry.Name,
                userFavorite: false,
                userRating: null,
            })) || [],
        artists: (item.ArtistItems?.length ? item.ArtistItems : item.AlbumArtists)?.map(
            (entry) => ({
                id: entry.Id,
                imageId: entry.Id,
                imageUrl: null,
                name: entry.Name,
                userFavorite: false,
                userRating: null,
            }),
        ),
        comment: null,
        createdAt: item.DateCreated,
        duration: item.RunTimeTicks / TICKS_PER_MS,
        explicitStatus: null,
        genres:
            item.GenreItems?.map((entry) => ({
                _itemType: LibraryItem.GENRE,
                _serverId: server?.id || '',
                _serverType: ServerType.JELLYFIN,
                albumCount: null,
                id: entry.Id,
                imageId: null,
                imageUrl: null,
                name: entry.Name,
                songCount: null,
            })) || [],
        id: item.Id,
        imageId: getAlbumImageId(item),
        imageUrl: null,
        isCompilation: null,
        lastPlayedAt: null,
        mbzId: item.ProviderIds?.MusicBrainzAlbum || null,
        mbzReleaseGroupId: item.ProviderIds?.MusicBrainzReleaseGroup || null,
        name: item.Name,
        originalDate: releaseDate,
        originalYear,
        participants: getPeople(item),
        playCount: item.UserData?.PlayCount || 0,
        recordLabels: item.Studios?.map((entry) => entry.Name) || [],
        releaseDate,
        releaseType: null,
        releaseTypes: [],
        releaseYear,
        size: null,
        songCount: item?.ChildCount || null,
        songs: item.Songs?.map((song) => normalizeSong(song, server, pathReplace, pathReplaceWith)),
        sortName: item.SortName || item.Name,
        tags: getTags(item),
        updatedAt: item?.DateLastMediaAdded || item.DateCreated,
        userFavorite: item.UserData?.IsFavorite || false,
        userRating: null,
        version: null,
    };
};

const normalizeAlbumArtist = (
    item: z.infer<typeof jfType._response.albumArtist> & {
        similarArtists?: z.infer<typeof jfType._response.albumArtistList>;
    },
    server: null | ServerListItem,
): AlbumArtist => {
    const similarArtists =
        item.similarArtists?.Items?.filter((entry) => entry.Name !== 'Various Artists').map(
            (entry) => ({
                id: entry.Id,
                imageId: getAlbumArtistImageId(entry),
                imageUrl: null,
                name: entry.Name,
                userFavorite: entry.UserData?.IsFavorite || false,
                userRating: null,
            }),
        ) || [];

    return {
        _itemType: LibraryItem.ALBUM_ARTIST,
        _serverId: server?.id || '',
        _serverType: ServerType.JELLYFIN,
        albumCount: item.AlbumCount ?? null,
        biography: item.Overview || null,
        duration: item.RunTimeTicks / TICKS_PER_MS,
        genres: item.GenreItems?.map((entry) => ({
            _itemType: LibraryItem.GENRE,
            _serverId: server?.id || '',
            _serverType: ServerType.JELLYFIN,
            albumCount: null,
            id: entry.Id,
            imageId: null,
            imageUrl: null,
            name: entry.Name,
            songCount: null,
        })),
        id: item.Id,
        imageId: getAlbumArtistImageId(item),
        imageUrl: null,
        lastPlayedAt: null,
        mbz: item.ProviderIds?.MusicBrainzArtist || null,
        name: item.Name,
        playCount: item.UserData?.PlayCount || 0,
        similarArtists,
        songCount: item.SongCount ?? null,
        userFavorite: item.UserData?.IsFavorite || false,
        userRating: null,
    };
};

const normalizePlaylist = (
    item: z.infer<typeof jfType._response.playlist>,
    server: null | ServerListItem,
): Playlist => {
    return {
        _itemType: LibraryItem.PLAYLIST,
        _serverId: server?.id || '',
        _serverType: ServerType.JELLYFIN,
        description: item.Overview || null,
        duration: item.RunTimeTicks / TICKS_PER_MS,
        genres: item.GenreItems?.map((entry) => ({
            _itemType: LibraryItem.GENRE,
            _serverId: server?.id || '',
            _serverType: ServerType.JELLYFIN,
            albumCount: null,
            id: entry.Id,
            imageId: null,
            imageUrl: null,
            name: entry.Name,
            songCount: null,
        })),
        id: item.Id,
        imageId: getPlaylistImageId(item),
        imageUrl: null,
        name: item.Name,
        owner: null,
        ownerId: null,
        public: null,
        rules: null,
        size: null,
        songCount: item?.ChildCount || null,
        sync: null,
    };
};

const normalizeMusicFolder = (item: z.infer<typeof jfType._response.musicFolder>): MusicFolder => {
    return {
        id: item.Id,
        name: item.Name,
    };
};

// const normalizeArtist = (item: any) => {
//   return {
//     album: (item.album || []).map((entry: any) => normalizeAlbum(entry)),
//     albumCount: item.AlbumCount,
//     duration: item.RunTimeTicks / 10000000,
//     genre: item.GenreItems && item.GenreItems.map((entry: any) => normalizeItem(entry)),
//     id: item.Id,
//     image: getCoverArtUrl(item),
//     info: {
//       biography: item.Overview,
//       externalUrl: (item.ExternalUrls || []).map((entry: any) => normalizeItem(entry)),
//       imageUrl: undefined,
//       similarArtist: (item.similarArtist || []).map((entry: any) => normalizeArtist(entry)),
//     },
//     starred: item.UserData && item.UserData?.IsFavorite ? 'true' : undefined,
//     title: item.Name,
//     uniqueId: nanoid(),
//   };
// };

const getGenreImageId = (item: z.infer<typeof jfType._response.genre>): null | string => {
    if (item.ImageTags?.Primary) {
        return item.Id;
    }

    return null;
};

const normalizeGenre = (
    item: z.infer<typeof jfType._response.genre>,
    server: null | ServerListItem,
): Genre => {
    return {
        _itemType: LibraryItem.GENRE,
        _serverId: server?.id || '',
        _serverType: ServerType.JELLYFIN,
        albumCount: null,
        id: item.Id,
        imageId: getGenreImageId(item),
        imageUrl: null,
        name: item.Name,
        songCount: null,
    };
};

const normalizeFolder = (
    item: z.infer<typeof jfType._response.folder>,
    server: null | ServerListItem,
): Folder => {
    return {
        _itemType: LibraryItem.FOLDER,
        _serverId: server?.id || 'unknown',
        _serverType: ServerType.JELLYFIN,
        children: undefined,
        id: item.Id,
        name: item.Name || 'Unknown folder',
        parentId: item.ParentId,
    };
};

// const normalizeScanStatus = () => {
//   return {
//     count: 'N/a',
//     scanning: false,
//   };
// };

export const jfNormalize = {
    album: normalizeAlbum,
    albumArtist: normalizeAlbumArtist,
    folder: normalizeFolder,
    genre: normalizeGenre,
    musicFolder: normalizeMusicFolder,
    playlist: normalizePlaylist,
    song: normalizeSong,
};
