/**
 * O protocolo de entrega — o papel que o cliente assina ao receber.
 *
 * Irmão de `order-label-print.ts`: mesma forma (monta HTML, imprime por iframe
 * escondido), assunto diferente. A etiqueta vai COLADA no pacote e diz para
 * onde ele vai; o protocolo vai NA MÃO de quem entrega e volta assinado.
 *
 * TRÊS DECISÕES QUE EXPLICAM O FORMATO INTEIRO:
 *
 *   1. NENHUM VALOR. Nem unitário, nem total, nem frete. Quem recebe a
 *      mercadoria muitas vezes não é quem comprou — é o porteiro, a recepção,
 *      um funcionário. O documento prova entrega, não preço.
 *
 *   2. A FOLHA SAI DEITADA, com dois protocolos EM PÉ lado a lado. Uma A4 em
 *      paisagem cortada ao meio dá duas A5 em retrato de 148,5 × 210 mm, que é
 *      o tamanho de recibo que todo mundo já conhece.
 *
 *   3. O VÃO ENTRE O RÓTULO E A LINHA é onde a pessoa escreve. Rótulo colado na
 *      linha é o erro clássico do formulário impresso: fica bonito na tela e
 *      não dá para assinar no papel.
 *
 * A medida de cada item sai de `quantidadeDoItem`, a mesma conta que a tela do
 * pedido e o PDF usam. Um item por m² tem duas quantidades — peças e área — e
 * este arquivo não tem permissão de inventar uma terceira.
 */

/* Extensão explícita: é o que o runner de teste do Node exige para resolver o
   módulo, e quem reparte itens em folha assinada precisa ser testável. */
import { quantidadeDoItem, type ItemMedido } from "./quantidade-do-item.ts";

export type ProtocoloEmpresa = {
  nome?: string | null;
  logoUrl?: string | null;
  cnpj?: string | null;
  telefone?: string | null;
  endereco?: string | null;
};

export type ProtocoloCliente = {
  nome?: string | null;
  empresa?: string | null;
  documento?: string | null;
  telefone?: string | null;
  endereco?: string | null;
};

export type ProtocoloItem = ItemMedido & {
  titulo?: string | null;
  /* "Pingente: com pingente" — a variação escolhida, que é o que distingue
     fisicamente uma peça da outra na hora de conferir. */
  variacao?: string | null;
};

export type ProtocoloPedido = {
  numero?: number | string | null;
  id?: string | null;
  empresa: ProtocoloEmpresa;
  cliente: ProtocoloCliente;
  itens: ProtocoloItem[];
};

/**
 * O que sai da impressora.
 *
 * "duas-vias" gasta uma folha por pedido e devolve uma assinada para o
 * arquivo. As outras duas gastam MEIA folha por pedido, porque emendam dois
 * pedidos no mesmo papel — é o que se usa quando saem trinta entregas e só
 * interessa o canhoto que vai junto da mercadoria.
 */
export type ModoDoProtocolo = "duas-vias" | "so-cliente" | "so-empresa";

/**
 * Quanto cabe numa via.
 *
 * Medido em papel, não calculado: 12 itens simples enchem a folha até a linha
 * das assinaturas. Item com variação ocupa uma linha a mais, e por isso pesa
 * mais que um. Errar para menos é barato (sobra branco); errar para mais corta
 * item de folha assinada, que é inaceitável.
 */
export const ORCAMENTO_DA_VIA = 12;

function pesoDoItem(item: ProtocoloItem) {
  const q = quantidadeDoItem(item);
  const temSubLinha = Boolean(item.variacao) || Boolean(q.medida);
  return temSubLinha ? 1.4 : 1;
}

export type PaginaDoProtocolo = {
  itens: ProtocoloItem[];
  folha: number;
  folhas: number;
};

/**
 * Reparte os itens em folhas, sem nunca cortar um item ao meio.
 *
 * Pedido vazio devolve UMA página vazia, e não nenhuma: o protocolo de um
 * pedido sem item ainda precisa existir para alguém assinar o recebimento.
 */
export function paginarItens(itens: ProtocoloItem[], orcamento = ORCAMENTO_DA_VIA): PaginaDoProtocolo[] {
  const paginas: ProtocoloItem[][] = [];
  let atual: ProtocoloItem[] = [];
  let peso = 0;

  for (const item of itens) {
    const custo = pesoDoItem(item);
    if (atual.length > 0 && peso + custo > orcamento) {
      paginas.push(atual);
      atual = [];
      peso = 0;
    }
    atual.push(item);
    peso += custo;
  }
  if (atual.length > 0 || paginas.length === 0) paginas.push(atual);

  return paginas.map((lista, i) => ({ itens: lista, folha: i + 1, folhas: paginas.length }));
}

