import { Worker } from "node:worker_threads";

import { paraWinAnsi } from "../certificado.ts";

/*
 * A marca no PDF: cada página do arquivo baixado leva, no rodapé, para quem
 * ele foi licenciado (nome, e-mail e pedido). O link assinado fica em
 * link-marcado.ts.
 *
 * Abrir e regravar um PDF é trabalho pesado, e um arquivo defeituoso pode
 * prender a biblioteca por minutos. Por isso a marca roda num trabalhador à
 * parte (o servidor da loja segue atendendo), com tempo máximo, no máximo
 * duas de cada vez e só em arquivo com cara de PDF inteiro. Não deu (PDF
 * protegido por senha, defeituoso, lento demais): devolve null e o arquivo
 * vai sem a marca.
 */

const TEMPO_MAXIMO_MS = 20_000;
const NO_MAXIMO_JUNTAS = 2;
let marcando = 0;

/* O código do trabalhador (roda fora da página, com a biblioteca de PDF). */
const CODIGO_DO_TRABALHADOR = `
const { parentPort, workerData } = require("node:worker_threads");
const { PDFDocument, ParseSpeeds, StandardFonts, rgb } = require("pdf-lib");
(async () => {
  try {
    const documento = await PDFDocument.load(workerData.bytes, { updateMetadata: false, parseSpeed: ParseSpeeds.Fastest });
    const fonte = await documento.embedFont(StandardFonts.Helvetica);
    const texto = workerData.texto;
    for (const pagina of documento.getPages()) {
      const largura = pagina.getSize().width;
      let tamanho = 7;
      while (tamanho > 4 && fonte.widthOfTextAtSize(texto, tamanho) > largura - 24) tamanho -= 0.5;
      const larguraDoTexto = fonte.widthOfTextAtSize(texto, tamanho);
      pagina.drawText(texto, { x: Math.max(8, (largura - larguraDoTexto) / 2), y: 8, size: tamanho, font: fonte, color: rgb(0.45, 0.47, 0.53), opacity: 0.85 });
    }
    documento.setSubject(texto);
    const saida = await documento.save();
    parentPort.postMessage({ ok: true, bytes: saida }, [saida.buffer]);
  } catch (erro) {
    parentPort.postMessage({ ok: false, erro: String((erro && erro.message) || erro) });
  }
})();
`;

/* Começa com %PDF- e termina com startxref e %%EOF: um PDF inteiro, não um pedaço nem outra coisa. */
export function pareceUmPdf(bytes: Uint8Array): boolean {
  if (bytes.length < 64 || Buffer.from(bytes.subarray(0, 5)).toString("latin1") !== "%PDF-") return false;
  const fim = Buffer.from(bytes.subarray(Math.max(0, bytes.length - 2048))).toString("latin1");
  return fim.includes("%%EOF") && fim.includes("startxref");
}

/** Põe a linha "Licenciado para..." no rodapé de cada página. null: vai sem a marca. */
export async function marcarPdf(bytes: Uint8Array, linha: string): Promise<Uint8Array | null> {
  if (!pareceUmPdf(bytes) || marcando >= NO_MAXIMO_JUNTAS) return null;
  marcando += 1;
  try {
    return await new Promise<Uint8Array | null>((resolve) => {
      let trabalhador: Worker;
      try {
        trabalhador = new Worker(CODIGO_DO_TRABALHADOR, {
          eval: true,
          workerData: { bytes, texto: paraWinAnsi(linha).slice(0, 220) },
          resourceLimits: { maxOldGenerationSizeMb: 512 },
        });
      } catch {
        resolve(null);
        return;
      }
      let acabou = false;
      const terminar = (resultado: Uint8Array | null) => {
        if (acabou) return;
        acabou = true;
        clearTimeout(relogio);
        void trabalhador.terminate();
        resolve(resultado);
      };
      const relogio = setTimeout(() => terminar(null), TEMPO_MAXIMO_MS);
      trabalhador.once("message", (mensagem: { ok?: boolean; bytes?: Uint8Array }) => {
        terminar(mensagem?.ok && mensagem.bytes ? new Uint8Array(mensagem.bytes) : null);
      });
      trabalhador.once("error", () => terminar(null));
      trabalhador.once("exit", () => terminar(null));
    });
  } finally {
    marcando -= 1;
  }
}
