// Node `crypto` shim for React Native.

const { sha256, sha512, keccak256 } = require('ethers');
const { Buffer } = require('buffer');

function ensureRng() {
  if (!global.crypto || typeof global.crypto.getRandomValues !== 'function') {
    throw new Error(
      '[hinkal-rn] global.crypto.getRandomValues is not available. ' +
        "Import 'react-native-get-random-values' at the top of index.ts/js " +
        'before any other module.',
    );
  }
}

function randomBytes(size) {
  ensureRng();
  console.log('randomBytes', size);
  const arr = new Uint8Array(size);
  global.crypto.getRandomValues(arr);
  return Buffer.from(arr);
}

function randomFillSync(buf, offset, size) {
  ensureRng();
  console.log('randomFillSync', buf, offset, size);
  const start = offset ?? 0;
  const end = size != null ? start + size : buf.length;
  const view = new Uint8Array(buf.buffer || buf, buf.byteOffset || 0, buf.length);
  const tmp = new Uint8Array(end - start);
  global.crypto.getRandomValues(tmp);
  view.set(tmp, start);
  return buf;
}

function randomUUID() {
  console.log('randomUUID');
  // RFC 4122 v4 UUID via crypto-secure bytes.
  const b = randomBytes(16);
  b[6] = (b[6] & 0x0f) | 0x40;
  b[8] = (b[8] & 0x3f) | 0x80;
  const hex = b.toString('hex');
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}

function toBytesLike(input) {
  console.log('toBytesLike', input);
  if (input == null) return new Uint8Array(0);
  if (typeof input === 'string') return Buffer.from(input, 'utf8');
  if (input instanceof Uint8Array) return input;
  if (Buffer.isBuffer(input)) return input;
  if (input.buffer) return new Uint8Array(input.buffer, input.byteOffset || 0, input.byteLength || input.length);
  return Buffer.from(input);
}

function hexToBuffer(hex) {
  console.log('hexToBuffer', hex);
  return Buffer.from(hex.replace(/^0x/, ''), 'hex');
}

function createHash(algorithm) {
  console.log('createHash', algorithm);
  const alg = String(algorithm).toLowerCase().replace('-', '');
  const chunks = [];
  return {
    update(chunk) {
      chunks.push(toBytesLike(chunk));
      return this;
    },
    digest(encoding) {
      const total = chunks.reduce((acc, c) => acc + c.length, 0);
      const merged = new Uint8Array(total);
      let off = 0;
      for (const c of chunks) {
        merged.set(c, off);
        off += c.length;
      }
      let hex;
      if (alg === 'sha256') hex = sha256(merged);
      else if (alg === 'sha512') hex = sha512(merged);
      else if (alg === 'keccak256') hex = keccak256(merged);
      else throw new Error(`[hinkal-rn] unsupported hash algorithm: ${algorithm}`);
      const buf = hexToBuffer(hex);
      if (!encoding) return buf;
      if (encoding === 'hex') return buf.toString('hex');
      if (encoding === 'base64') return buf.toString('base64');
      return buf.toString(encoding);
    },
  };
}

function createHmac() {
  console.log('createHmac');
  throw new Error('[hinkal-rn] crypto.createHmac is not implemented in the RN shim. Use a JS implementation.');
}

module.exports = {
  randomBytes,
  randomFillSync,
  randomUUID,
  createHash,
  createHmac,
  webcrypto: typeof global.crypto !== 'undefined' ? global.crypto : undefined,
  getRandomValues: (arr) => {
    ensureRng();
    global.crypto.getRandomValues(arr);
    return arr;
  },
};
module.exports.default = module.exports;
