import type { Page } from "puppeteer";

import { abrirChrome, ChromeAusente } from "@/lib/catalogo-pdf/servidor/imprimir";

import { pedidoDoDocumentoPermitido } from "./loja-e-documentos-folha";

/*
 * Pedido e orçamento em PDF, com o mesmo Chrome do catálogo em PDF
 * (lib/catalogo-pdf/servidor/imprimir.ts). Duas entradas: uma página da loja
 * (o link público do orçamento, que já tem a "folha limpa" e o estilo de
 * impressão) ou um HTML pronto (a folha de loja-e-documentos-folha.ts).
 */

export class PaginaNaoAbriu extends Error {}
export { ChromeAusente };

const TEMPO_MS = 90_000;

async function protegerPagina(pagina: Page, origem: string | null) {
  await pagina.setRequestInterception(true);
  pagina.on("request", (pedido) => {
    if (pedidoDoDocumentoPermitido(pedido.url(), pedido.method(), origem)) pedido.continue().catch(() => undefined);
    else pedido.abort().catch(() => undefined);
  });
  await pagina.setViewport({ width: 1000, height: 1300, deviceScaleFactor: 1 });
}

async function esperarImagens(pagina: Page) {
  await pagina.waitForNetworkIdle({ idleTime: 500, timeout: 20_000 }).catch(() => undefined);
  await pagina
    .evaluate(async () => {
      await document.fonts.ready;
      await Promise.all(
        [...document.images].map((img) =>
          img.complete
            ? null
            : new Promise((pronto) => {
                img.onload = img.onerror = pronto;
              })
        )
      );
    })
    .catch(() => undefined);
}

async function comLimite<T>(trabalho: Promise<T>): Promise<T> {
  let relogio: ReturnType<typeof setTimeout> | undefined;
  try {
    return await Promise.race([
      trabalho,
      new Promise<never>((_, falhar) => {
        relogio = setTimeout(() => falhar(new PaginaNaoAbriu("demorou")), TEMPO_MS);
      }),
    ]);
  } finally {
    if (relogio) clearTimeout(relogio);
  }
}

/**
 * Imprime uma página da loja. Tenta os endereços em ordem (a loja pode
 * atender em mais de um: o interno da máquina e o público) e fica com o
 * primeiro que abre o documento. `seletor` é o que precisa aparecer para a
 * página contar como carregada. Nenhum abriu (404, link desligado, loja fora
 * do ar) lança PaginaNaoAbriu.
 */
export async function imprimirPaginaDaLoja(enderecos: string[], seletor: string): Promise<Buffer> {
  const navegador = await abrirChrome();
  try {
    return await comLimite(
      (async () => {
        for (const endereco of enderecos) {
          const pagina = await navegador.newPage();
          try {
            await protegerPagina(pagina, new URL(endereco).origin);
            const resposta = await pagina.goto(endereco, { waitUntil: "load", timeout: 30_000 }).catch(() => null);
            if (!resposta || !resposta.ok()) continue;
            const achou = await pagina.waitForSelector(seletor, { timeout: 20_000 }).catch(() => null);
            if (!achou) continue;
            await esperarImagens(pagina);
            await pagina.emulateMediaType("print");
            const pdf = await pagina.pdf({ format: "A4", preferCSSPageSize: true, printBackground: true, timeout: 45_000 });
            return Buffer.from(pdf);
          } finally {
            await pagina.close().catch(() => undefined);
          }
        }
        throw new PaginaNaoAbriu("nenhum endereço abriu o documento");
      })()
    );
  } finally {
    await navegador.close().catch(() => undefined);
  }
}

/** Imprime um HTML pronto (a folha montada aqui). */
export async function imprimirHtml(html: string): Promise<Buffer> {
  const navegador = await abrirChrome();
  try {
    return await comLimite(
      (async () => {
        const pagina = await navegador.newPage();
        await protegerPagina(pagina, null);
        await pagina.setContent(html, { waitUntil: "load", timeout: 45_000 });
        await esperarImagens(pagina);
        const pdf = await pagina.pdf({ format: "A4", preferCSSPageSize: true, printBackground: true, timeout: 45_000 });
        return Buffer.from(pdf);
      })()
    );
  } finally {
    await navegador.close().catch(() => undefined);
  }
}
