import { PAGINATION_LIMIT } from "#lib/pagination";
import { fetchArtistProfile, fetchProfiles } from "#api/profiles";
import { findFavouriteProfiles } from "#entities/account";
import { IArtistDetails, IArtistWithFavs } from "../types";

// the original page is a clusterfuck which pulls an entire artist list
// and does sorting/ordering/filtering on client
// rewriting it requires rewriting backend endpoints
// so for now it does the same
// TODO: rewrite it on backend
let allArtists: Awaited<ReturnType<typeof fetchProfiles>> | undefined =
  undefined;

export interface IGetArtistsArgs {
  service?: string;
  offset?: number;
  order?: "asc" | "desc";
  sort_by?: "favorited" | "indexed" | "updated" | "name" | "service";
  query?: string;
}

interface IGetArtistsResult {
  artists: (IArtistWithFavs & { isFavourite: boolean })[];
  count: number;
}

export async function getArtists({
  offset = 0,
  service,
  order = "desc",
  sort_by = "favorited",
  query,
}: IGetArtistsArgs): Promise<IGetArtistsResult> {
  if (!allArtists) {
    allArtists = await fetchProfiles();
  }

  const normalizedQuery = query?.trim().toLowerCase();
  // MDN recommends doing this for large arrays
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
  const compare = new Intl.Collator().compare;

  const filteredArtists = allArtists
    .filter((profile) => {
      let isServiceMatched = true

      if (service) {
        isServiceMatched = profile.service === service;
      }

      let isQueryMatched = true

      if (normalizedQuery) {
        const normalizedName = profile.name.trim().toLowerCase();
        const normalizedID = profile.id.trim().toLowerCase();

        return (
          normalizedID.includes(normalizedQuery) ||
          normalizedName.includes(normalizedQuery)
        );
      }

      return isServiceMatched && isQueryMatched;
    })
    .sort((prev, next) => {
      switch (sort_by) {
        case "favorited": {
          return prev.favorited === next.favorited
            ? 0
            : prev.favorited > next.favorited
            ? 1
            : -1;
        }

        case "service": {
          return compare(prev.service, next.service);
        }

        case "name": {
          return compare(prev.name, next.name);
        }

        case "indexed": {
          // @ts-expect-error fuck dates
          const prevIndexed = prev.indexed * 1000;
          // @ts-expect-error fuck dates
          const nextIndexed = next.indexed * 1000;

          return prevIndexed === nextIndexed
            ? 0
            : prevIndexed > nextIndexed
            ? 1
            : -1;
        }
        case "updated": {
          // @ts-expect-error fuck dates
          const prevUpdated = prev.updated * 1000;
          // @ts-expect-error fuck dates
          const nextUpdated = next.updated * 1000;

          return prevUpdated === nextUpdated
            ? 0
            : prevUpdated > nextUpdated
            ? 1
            : -1;
        }

        default: {
          throw new Error(`Unknown sorting type "${sort_by satisfies never}".`);
        }
      }
    });

  // artists are only sorted by one field
  // so we can get away with "desc" just slicing off the end
  // without changing sorting logic
  const slicedArtists =
    order === "asc"
      ? filteredArtists.slice(offset, offset + PAGINATION_LIMIT)
      : filteredArtists
          .slice(
            -offset + -PAGINATION_LIMIT,
            offset === 0 ? undefined : -offset
          )
          .reverse();
  const profilesData: Parameters<typeof findFavouriteProfiles>[0] =
    slicedArtists.map(({ id, service }) => {
      return { id, service };
    });
  const favArtists = await findFavouriteProfiles(profilesData);
  const resultArtists: IGetArtistsResult["artists"] = slicedArtists.map(
    (artist) => {
      const fav = favArtists.find(
        ({ id, service }) => id === artist.id && service === artist.service
      );

      return {
        ...artist,
        isFavourite: fav === undefined ? false : true,
      };
    }
  );
  const count = filteredArtists.length;

  return { artists: resultArtists, count };
}

export async function getArtist(
  service: string,
  id: string
): Promise<IArtistDetails> {
  const profile = await fetchArtistProfile(service, id);

  return profile;
}
