import { inflateRawSync } from 'zlib';

const EOCD_SIGNATURE = 0x06054b50;
const CENTRAL_SIGNATURE = 0x02014b50;
const LOCAL_SIGNATURE = 0x04034b50;

function normalizeEntryName(name) {
  const normalized = String(name || '').replace(/\\/g, '/').replace(/^\/+/, '');
  if (!normalized || normalized.endsWith('/')) return normalized;
  if (normalized.includes('\0') || normalized.split('/').some(part => part === '..')) {
    throw new Error(`Unsicherer ZIP-Pfad: ${name}`);
  }
  return normalized;
}

function findEndOfCentralDirectory(buffer) {
  const min = Math.max(0, buffer.length - 0xffff - 22);
  for (let offset = buffer.length - 22; offset >= min; offset--) {
    if (buffer.readUInt32LE(offset) === EOCD_SIGNATURE) return offset;
  }
  throw new Error('ZIP-Endverzeichnis wurde nicht gefunden.');
}

export function readZipEntries(buffer, { maxEntries = 10000, maxUncompressedBytes = 8 * 1024 * 1024 * 1024 } = {}) {
  if (!Buffer.isBuffer(buffer)) buffer = Buffer.from(buffer || []);
  const eocd = findEndOfCentralDirectory(buffer);
  const entryCount = buffer.readUInt16LE(eocd + 10);
  const centralSize = buffer.readUInt32LE(eocd + 12);
  const centralOffset = buffer.readUInt32LE(eocd + 16);
  if (entryCount > maxEntries) throw new Error(`ZIP enthält zu viele Dateien (${entryCount}).`);
  if (centralOffset + centralSize > buffer.length) throw new Error('ZIP-Zentralverzeichnis ist beschädigt.');

  const entries = [];
  let offset = centralOffset;
  let totalUncompressed = 0;
  for (let index = 0; index < entryCount; index++) {
    if (buffer.readUInt32LE(offset) !== CENTRAL_SIGNATURE) throw new Error('ZIP-Zentralverzeichnis ist ungültig.');
    const flags = buffer.readUInt16LE(offset + 8);
    const method = buffer.readUInt16LE(offset + 10);
    const crc = buffer.readUInt32LE(offset + 16);
    const compressedSize = buffer.readUInt32LE(offset + 20);
    const uncompressedSize = buffer.readUInt32LE(offset + 24);
    const nameLength = buffer.readUInt16LE(offset + 28);
    const extraLength = buffer.readUInt16LE(offset + 30);
    const commentLength = buffer.readUInt16LE(offset + 32);
    const localOffset = buffer.readUInt32LE(offset + 42);
    const nameStart = offset + 46;
    const name = normalizeEntryName(buffer.subarray(nameStart, nameStart + nameLength).toString((flags & 0x0800) ? 'utf8' : 'utf8'));
    totalUncompressed += uncompressedSize;
    if (totalUncompressed > maxUncompressedBytes) throw new Error('ZIP ist nach dem Entpacken zu groß.');
    entries.push({ name, method, crc, compressedSize, uncompressedSize, localOffset, directory: name.endsWith('/') });
    offset = nameStart + nameLength + extraLength + commentLength;
  }
  return entries;
}

export function extractZipEntry(buffer, entry) {
  if (entry.directory) return Buffer.alloc(0);
  const offset = entry.localOffset;
  if (buffer.readUInt32LE(offset) !== LOCAL_SIGNATURE) throw new Error(`Lokaler ZIP-Header fehlt: ${entry.name}`);
  const nameLength = buffer.readUInt16LE(offset + 26);
  const extraLength = buffer.readUInt16LE(offset + 28);
  const dataStart = offset + 30 + nameLength + extraLength;
  const compressed = buffer.subarray(dataStart, dataStart + entry.compressedSize);
  let data;
  if (entry.method === 0) data = Buffer.from(compressed);
  else if (entry.method === 8) data = inflateRawSync(compressed);
  else throw new Error(`Nicht unterstützte ZIP-Kompression (${entry.method}) für ${entry.name}.`);
  if (data.length !== entry.uncompressedSize) throw new Error(`ZIP-Größe stimmt nicht: ${entry.name}`);
  return data;
}

export function readZip(buffer, options) {
  const entries = readZipEntries(buffer, options);
  const files = new Map();
  for (const entry of entries) if (!entry.directory) files.set(entry.name, extractZipEntry(buffer, entry));
  return { entries, files };
}
