const elliptic = require('elliptic');
const { keccak_256 } = require('js-sha3');

const ec = new elliptic.ec('secp256k1');

function bytesToHex(bytes) {
  return Array.from(bytes || []).map((b) => b.toString(16).padStart(2, '0')).join('');
}

function hexToBytes(hex) {
  const clean = String(hex || '').replace(/^0x/i, '');
  const out = new Uint8Array(clean.length / 2);
  for (let i = 0; i < clean.length; i += 2) out[i / 2] = parseInt(clean.slice(i, i + 2), 16);
  return out;
}

function leftPad32(bytes) {
  const out = new Uint8Array(32);
  out.set(bytes.slice(-32), 32 - Math.min(32, bytes.length));
  return out;
}

function randomPrivateKey() {
  for (;;) {
    const bytes = new Uint8Array(32);
    globalThis.crypto.getRandomValues(bytes);
    const key = ec.keyFromPrivate(bytesToHex(bytes), 'hex');
    if (!key.getPrivate().isZero()) return bytes;
  }
}

function privateKeyToAccount(privateKey) {
  const priv = privateKey instanceof Uint8Array ? privateKey : hexToBytes(privateKey);
  if (priv.length !== 32) throw new Error('secp256k1 private key must be 32 bytes');
  const key = ec.keyFromPrivate(bytesToHex(priv), 'hex');
  const pubCompressed = new Uint8Array(key.getPublic(true, 'array'));
  const pubUncompressed = new Uint8Array(key.getPublic(false, 'array')).slice(1);
  const hash = new Uint8Array(keccak_256.array(pubUncompressed));
  return {
    privateKeyHex: bytesToHex(priv),
    publicKeyCompressed: pubCompressed,
    evmAddress: '0x' + bytesToHex(hash.slice(-20)),
  };
}

function generateAccount() {
  return privateKeyToAccount(randomPrivateKey());
}

function signDigest64(privateKey, digestBytes) {
  const priv = privateKey instanceof Uint8Array ? privateKey : hexToBytes(privateKey);
  const digest = digestBytes instanceof Uint8Array ? digestBytes : hexToBytes(digestBytes);
  const key = ec.keyFromPrivate(bytesToHex(priv), 'hex');
  const sig = key.sign(bytesToHex(digest), { canonical: true });
  const r = leftPad32(new Uint8Array(sig.r.toArray('be')));
  const s = leftPad32(new Uint8Array(sig.s.toArray('be')));
  const out = new Uint8Array(64);
  out.set(r, 0);
  out.set(s, 32);
  return out;
}

function signDigest65(privateKey, digestBytes) {
  const priv = privateKey instanceof Uint8Array ? privateKey : hexToBytes(privateKey);
  const digest = digestBytes instanceof Uint8Array ? digestBytes : hexToBytes(digestBytes);
  const key = ec.keyFromPrivate(bytesToHex(priv), 'hex');
  const sig = key.sign(bytesToHex(digest), { canonical: true });
  const r = leftPad32(new Uint8Array(sig.r.toArray('be')));
  const s = leftPad32(new Uint8Array(sig.s.toArray('be')));
  const out = new Uint8Array(65);
  out.set(r, 0);
  out.set(s, 32);
  out[64] = 27 + (sig.recoveryParam || 0);
  return out;
}

function keccakBytes(bytes) {
  return new Uint8Array(keccak_256.array(bytes));
}

function concatBytes(parts) {
  const len = parts.reduce((n, p) => n + p.length, 0);
  const out = new Uint8Array(len);
  let off = 0;
  for (const p of parts) {
    out.set(p, off);
    off += p.length;
  }
  return out;
}

function signPersonalMessage(privateKey, messageBytes) {
  const msg = messageBytes instanceof Uint8Array ? messageBytes : new TextEncoder().encode(String(messageBytes || ''));
  const prefix = new TextEncoder().encode('\x19Ethereum Signed Message:\n' + String(msg.length));
  return signDigest65(privateKey, keccakBytes(concatBytes([prefix, msg])));
}

globalThis.WCAHT_EVM = {
  generateAccount,
  privateKeyToAccount,
  signDigest64,
  signDigest65,
  signPersonalMessage,
  keccakBytes,
  hexToBytes,
  bytesToHex,
};
