import { MessageHelper } from "zotero-plugin-toolkit";
import { config } from "../../package.json";
import { getNoteLinkParams } from "./link";
import type { handlers } from "../extras/relationWorker";

function closeRelationServer() {
  if (addon.data.relation.server) {
    addon.data.relation.server.destroy();
    addon.data.relation.server = undefined;
  }
}

async function getRelationServer(): Promise<MessageHelper<typeof handlers>> {
  if (!addon.data.relation.server) {
    const worker = new Worker(
      `chrome://${config.addonRef}/content/scripts/relationWorker.js`,
      { name: "relationWorker" },
    );
    const server = new MessageHelper<typeof handlers>({
      canBeDestroyed: false,
      dev: __env__ === "development",
      name: "relationWorkerMain",
      target: worker,
      handlers: {},
    });
    server.start();
    await server.exec("_ping");
    addon.data.relation.server = server;
  }

  return addon.data.relation.server!;
}

export { getRelationServer, closeRelationServer };

export {
  updateNoteLinkRelation,
  getNoteLinkInboundRelation,
  getNoteLinkOutboundRelation,
  linkAnnotationToTarget,
  getLinkTargetByAnnotation,
  getAnnotationByLinkTarget,
};

async function updateNoteLinkRelation(noteID: number) {
  ztoolkit.log("updateNoteLinkRelation", noteID);
  const note = Zotero.Items.get(noteID);
  const affectedNoteIDs = new Set([noteID]);
  const fromLibID = note.libraryID;
  const fromKey = note.key;
  const lines = await addon.api.note.getLinesInNote(note);
  const linkToData: LinkModel[] = [];
  for (let i = 0; i < lines.length; i++) {
    const linkMatches = lines[i].match(/href="zotero:\/\/note\/[^"]+"/g);
    if (!linkMatches) {
      continue;
    }
    for (const match of linkMatches) {
      const link = decodeHTMLEntities(match.slice(6, -1));
      const { noteItem, libraryID, noteKey, lineIndex, sectionName } =
        getNoteLinkParams(link);
      if (noteItem && noteItem.isNote() && noteItem.id !== note.id) {
        affectedNoteIDs.add(noteItem.id);
        linkToData.push({
          fromLibID,
          fromKey,
          toLibID: libraryID,
          toKey: noteKey!,
          fromLine: i,
          toLine: lineIndex ?? null,
          toSection: sectionName ?? null,
          url: link,
        });
      }
    }
  }
  const result = await (
    await getRelationServer()
  ).proxy.rebuildLinkForNote(fromLibID, fromKey, linkToData);

  for (const link of result.oldOutboundLinks as LinkModel[]) {
    const item = Zotero.Items.getByLibraryAndKey(link.toLibID, link.toKey);
    if (!item) {
      continue;
    }
    affectedNoteIDs.add(item.id);
  }
  Zotero.Notifier.trigger(
    // @ts-ignore
    "updateBNRelation",
    "item",
    Array.from(affectedNoteIDs),
    {},
    true,
  );
}

async function getNoteLinkOutboundRelation(
  noteID: number,
): Promise<LinkModel[]> {
  const note = Zotero.Items.get(noteID);
  const fromLibID = note.libraryID;
  const fromKey = note.key;
  return await (
    await getRelationServer()
  ).proxy.getOutboundLinks(fromLibID, fromKey);
}

async function getNoteLinkInboundRelation(
  noteID: number,
): Promise<LinkModel[]> {
  const note = Zotero.Items.get(noteID);
  const toLibID = note.libraryID;
  const toKey = note.key;
  return await (
    await getRelationServer()
  ).proxy.getInboundLinks(toLibID, toKey);
}

function decodeHTMLEntities(text: string) {
  return text
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">");
}

interface LinkModel {
  fromLibID: number;
  fromKey: string;
  toLibID: number;
  toKey: string;
  fromLine: number;
  toLine: number | null;
  toSection: string | null;
  url: string;
}

async function linkAnnotationToTarget(model: AnnotationModel) {
  return await (await getRelationServer()).proxy.linkAnnotationToTarget(model);
}

async function getLinkTargetByAnnotation(
  fromLibID: number,
  fromKey: string,
): Promise<AnnotationModel | undefined> {
  return await (
    await getRelationServer()
  ).proxy.getLinkTargetByAnnotation(fromLibID, fromKey);
}

async function getAnnotationByLinkTarget(
  toLibID: number,
  toKey: string,
): Promise<AnnotationModel | undefined> {
  return await (
    await getRelationServer()
  ).proxy.getAnnotationByLinkTarget(toLibID, toKey);
}

interface AnnotationModel {
  fromLibID: number;
  fromKey: string;
  toLibID: number;
  toKey: string;
  url: string;
}
