/// <reference lib="dom" />
import { createHash } from "node:crypto";
import { Worker, isMainThread, parentPort } from "node:worker_threads";
import * as url from "url";
import getHighlights, {
  getFileExamples,
} from "../../frontend/app/getHighlights";
import { ParsedMIDIFile } from "../../frontend/app/types";

const SERVER_URL = process.env.SERVER_URL || "http://newarch.local:3000";
const NUM_WORKERS = 8;

export const hashExample = (ex: any[]) => {
  ex = JSON.parse(JSON.stringify(ex));
  let lowNote = Infinity;
  for (const symbol of ex) {
    if (symbol.type === "note") {
      lowNote = Math.min(lowNote, symbol.note);
    }
  }
  if (lowNote !== Infinity) {
    for (const symbol of ex) {
      if (symbol.type === "note") {
        symbol.note -= lowNote;
      }
    }
  }

  const hash = createHash("sha256");
  for (const symbol of ex) {
    hash.update(
      `${symbol.type} ${symbol.note} ${symbol.whole} ${symbol.numerator} ${symbol.denominator}`
    );
  }

  return hash.digest("hex").slice(16);
};

const processFile = async (id: string) => {
  const parsedFile = (
    await (await fetch(`${SERVER_URL}/midi?id=eq.${id}`)).json()
  )[0] as ParsedMIDIFile;
  let highlights = parsedFile.analysis?.highlights;
  if (!highlights) {
    highlights = getHighlights(parsedFile);
    try {
      const res = await fetch(`${SERVER_URL}/midi?id=eq.${id}`, {
        method: "PATCH",
        body: JSON.stringify({ analysis: { highlights } }),
        headers: {
          "Content-Type": "application/json",
        },
      });
      if (res.status >= 400) {
        console.log("Error:", await res.text());
      }
    } catch (e) {
      console.log("Exception posting highlights:", e.message);
    }
  }
  const payload = getFileExamples(parsedFile, highlights).map((example) => ({
    hash: hashExample(example.example),
    ...example,
  }));
  if (payload.length === 0) {
    return;
  }

  try {
    const res = await fetch(`${SERVER_URL}/examples?on_conflict=hash`, {
      method: "POST",
      body: JSON.stringify(payload),
      headers: {
        "Content-Type": "application/json",
        Prefer: "resolution=ignore-duplicates",
      },
    });
    if (res.status >= 400) {
      console.log("Error:", await res.text());
    }
  } catch (e) {
    console.log("Exception posting examples:", e.message);
  }
};

const main = async () => {
  console.log("starting");
  const channel = new MessageChannel();
  let numExited = 0;
  let resolveJoin: ((arg: any) => void) | null = null;
  const join = new Promise((resolve) => {
    resolveJoin = resolve;
  });
  const messages = (
    await (
      await fetch(SERVER_URL + "/midi?select=id&order=rand_order")
    ).json()
  ).map((x) => x.id);
  for (let i = 0; i < NUM_WORKERS; i++) {
    messages.push("exit");
  }
  let messagePos = 0;
  for (let i = 0; i < NUM_WORKERS; i++) {
    const worker = new Worker(url.fileURLToPath(import.meta.url));
    worker.on("exit", () => {
      numExited += 1;
      if (numExited === NUM_WORKERS) {
        resolveJoin && resolveJoin(null);
      }
    });
    worker.on("message", () => {
      worker.postMessage(messages[messagePos++]);
    });
  }
  await join;
};

const child = () => {
  parentPort.postMessage("here");
  parentPort.on("message", async (msg) => {
    if (msg === "exit") {
      process.exit(0);
    }
    console.log("process", msg);
    try {
      await processFile(msg);
    } catch(e) {
      console.log("error processing:", e.msg);
    }
    parentPort.postMessage("here");
  });
};

if (isMainThread) {
  await main();
} else {
  child();
}
