import { queryOptions } from '@tanstack/react-query';
import isElectron from 'is-electron';

import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { queryClient, QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById, useSettingsStore } from '/@/renderer/store';
import { hasFeature } from '/@/shared/api/utils';
import {
    FullLyricsMetadata,
    InternetProviderLyricResponse,
    InternetProviderLyricSearchResponse,
    LyricGetQuery,
    LyricSearchQuery,
    LyricsQuery,
    QueueSong,
    StructuredLyric,
    SynchronizedLyricsArray,
} from '/@/shared/types/domain-types';
import { LyricSource } from '/@/shared/types/domain-types';
import { ServerFeature } from '/@/shared/types/features-types';

const lyricsIpc = isElectron() ? window.api.lyrics : null;

// Match LRC lyrics format by https://github.com/ustbhuangyi/lyric-parser
// [mm:ss.SSS] text
const timeExp = /\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]([^\n]+)(\n|$)/g;

// Match karaoke lyrics format returned by NetEase
// [SSS,???] text
const alternateTimeExp = /\[(\d*),(\d*)]([^\n]+)(\n|$)/g;

const formatLyrics = (lyrics: string) => {
    const synchronizedLines = lyrics.matchAll(timeExp);
    const formattedLyrics: SynchronizedLyricsArray = [];

    for (const line of synchronizedLines) {
        const [, minute, sec, ms, text] = line;
        const minutes = parseInt(minute, 10);
        const seconds = parseInt(sec, 10);
        const milis = ms?.length === 3 ? parseInt(ms, 10) : parseInt(ms, 10) * 10;

        const timeInMilis = (minutes * 60 + seconds) * 1000 + milis;

        formattedLyrics.push([timeInMilis, text]);
    }

    if (formattedLyrics.length > 0) return formattedLyrics;

    const alternateSynchronizedLines = lyrics.matchAll(alternateTimeExp);
    for (const line of alternateSynchronizedLines) {
        const [, timeInMilis, , text] = line;
        const cleanText = text
            .replaceAll(/\(\d+,\d+\)/g, '')
            .replaceAll(/\s,/g, ',')
            .replaceAll(/\s\./g, '.');
        formattedLyrics.push([Number(timeInMilis), cleanText]);
    }

    if (formattedLyrics.length > 0) return formattedLyrics;

    // If no synchronized lyrics were found, return the original lyrics
    return lyrics;
};

export const lyricsQueries = {
    remoteLyrics: (args: QueryHookArgs<LyricsQuery>, song: QueueSong | undefined) => {
        return queryOptions({
            gcTime: Infinity,
            queryFn: async (): Promise<FullLyricsMetadata | null> => {
                if (!song) return null;

                const { fetch } = useSettingsStore.getState().lyrics;

                if (!fetch) return null;

                const remoteLyricsResult: InternetProviderLyricResponse | null =
                    await lyricsIpc?.getRemoteLyricsBySong(song);

                if (remoteLyricsResult) {
                    return {
                        ...remoteLyricsResult,
                        lyrics: formatLyrics(remoteLyricsResult.lyrics),
                        remote: true,
                    };
                }

                return null;
            },
            queryKey: queryKeys.songs.remoteLyrics(args.serverId, args.query),
            staleTime: Infinity,
            ...args.options,
        });
    },
    search: (args: Omit<QueryHookArgs<LyricSearchQuery>, 'serverId'>) => {
        return queryOptions({
            gcTime: 1000 * 60 * 1,
            queryFn: () => {
                if (lyricsIpc) {
                    return lyricsIpc.searchRemoteLyrics(args.query);
                }
                return {} as Record<LyricSource, InternetProviderLyricSearchResponse[]>;
            },
            queryKey: queryKeys.songs.lyricsSearch(args.query),
            staleTime: 1000 * 60 * 1,
            ...args.options,
        });
    },
    serverLyrics: (args: QueryHookArgs<LyricsQuery>, song: QueueSong | undefined) => {
        return queryOptions({
            gcTime: Infinity,
            queryFn: async ({ signal }): Promise<FullLyricsMetadata | null | StructuredLyric[]> => {
                const server = getServerById(args.serverId);
                if (!server) throw new Error('Server not found');
                if (!song) return null;

                let localLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;

                if (hasFeature(server, ServerFeature.LYRICS_MULTIPLE_STRUCTURED)) {
                    const subsonicLyrics = await api.controller
                        .getStructuredLyrics({
                            apiClientProps: { serverId: args.serverId, signal },
                            query: { songId: song.id },
                        })
                        .catch(console.error);

                    if (subsonicLyrics?.length) {
                        localLyrics = subsonicLyrics;
                    }
                } else if (hasFeature(server, ServerFeature.LYRICS_SINGLE_STRUCTURED)) {
                    const jfLyrics = await api.controller
                        .getLyrics({
                            apiClientProps: { serverId: args.serverId, signal },
                            query: { songId: song.id },
                        })
                        .catch((err) => console.error(err));

                    if (jfLyrics) {
                        localLyrics = {
                            artist: song.artists?.[0]?.name,
                            lyrics: jfLyrics,
                            name: song.name,
                            remote: false,
                            source: server?.name ?? 'music server',
                        };
                    }
                } else if (song.lyrics) {
                    localLyrics = {
                        artist: song.artists?.[0]?.name,
                        lyrics: formatLyrics(song.lyrics),
                        name: song.name,
                        remote: false,
                        source: server?.name ?? 'music server',
                    };
                }

                return localLyrics;
            },
            queryKey: queryKeys.songs.serverLyrics(args.serverId, args.query),
            staleTime: Infinity,
            ...args.options,
        });
    },
    songLyrics: (args: QueryHookArgs<LyricsQuery>, song: QueueSong | undefined) => {
        return queryOptions({
            gcTime: Infinity,
            queryFn: async ({ signal }): Promise<FullLyricsMetadata | null | StructuredLyric[]> => {
                if (!song) return null;

                const serverLyricsQuery = lyricsQueries.serverLyrics(args, song);
                const serverLyrics = await queryClient.fetchQuery({
                    ...serverLyricsQuery,
                    queryFn: async (context) => {
                        return (
                            serverLyricsQuery.queryFn?.({
                                ...context,
                                signal,
                            }) ?? null
                        );
                    },
                });

                return serverLyrics;
            },
            queryKey: queryKeys.songs.lyrics(args.serverId, args.query),
            staleTime: Infinity,
            ...args.options,
        });
    },
    songLyricsByRemoteId: (args: QueryHookArgs<Partial<LyricGetQuery>>) => {
        return queryOptions({
            gcTime: Infinity,
            queryFn: async () => {
                const remoteLyricsResult = await lyricsIpc?.getRemoteLyricsByRemoteId(
                    args.query as any,
                );

                if (remoteLyricsResult) {
                    return formatLyrics(remoteLyricsResult);
                }

                return null;
            },
            queryKey: queryKeys.songs.lyricsByRemoteId(args.query),
            staleTime: Infinity,
            ...args.options,
        });
    },
};
