import * as Uint8 from "uint8arrays";
import { createEndpoint, type MessageEndpoint } from "@remote-ui/rpc";
import { xxh32 } from "xxh32";

import type { Track } from "@applets/core/types";

export { SharedWorkerPolyfill as SharedWorker } from "@okikio/sharedworker";

export function arrayShuffle<T>(array: Array<T>): Array<T> {
  if (array.length === 0) {
    return [];
  }

  array = [...array];

  for (let index = array.length - 1; index > 0; index--) {
    const randArr = crypto.getRandomValues(new Uint32Array(1));
    const randVal = randArr[0] / 2 ** 32;
    const newIndex = Math.floor(randVal * (index + 1));
    [array[index], array[newIndex]] = [array[newIndex], array[index]];
  }

  return array;
}

export function cleanUndefinedValuesForTracks(tracks: Track[]): Track[] {
  return tracks.map((track) => {
    const t = { ...track };

    if (t.tags) {
      if ("album" in t.tags && t.tags.album === undefined) delete t.tags.album;
      if ("artist" in t.tags && t.tags.artist === undefined) delete t.tags.artist;
      if ("genre" in t.tags && t.tags.genre === undefined) delete t.tags.genre;
      if ("year" in t.tags && t.tags.year === undefined) delete t.tags.year;

      if ("of" in t.tags.disc && t.tags.disc.of === undefined) delete t.tags.disc.of;
      if ("of" in t.tags.track && t.tags.track.of === undefined) delete t.tags.track.of;
    }

    return t;
  });
}

export function comparable(value: unknown) {
  return xxh32(JSON.stringify(value));
}

export function endpoint<T extends Record<string, any>>(port: MessagePort) {
  const e = createEndpoint<T>(port);
  port.start();
  return e;
}

export function expose<T extends Record<string, any>>(actions: T): T {
  (self as unknown as SharedWorkerGlobalScope).onconnect = (event: MessageEvent) => {
    const port = event.ports[0];
    createEndpoint<T>(port).expose(actions);
    port.start();
  };

  return actions;
}

export function groupTracksPerScheme(
  tracks: Track[],
  initial: Record<string, Track[]> = {},
): Record<string, Track[]> {
  return tracks.reduce((acc: Record<string, Track[]>, track: Track) => {
    const scheme = track.uri.split(":", 1)[0];
    return { ...acc, [scheme]: [...(acc[scheme] || []), track] };
  }, initial);
}

export function inIframe() {
  return window.self !== window.top;
}

export function isPrimitive(test: unknown) {
  return test !== Object(test);
}

export function jsonDecode<T>(a: any): T {
  return JSON.parse(new TextDecoder().decode(a));
}

export function jsonEncode<T>(a: T): Uint8Array {
  return new TextEncoder().encode(JSON.stringify(a));
}

export async function trackArtworkCacheId(track: Track): Promise<string> {
  return await crypto.subtle
    .digest("SHA-256", new TextEncoder().encode(track.uri))
    .then((a) => Uint8.toString(new Uint8Array(a), "base64url"));
}
