class MiniZip {
    constructor() {
        this.files = [];
    }

    addFile(name, buffer) {
        this.files.push({
            name,
            data: new Uint8Array(buffer)
        });
    }

    generate() {
        let parts = [];
        let central = [];
        let offset = 0;

        const now = new Date();
        const dostime =
            (now.getHours() << 11) |
            (now.getMinutes() << 5) |
            (now.getSeconds() >> 1);

        const dosdate =
            ((now.getFullYear() - 1980) << 9) |
            ((now.getMonth() + 1) << 5) |
            now.getDate();

        for (const file of this.files) {
            const nameBytes = new TextEncoder().encode(file.name);
            const crc = this.crc32(file.data);
            const size = file.data.length;

            // ===== LOCAL FILE HEADER (30 байт) =====
            const local = new ArrayBuffer(30 + nameBytes.length);
            const v = new DataView(local);

            v.setUint32(0, 0x04034b50, true); // PK\03\04
            v.setUint16(4, 20, true);        // version
            v.setUint16(6, 0, true);         // flags
            v.setUint16(8, 0, true);         // store
            v.setUint16(10, dostime, true);
            v.setUint16(12, dosdate, true);
            v.setUint32(14, crc, true);
            v.setUint32(18, size, true);
            v.setUint32(22, size, true);
            v.setUint16(26, nameBytes.length, true);
            v.setUint16(28, 0, true);

            new Uint8Array(local, 30).set(nameBytes);

            parts.push(new Uint8Array(local));
            parts.push(file.data);

            // ===== CENTRAL DIRECTORY (46 байт) =====
            const centralBuf = new ArrayBuffer(46 + nameBytes.length);
            const c = new DataView(centralBuf);

            c.setUint32(0, 0x02014b50, true); // PK\01\02
            c.setUint16(4, 20, true);
            c.setUint16(6, 20, true);
            c.setUint16(8, 0, true);
            c.setUint16(10, 0, true);
            c.setUint16(12, dostime, true);
            c.setUint16(14, dosdate, true);
            c.setUint32(16, crc, true);
            c.setUint32(20, size, true);
            c.setUint32(24, size, true);
            c.setUint16(28, nameBytes.length, true);
            c.setUint16(30, 0, true);
            c.setUint16(32, 0, true);
            c.setUint16(34, 0, true);
            c.setUint16(36, 0, true);
            c.setUint32(38, 0, true);
            c.setUint32(42, offset, true);

            new Uint8Array(centralBuf, 46).set(nameBytes);
            central.push(new Uint8Array(centralBuf));

            offset += local.byteLength + file.data.length;
        }

        const centralOffset = offset;
        for (const c of central) offset += c.length;

        // ===== END OF CENTRAL DIRECTORY =====
        const end = new ArrayBuffer(22);
        const e = new DataView(end);

        e.setUint32(0, 0x06054b50, true);
        e.setUint16(4, 0, true);
        e.setUint16(6, 0, true);
        e.setUint16(8, this.files.length, true);
        e.setUint16(10, this.files.length, true);
        e.setUint32(12, offset - centralOffset, true);
        e.setUint32(16, centralOffset, true);
        e.setUint16(20, 0, true);

        return new Blob([...parts, ...central, new Uint8Array(end)], {
            type: "application/zip"
        });
    }

    crc32(data) {
        let crc = 0xFFFFFFFF;
        for (let b of data) {
            crc ^= b;
            for (let i = 0; i < 8; i++)
                crc = (crc >>> 1) ^ (0xEDB88320 & -(crc & 1));
        }
        return (crc ^ 0xFFFFFFFF) >>> 0;
    }
}