import { Sha256 } from 'asmcrypto.js';
import wasmInit from './memory-pow.wasm?init';

let compute; // Exported compute function from Wasm
let memory;  // WebAssembly.Memory instance
let heap;    // Uint8Array view of the memory buffer
let brk = 512 * 1024; // Initial brk set to 512 KiB
const allocations = new Map(); // Track allocations by pointer
let workBufferPtr = null; // Reuse work buffer
const workBufferLength = 8 * 1024 * 1024; // 8 MiB

// Load the WebAssembly module
async function loadWasm() {
  try {
    memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }); // 16 MiB
    heap = new Uint8Array(memory.buffer);

    const importObject = {
      env: {
        memory, // Pass memory to Wasm
        abort: () => { throw new Error('Wasm abort called'); }, // Handle abort calls from Wasm
      },
    };

    const wasmModule = await wasmInit(importObject);
    compute = wasmModule.exports.compute2;
    console.log('Wasm loaded successfully:', compute);
  } catch (error) {
    console.error('Error loading Wasm:', error);
    throw error;
  }
}

// Memory allocation function
function sbrk(size) {
  const old = brk;

  // If a previous allocation exists for this size, reuse it
  if (allocations.has(size)) {
    return allocations.get(size);
  }

  brk += size;

  // Grow memory if needed
  if (brk > memory.buffer.byteLength) {
    const pagesNeeded = Math.ceil((brk - memory.buffer.byteLength) / (64 * 1024)); // 64 KiB per page
    try {
      memory.grow(pagesNeeded);
      heap = new Uint8Array(memory.buffer); // Update heap view
    } catch (e) {
      console.error('Failed to grow memory:', e);
      throw new RangeError('WebAssembly.Memory.grow(): Maximum memory size exceeded');
    }
  }

  allocations.set(size, old); // Track the allocation
  return old;
}

// Proof-of-Work computation function
async function computePow(chatBytes, difficulty) {
  if (!compute) {
    throw new Error('WebAssembly module not initialized. Call loadWasm first.');
  }

  const chatBytesArray = Uint8Array.from(Object.values(chatBytes));
  const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;

  // Allocate memory for the hash
  const hashPtr = sbrk(32);
  const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
  hashAry.set(chatBytesHash);

  // Reuse the work buffer if already allocated
  if (!workBufferPtr) {
    workBufferPtr = sbrk(workBufferLength);
  }

  const nonce = compute(hashPtr, workBufferPtr, workBufferLength, difficulty);

  return { nonce, chatBytesArray };
}

// Worker event listener
self.addEventListener('message', async (e) => {
  const { chatBytes, difficulty } = e.data;

  try {
    // Initialize Wasm if not already done
    if (!compute) {
      await loadWasm();
    }

    // Perform the POW computation
    const result = await computePow(chatBytes, difficulty);

    // Send the result back to the main thread
    self.postMessage(result);
  } catch (error) {
    console.error('Error in worker:', error);
    self.postMessage({ error: error.message });
  }
});