export type ViaDaFolha = {
  pedido: ProtocoloPedido;
  pagina: PaginaDoProtocolo;
  via: "empresa" | "cliente";
};

export type FolhaDoProtocolo = {
  esquerda: ViaDaFolha | null;
  direita: ViaDaFolha | null;
};

/**
 * Monta as folhas a partir dos pedidos.
 *
 * Em "duas-vias" cada folha traz a MESMA página duas vezes: o cliente assina as
 * duas, fica com uma e a outra volta para o arquivo. Um pedido que ocupa duas
 * páginas gera duas folhas, e cada parte sai com as suas duas.
 *
 * Em "varios-pedidos" as páginas entram em fila e a folha só emenda o que
 * couber. Sobra meia folha em branco quando o total é ímpar, e isso é melhor
 * que espremer o começo de outro pedido no mesmo papel que alguém vai cortar.
 */
export function montarFolhas(pedidos: ProtocoloPedido[], modo: ModoDoProtocolo): FolhaDoProtocolo[] {
  if (modo === "duas-vias") {
    const folhas: FolhaDoProtocolo[] = [];
    for (const pedido of pedidos) {
      for (const pagina of paginarItens(pedido.itens)) {
        folhas.push({
          esquerda: { pedido, pagina, via: "empresa" },
          direita: { pedido, pagina, via: "cliente" },
        });
      }
    }
    return folhas;
  }

  const via = modo === "so-empresa" ? "empresa" : "cliente";
  const fila: ViaDaFolha[] = [];
  for (const pedido of pedidos) {
    for (const pagina of paginarItens(pedido.itens)) {
      fila.push({ pedido, pagina, via });
    }
  }

  const folhas: FolhaDoProtocolo[] = [];
  for (let i = 0; i < fila.length; i += 2) {
    folhas.push({ esquerda: fila[i], direita: fila[i + 1] ?? null });
  }
  return folhas;
}

/* --------------------------------------------------------------- o desenho */

