/*
 * Um .zip simples (sem compressão: PNG e JPG já vêm comprimidos), feito no
 * navegador, para baixar o carrossel inteiro de uma vez.
 */

const TABELA = (() => {
  const t = new Uint32Array(256);
  for (let i = 0; i < 256; i++) {
    let c = i;
    for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
    t[i] = c >>> 0;
  }
  return t;
})();

function crc32(dados: Uint8Array) {
  let c = 0xffffffff;
  for (let i = 0; i < dados.length; i++) c = TABELA[(c ^ dados[i]) & 0xff] ^ (c >>> 8);
  return (c ^ 0xffffffff) >>> 0;
}

export function zipar(arquivos: Array<{ nome: string; dados: Uint8Array }>): Blob {
  const codificar = new TextEncoder();
  const partes: BlobPart[] = [];
  const central: Uint8Array[] = [];
  let posicao = 0;
  /* Data e hora do DOS (só para os programas mostrarem algo). */
  const agora = new Date();
  const hora = (agora.getHours() << 11) | (agora.getMinutes() << 5) | (agora.getSeconds() >> 1);
  const dia = ((agora.getFullYear() - 1980) << 9) | ((agora.getMonth() + 1) << 5) | agora.getDate();
  for (const arquivo of arquivos) {
    const nome = codificar.encode(arquivo.nome);
    const crc = crc32(arquivo.dados);
    const tamanho = arquivo.dados.length;
    const local = new DataView(new ArrayBuffer(30));
    local.setUint32(0, 0x04034b50, true);
    local.setUint16(4, 20, true);
    local.setUint16(6, 0x0800, true);
    local.setUint16(8, 0, true);
    local.setUint16(10, hora, true);
    local.setUint16(12, dia, true);
    local.setUint32(14, crc, true);
    local.setUint32(18, tamanho, true);
    local.setUint32(22, tamanho, true);
    local.setUint16(26, nome.length, true);
    local.setUint16(28, 0, true);
    partes.push(local.buffer as ArrayBuffer, nome as BlobPart, arquivo.dados as BlobPart);
    const c = new DataView(new ArrayBuffer(46));
    c.setUint32(0, 0x02014b50, true);
    c.setUint16(4, 20, true);
    c.setUint16(6, 20, true);
    c.setUint16(8, 0x0800, true);
    c.setUint16(10, 0, true);
    c.setUint16(12, hora, true);
    c.setUint16(14, dia, true);
    c.setUint32(16, crc, true);
    c.setUint32(20, tamanho, true);
    c.setUint32(24, tamanho, true);
    c.setUint16(28, nome.length, true);
    c.setUint32(42, posicao, true);
    const registro = new Uint8Array(46 + nome.length);
    registro.set(new Uint8Array(c.buffer), 0);
    registro.set(nome, 46);
    central.push(registro);
    posicao += 30 + nome.length + tamanho;
  }
  const tamanhoCentral = central.reduce((s, r) => s + r.length, 0);
  const fim = new DataView(new ArrayBuffer(22));
  fim.setUint32(0, 0x06054b50, true);
  fim.setUint16(8, arquivos.length, true);
  fim.setUint16(10, arquivos.length, true);
  fim.setUint32(12, tamanhoCentral, true);
  fim.setUint32(16, posicao, true);
  return new Blob([...partes, ...(central as BlobPart[]), fim.buffer as ArrayBuffer], { type: "application/zip" });
}
