import { parseFromTokenizer, parseWebStream } from "music-metadata";
import { contentType } from "@std/media-types";
import * as URI from "uri-js";
import * as HTTP_TOKENIZER from "@tokenizer/http";
import * as RANGE_TOKENIZER from "@tokenizer/range";

import type { TrackStats, TrackTags } from "@applets/core/types";
import type { Extraction, Urls } from "./types.d.ts";
import { expose } from "@scripts/common";

////////////////////////////////////////////
// ACTIONS
////////////////////////////////////////////
const actions = expose({
  supply,
});

export type Actions = typeof actions;

// Actions

async function supply(args: {
  includeArtwork?: boolean;
  mimeType?: string;
  stream?: ReadableStream;
  urls?: Urls;
}): Promise<Extraction> {
  // Construct records
  // TODO: Use other metadata lib as fallback: https://github.com/buzz/mediainfo.js
  const response = await musicMetadataTags(args).catch((err): Extraction => {
    console.warn("Metadata processor error:", err);
    console.log(args);

    return {};
  });

  // Fin
  return response;
}

////////////////////////////////////////////
// 🛠️
////////////////////////////////////////////
async function musicMetadataTags({
  includeArtwork,
  mimeType,
  stream,
  urls,
}: {
  includeArtwork?: boolean;
  mimeType?: string;
  stream?: ReadableStream;
  urls?: Urls;
}): Promise<Extraction> {
  const uri = urls ? URI.parse(urls.get) : undefined;
  const pathParts = uri?.path?.split("/");
  const filename = pathParts?.[pathParts.length - 1];

  let meta;

  if (urls?.get.startsWith("blob:")) {
    const mimeFallback = filename?.includes(".")
      ? contentType(filename.split(".").reverse()[0])
      : undefined;

    const resp = await fetch(urls.get);
    const stream = resp.body;

    if (!stream) return {};
    meta = await parseWebStream(
      stream,
      { mimeType: mimeType || mimeFallback },
      { skipCovers: !includeArtwork },
    );
  } else if (urls) {
    const httpClient = new HTTP_TOKENIZER.HttpClient(urls.head, { resolveUrl: false });
    httpClient.resolvedUrl = urls.get;

    const tokenizer = await RANGE_TOKENIZER.tokenizer(httpClient);

    meta = await parseFromTokenizer(tokenizer, { skipCovers: !includeArtwork });
  } else if (stream) {
    meta = await parseWebStream(stream, { mimeType }, { skipCovers: !includeArtwork });
  } else {
    throw new Error("Missing args, need either some urls or a stream.");
  }

  const stats: TrackStats = {
    duration: meta.format.duration,
  };

  const tags: TrackTags = {
    album: meta.common.album,
    artist: meta.common.artist,
    disc: { no: meta.common.disk.no || 1, of: meta.common.disk.of ?? undefined },
    genre: Array.isArray(meta.common.genre) ? meta.common.genre[0] : meta.common.genre,
    title: meta.common.title || filename || urls?.head || "Unknown",
    track: { no: meta.common.track.no || 1, of: meta.common.track.of ?? undefined },
    year: meta.common.year,
  };

  return {
    artwork: includeArtwork ? meta.common.picture : undefined,
    stats,
    tags,
  };
}