function escapar(valor: unknown) {
  return String(valor ?? "")
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

function texto(valor: unknown) {
  return String(valor ?? "").trim();
}

function primeiroQueTem(...valores: Array<unknown>) {
  for (const v of valores) {
    const t = texto(v);
    if (t) return t;
  }
  return "";
}

function documentoFormatado(valor: unknown) {
  const digitos = texto(valor).replace(/\D/g, "");
  if (digitos.length === 11) {
    return `CPF ${digitos.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4")}`;
  }
  if (digitos.length === 14) {
    return `CNPJ ${digitos.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, "$1.$2.$3/$4-$5")}`;
  }
  return texto(valor);
}

/* ------------------------------------------- o que vem do banco, em uma linha */

function juntar(...partes: Array<unknown>) {
  return partes.map((v) => texto(v)).filter(Boolean);
}

/**
 * O endereço do cliente numa linha só.
 *
 * Mora aqui, e não na tela, porque o protocolo de um pedido e o de trinta
 * pedidos precisam escrever o MESMO endereço. Duas cópias desta função
 * divergem na primeira vez que alguém corrigir uma delas.
 */
export function enderecoDoCliente(c: Record<string, unknown> | null | undefined) {
  if (!c) return "";
  const rua = juntar(c.address_street, c.address_number).join(", ");
  const primeira = juntar(rua, c.address_complement, c.address_neighborhood).join(" — ");
  const cidade = juntar(c.address_city, c.address_state).join(" / ");
  const cep = texto(c.address_zip) ? `CEP ${texto(c.address_zip)}` : "";
  return juntar(primeira, juntar(cidade, cep).join(" · ")).join(" · ");
}

export function enderecoDaEmpresa(p: Record<string, unknown> | null | undefined) {
  if (!p) return "";
  const rua = juntar(p.address_street, p.address_number).join(", ");
  const cidade = juntar(p.address_city, p.address_state).join(" / ");
  return juntar(rua, p.address_complement, p.address_neighborhood, cidade).join(" · ");
}

/**
 * A variação escolhida, em uma linha: "Pingente: com pingente".
 *
 * É o que distingue fisicamente uma peça da outra na hora de conferir a
 * entrega — duas canecas iguais com alças de cor diferente são dois itens
 * diferentes para quem está com elas na mão.
 */
export function variacaoDoItem(bruto: unknown): string | null {
  const dados = bruto && typeof bruto === "object" ? (bruto as Record<string, unknown>) : null;
  const opcoes = Array.isArray(dados?.options) ? (dados.options as Array<Record<string, unknown>>) : [];
  const partes = opcoes
    .map((o) => {
      const valor = texto(o?.label);
      if (!valor) return "";
      const atributo = texto(o?.attribute_name);
      return atributo ? `${atributo}: ${valor.toLowerCase()}` : valor;
    })
    .filter(Boolean);
  return partes.length ? partes.join(" · ") : null;
}

function linhaDoItem(item: ProtocoloItem) {
  const q = quantidadeDoItem(item);
  const nome = primeiroQueTem(item.titulo, "Item");

  const medida = q.medida
    ? `${escapar(q.medida)}${q.areaPorPeca ? `<span class="cada">${escapar(q.areaPorPeca)}/cada</span>` : ""}`
    : `<span class="vazio">&mdash;</span>`;

  const quantidade = `${escapar(q.pecasTexto)}${
    q.total ? `<span class="total">${escapar(q.total.replace(" no total", " total"))}</span>` : ""
  }`;

  return `
    <div class="grade item">
      <div class="nome">${escapar(nome)}${
        item.variacao ? `<div class="detalhe">${escapar(item.variacao)}</div>` : ""
      }</div>
      <div class="medida">${medida}</div>
      <div class="qtd">${quantidade}</div>
    </div>`;
}

function blocoDaVia(via: ViaDaFolha | null, mostrarCorte: boolean) {
  if (!via) return `<div class="via vazia"></div>`;

  const { pedido, pagina } = via;
  const empresa = pedido.empresa || {};
  const cliente = pedido.cliente || {};

  const numero = primeiroQueTem(pedido.numero, texto(pedido.id).slice(0, 8));
  const nomeDoCliente = primeiroQueTem(cliente.empresa, cliente.nome, "Cliente");
  const documento = documentoFormatado(cliente.documento);
  const endereco = texto(cliente.endereco);

  const logo = texto(empresa.logoUrl)
    ? `<img class="marca" src="${escapar(empresa.logoUrl)}" alt="">`
    : `<div class="marca sem-logo"></div>`;

  const contato = [texto(empresa.cnpj) ? documentoFormatado(empresa.cnpj) : "", texto(empresa.telefone)]
    .filter(Boolean)
    .join(" · ");

  /* As assinaturas só na última folha da via. Assinar a folha 1 de 2 é assinar
     metade do que se está recebendo. */
  const ehUltima = pagina.folha === pagina.folhas;
  const contador = pagina.folhas > 1 ? `Folha ${pagina.folha} de ${pagina.folhas}` : "";

  const rodape = ehUltima
    ? `
      <div class="rodape">
        <div class="declaro">Declaro que recebi os itens listados acima, conferidos e em boas condições.</div>
        <div class="assinaturas">
          <div class="par">
            <div class="campo"><div class="legenda">Entregue por</div><div class="risco"></div></div>
            <div class="campo"><div class="legenda">Recebido por &mdash; assinatura</div><div class="risco"></div></div>
          </div>
          <div class="par">
            <div class="campo escrito"><div class="legenda">Nome legível e documento</div><div class="risco"></div></div>
            <div class="campo escrito data"><div class="legenda">Data e hora</div><div class="risco"></div></div>
          </div>
        </div>
      </div>`
    : `
      <div class="rodape">
        <div class="declaro">A relação dos itens continua na folha seguinte. As assinaturas ficam na última.</div>
      </div>`;

  return `
    <div class="via">
      ${mostrarCorte ? `<span class="corte">corte aqui</span>` : ""}
      <div class="topo">
        <div class="assinante">
          ${logo}
          <div class="empresa">
            <div class="nome">${escapar(primeiroQueTem(empresa.nome, "Empresa"))}</div>
            <div class="meta">
              ${contato ? `${escapar(contato)}<br>` : ""}
              ${escapar(texto(empresa.endereco))}
            </div>
          </div>
        </div>
        <div class="selo">
          <div class="titulo">Protocolo<br>de entrega</div>
          <div class="numero">Pedido ${escapar(numero)}</div>
          <div class="via-nome">Via ${via.via === "empresa" ? "da empresa" : "do cliente"}${
            contador ? `<br>${escapar(contador)}` : ""
          }</div>
        </div>
      </div>

      <div class="cliente">
        <div class="coluna quem">
          <div class="secao">Entregue a</div>
          <div class="linha">${escapar(nomeDoCliente)}</div>
          ${documento ? `<div class="sub">${escapar(documento)}</div>` : ""}
        </div>
        <div class="coluna onde">
          <div class="secao">Endereço</div>
          <div class="sub">${endereco ? escapar(endereco) : "&mdash;"}</div>
        </div>
      </div>

      <div class="lista">
        <div class="grade chapeu">
          <span class="secao">Itens entregues</span>
          <span class="secao">Medida</span>
          <span class="secao direita">Qtd.</span>
        </div>
        ${pagina.itens.map(linhaDoItem).join("")}
        <div class="fecho"></div>
      </div>

      ${rodape}
    </div>`;
}

const ESTILO = `
  @page { size: A4 landscape; margin: 0; }
  * { box-sizing: border-box; }
  html, body { margin: 0; padding: 0; background: #fff; }
  body { font-family: "Archivo Narrow", "Arial Narrow", Arial, Helvetica, sans-serif; color: #14120f; }

  .folha {
    width: 297mm; height: 210mm; display: grid; grid-template-columns: 148.5mm 148.5mm;
    page-break-after: always; break-after: page; overflow: hidden;
  }
  .folha:last-child { page-break-after: auto; break-after: auto; }

  .via { padding: 9mm 8mm; display: flex; flex-direction: column; height: 210mm; overflow: hidden; position: relative; }
  .via + .via { border-left: 1px dashed #cfc9bd; }
  .via.vazia { border-left: 1px dashed #cfc9bd; }

  .corte {
    position: absolute; left: -3.4mm; top: 50%; transform: translateY(-50%);
    writing-mode: vertical-rl; font-size: 5.5pt; letter-spacing: 0.14em;
    text-transform: uppercase; color: #8b857a; background: #fff; padding: 2.5mm 0;
  }

  .topo { display: flex; align-items: flex-start; justify-content: space-between; gap: 5mm; }
  .assinante { display: flex; gap: 3mm; align-items: flex-start; min-width: 0; }
  .marca { width: 14mm; height: 14mm; flex: 0 0 14mm; object-fit: contain; }
  .marca.sem-logo { border: 0.3mm solid #cfc9bd; }
  .empresa { min-width: 0; }
  .empresa .nome { font-size: 9pt; font-weight: 700; line-height: 1.2; }
  .empresa .meta { font-size: 6.6pt; line-height: 1.5; color: #5d574d; margin-top: 0.8mm; }

  .selo { text-align: right; flex-shrink: 0; }
  .selo .titulo { font-size: 7.2pt; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; line-height: 1.2; }
  .selo .numero { font-size: 19pt; font-weight: 700; line-height: 1.05; margin-top: 0.5mm; font-variant-numeric: tabular-nums; }
  .selo .via-nome { font-size: 6.4pt; text-transform: uppercase; letter-spacing: 0.1em; color: #5d574d; line-height: 1.3; margin-top: 0.8mm; }

  .secao { font-size: 6pt; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: #8b857a; }

  .cliente { margin-top: 4mm; padding-top: 2.5mm; border-top: 0.5mm solid #14120f; display: flex; gap: 6mm; }
  .cliente .coluna.quem { flex: 0 0 48mm; min-width: 0; }
  .cliente .coluna.onde { flex: 1; min-width: 0; }
  .cliente .linha { font-size: 8.4pt; font-weight: 600; margin-top: 0.8mm; line-height: 1.25; }
  .cliente .sub { font-size: 6.8pt; color: #5d574d; line-height: 1.45; margin-top: 0.4mm; }

  .lista { margin-top: 4mm; flex: 1; display: flex; flex-direction: column; min-height: 0; }
  .grade { display: grid; grid-template-columns: 1fr 31mm 25mm; gap: 0 4mm; align-items: baseline; }
  .chapeu { padding-bottom: 1.4mm; }
  .chapeu .direita { text-align: right; }

  .item { padding: 1.7mm 0; border-top: 0.2mm solid #cfc9bd; }
  .item .nome { font-size: 8.4pt; line-height: 1.2; min-width: 0; }
  .item .detalhe { font-size: 6.8pt; color: #5d574d; line-height: 1.35; margin-top: 0.4mm; }
  .item .medida { font-size: 7.4pt; line-height: 1.25; font-variant-numeric: tabular-nums; }
  .item .medida .vazio { color: #8b857a; }
  .item .qtd { font-size: 8.4pt; font-weight: 600; text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
  .item .cada, .item .total {
    display: block; font-size: 6.6pt; font-weight: 400; color: #5d574d;
    margin-top: 0.4mm; font-variant-numeric: tabular-nums;
  }
  /* A linha de fechamento vem LOGO DEPOIS do ultimo item, e nao no pe da
     pagina. O branco que sobra fica visivelmente FORA da tabela: ninguem
     acrescenta uma linha num papel ja assinado e alega que estava la. */
  .fecho { border-top: 0.5mm solid #14120f; }

  .rodape { padding-top: 3mm; margin-top: auto; }
  .declaro { font-size: 6.8pt; line-height: 1.5; color: #5d574d; max-width: 118mm; }
  .assinaturas { margin-top: 6mm; display: flex; flex-direction: column; gap: 6mm; }
  .par { display: flex; gap: 6mm; }
  .campo { flex: 1; display: flex; flex-direction: column; min-width: 0; }
  .campo .legenda { font-size: 6pt; text-transform: uppercase; letter-spacing: 0.1em; color: #5d574d; line-height: 1.2; }
  .campo .risco { margin-top: 10mm; border-bottom: 0.3mm solid #14120f; }
  .campo.escrito .risco { margin-top: 7mm; }
  .campo.data { flex: 0 0 42mm; }
`;

export function montarHtmlDoProtocolo(pedidos: ProtocoloPedido[], modo: ModoDoProtocolo = "duas-vias") {
  const folhas = montarFolhas(pedidos, modo);
  const titulo =
    pedidos.length === 1
      ? `Protocolo de entrega — pedido ${primeiroQueTem(pedidos[0]?.numero, "")}`
      : `Protocolos de entrega — ${pedidos.length} pedidos`;

  const corpo = folhas
    .map(
      (folha) => `
      <div class="folha">
        ${blocoDaVia(folha.esquerda, false)}
        ${blocoDaVia(folha.direita, true)}
      </div>`
    )
    .join("");

  return `<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>${escapar(titulo)}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo+Narrow:wght@400;600;700&display=swap">
<style>${ESTILO}</style>
</head>
<body>${corpo}</body>
</html>`;
}

/**
 * Imprime sem abrir aba.
 *
 * Mesma técnica da etiqueta: um iframe escondido recebe o documento e chama o
 * print dele. Abrir aba nova esbarra em bloqueador de pop-up e deixa lixo
 * aberto no navegador de quem imprime trinta protocolos por dia.
 */
export async function imprimirProtocolo(pedidos: ProtocoloPedido[], modo: ModoDoProtocolo = "duas-vias") {
  const iframe = document.createElement("iframe");
  iframe.setAttribute("aria-hidden", "true");
  iframe.style.position = "fixed";
  iframe.style.right = "0";
  iframe.style.bottom = "0";
  iframe.style.width = "0";
  iframe.style.height = "0";
  iframe.style.border = "0";
  iframe.style.opacity = "0";
  iframe.style.pointerEvents = "none";

  const limpar = () => {
    iframe.onload = null;
    if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
  };

  iframe.onload = () => {
    const janela = iframe.contentWindow;
    if (!janela) {
      limpar();
      return;
    }
    const aoTerminar = () => {
      janela.removeEventListener("afterprint", aoTerminar);
      limpar();
    };
    janela.addEventListener("afterprint", aoTerminar);
    /* A espera é a fonte carregando: imprimir antes disso sai em Arial e
       desalinha as colunas de número. */
    window.setTimeout(() => {
      janela.focus();
      janela.print();
    }, 400);
    window.setTimeout(limpar, 20000);
  };

  iframe.srcdoc = montarHtmlDoProtocolo(pedidos, modo);
  document.body.appendChild(iframe);
}

export function nomeDoArquivoDoProtocolo(pedidos: ProtocoloPedido[]) {
  const limpo = (v: string) =>
    v
      .normalize("NFD")
      .replace(/[̀-ͯ]/g, "")
      .replace(/[^a-zA-Z0-9]+/g, "-")
      .replace(/^-+|-+$/g, "")
      .toLowerCase();

  if (pedidos.length === 1) {
    const numero = primeiroQueTem(pedidos[0]?.numero, texto(pedidos[0]?.id).slice(0, 8));
    const cliente = primeiroQueTem(pedidos[0]?.cliente?.empresa, pedidos[0]?.cliente?.nome, "cliente");
    return `protocolo-entrega-${limpo(numero)}-${limpo(cliente)}.html`;
  }
  return `protocolos-entrega-${pedidos.length}-pedidos.html`;
}
