// OrbitCosmo wiki export — a minimal store-only (no compression) ZIP writer so
// the wiki can leave as ordinary .md files in one download. Pure byte
// layout per APPNOTE.TXT: local file headers + data, then a central
// directory, then the end record. No vendor deps, Node-testable.

(function (root) {
  "use strict";

  // CRC-32 (IEEE), table-driven.
  const CRC_TABLE = (() => {
    const t = new Uint32Array(256);
    for (let n = 0; n < 256; n++) {
      let c = n;
      for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
      t[n] = c >>> 0;
    }
    return t;
  })();

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

  function encodeUtf8(s) {
    if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s);
    return new Uint8Array(Buffer.from(s, "utf8")); // Node test path
  }

  // files: [{ name, data: string | Uint8Array }] → Uint8Array of the archive.
  function buildZip(files) {
    const chunks = [];
    const central = [];
    let offset = 0;

    function u16(v) { return [v & 0xff, (v >>> 8) & 0xff]; }
    function u32(v) { return [v & 0xff, (v >>> 8) & 0xff, (v >>> 16) & 0xff, (v >>> 24) & 0xff]; }

    for (const f of files) {
      const nameBytes = encodeUtf8(f.name);
      const data = typeof f.data === "string" ? encodeUtf8(f.data) : f.data;
      const crc = crc32(data);
      // Local file header: sig, version 20, flags 0x0800 (UTF-8 names),
      // method 0 (store), dos time/date 0, crc, sizes, name length.
      const local = new Uint8Array([
        ...u32(0x04034b50), ...u16(20), ...u16(0x0800), ...u16(0),
        ...u16(0), ...u16(0), ...u32(crc), ...u32(data.length), ...u32(data.length),
        ...u16(nameBytes.length), ...u16(0),
      ]);
      chunks.push(local, nameBytes, data);
      central.push({ nameBytes, crc, size: data.length, offset });
      offset += local.length + nameBytes.length + data.length;
    }

    const centralStart = offset;
    for (const e of central) {
      const hdr = new Uint8Array([
        ...u32(0x02014b50), ...u16(20), ...u16(20), ...u16(0x0800), ...u16(0),
        ...u16(0), ...u16(0), ...u32(e.crc), ...u32(e.size), ...u32(e.size),
        ...u16(e.nameBytes.length), ...u16(0), ...u16(0), ...u16(0), ...u16(0),
        ...u32(0), ...u32(e.offset),
      ]);
      chunks.push(hdr, e.nameBytes);
      offset += hdr.length + e.nameBytes.length;
    }

    const end = new Uint8Array([
      ...u32(0x06054b50), ...u16(0), ...u16(0),
      ...u16(central.length), ...u16(central.length),
      ...u32(offset - centralStart), ...u32(centralStart), ...u16(0),
    ]);
    chunks.push(end);

    const total = offset + end.length;
    const out = new Uint8Array(total);
    let pos = 0;
    for (const c of chunks) { out.set(c, pos); pos += c.length; }
    return out;
  }

  // Wiki pages → zip entries. Slug is already filesystem-safe ([a-z0-9-]).
  function wikiToZip(pages) {
    return buildZip(pages.map((p) => ({ name: `${p.slug}.md`, data: p.markdown })));
  }

  const api = { crc32, buildZip, wikiToZip };

  // Dual export: CommonJS for Node tests, global attach for extension pages.
  if (typeof module !== "undefined" && module.exports) {
    module.exports = api;
  } else {
    Object.assign(root, api);
  }
})(typeof globalThis !== "undefined" ? globalThis : self);
