import QRCode from "qrcode";

function u32(buf: Uint8Array, offset: number, value: number) {
	buf[offset] = (value >>> 24) & 255;
	buf[offset + 1] = (value >>> 16) & 255;
	buf[offset + 2] = (value >>> 8) & 255;
	buf[offset + 3] = value & 255;
}

function crc32(data: Uint8Array) {
	let c = 0xffffffff;
	for (let i = 0; i < data.length; i++) {
		c ^= data[i];
		for (let k = 0; k < 8; k++) {
			c = (c >>> 1) ^ (c & 1 ? 0xedb88320 : 0);
		}
	}
	return (c ^ 0xffffffff) >>> 0;
}

function adler32(data: Uint8Array) {
	let a = 1;
	let b = 0;
	for (let i = 0; i < data.length; i++) {
		a = (a + data[i]) % 65521;
		b = (b + a) % 65521;
	}
	return ((b << 16) | a) >>> 0;
}

function chunk(type: string, data: Uint8Array) {
	const out = new Uint8Array(12 + data.length);
	u32(out, 0, data.length);
	out[4] = type.charCodeAt(0);
	out[5] = type.charCodeAt(1);
	out[6] = type.charCodeAt(2);
	out[7] = type.charCodeAt(3);
	out.set(data, 8);
	u32(out, 8 + data.length, crc32(out.subarray(4, 8 + data.length)));
	return out;
}

function concat(parts: Uint8Array[]) {
	const len = parts.reduce((sum, part) => sum + part.length, 0);
	const out = new Uint8Array(len);
	let offset = 0;
	for (const part of parts) {
		out.set(part, offset);
		offset += part.length;
	}
	return out;
}

function zlibStore(raw: Uint8Array) {
	const blocks: Uint8Array[] = [];
	let offset = 0;
	while (offset < raw.length) {
		const len = Math.min(65535, raw.length - offset);
		const last = offset + len >= raw.length;
		const block = new Uint8Array(5 + len);
		block[0] = last ? 1 : 0;
		block[1] = len & 255;
		block[2] = (len >>> 8) & 255;
		const nlen = (~len) & 0xffff;
		block[3] = nlen & 255;
		block[4] = (nlen >>> 8) & 255;
		block.set(raw.subarray(offset, offset + len), 5);
		blocks.push(block);
		offset += len;
	}
	const bodyLen = blocks.reduce((sum, block) => sum + block.length, 0);
	const out = new Uint8Array(2 + bodyLen + 4);
	out[0] = 0x78;
	out[1] = 0x01;
	let cursor = 2;
	for (const block of blocks) {
		out.set(block, cursor);
		cursor += block.length;
	}
	u32(out, cursor, adler32(raw));
	return out;
}

function bytesToBase64(bytes: Uint8Array) {
	const api = typeof uni === "undefined" ? undefined : uni;
	if (api && typeof api.arrayBufferToBase64 === "function") {
		const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
		return api.arrayBufferToBase64(buffer as ArrayBuffer);
	}
	const table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	let out = "";
	for (let i = 0; i < bytes.length; i += 3) {
		const a = bytes[i];
		const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
		const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
		const n = (a << 16) | (b << 8) | c;
		out += table[(n >> 18) & 63];
		out += table[(n >> 12) & 63];
		out += i + 1 < bytes.length ? table[(n >> 6) & 63] : "=";
		out += i + 2 < bytes.length ? table[n & 63] : "=";
	}
	return out;
}

/** 不依赖浏览器 canvas，直接画出可在小程序 image 中显示的 PNG。 */
export function createQrPng(text: string) {
	const qr = QRCode.create(text, { errorCorrectionLevel: "M" });
	const modules = qr.modules;
	const count = modules.size;
	const margin = 4;
	const scale = 8;
	const size = (count + margin * 2) * scale;
	const raw = new Uint8Array(size * (size + 1));
	let cursor = 0;
	for (let y = 0; y < size; y++) {
		raw[cursor++] = 0;
		const row = Math.floor(y / scale) - margin;
		for (let x = 0; x < size; x++) {
			const col = Math.floor(x / scale) - margin;
			const dark =
				row >= 0 && col >= 0 && row < count && col < count && !!modules.get(row, col);
			raw[cursor++] = dark ? 0 : 255;
		}
	}
	const ihdr = new Uint8Array(13);
	u32(ihdr, 0, size);
	u32(ihdr, 4, size);
	ihdr[8] = 8;
	const signature = Uint8Array.from([137, 80, 78, 71, 13, 10, 26, 10]);
	return concat([
		signature,
		chunk("IHDR", ihdr),
		chunk("IDAT", zlibStore(raw)),
		chunk("IEND", new Uint8Array(0)),
	]);
}

function fileName(text: string) {
	let hash = 0;
	for (let i = 0; i < text.length; i++) {
		hash = (hash * 31 + text.charCodeAt(i)) >>> 0;
	}
	return hash.toString(16);
}

function savePng(bytes: Uint8Array, text: string) {
	const base64 = bytesToBase64(bytes);
	const dataUrl = `data:image/png;base64,${base64}`;
	const root = (globalThis as any).wx?.env?.USER_DATA_PATH;
	if (!root || typeof uni === "undefined" || typeof uni.getFileSystemManager !== "function") {
		return Promise.resolve(dataUrl);
	}
	const filePath = `${root}/recycle-qr-${fileName(text)}.png`;
	return new Promise<string>((resolve) => {
		uni.getFileSystemManager().writeFile({
			filePath,
			data: base64,
			encoding: "base64",
			success: () => resolve(filePath),
			fail: () => resolve(dataUrl),
		});
	});
}

export function generateQrcode(text: string) {
	return savePng(createQrPng(text), text);
}

export function generateRecycleQrcode(text: string) {
	return generateQrcode(text);
}

/**
 * 清除指定二维码缓存
 * @param key
 */
export function clearQrcodeCache(key: string) {
	uni.removeStorageSync(key);
}
