/**
 * OpenJDK / Android SHA1PRNG compatible with SecureRandom.getInstance("SHA1PRNG").
 * Used by WalletCrypto.generateKeyPairFromSeed historically; ML-DSA-87 keygen now
 * uses sha3_256(fullSeed) directly (no SHA1PRNG). Kept for parity self-checks.
 */
export class Sha1Prng {
  constructor() {
    this.state = new Uint8Array(20);
    this.remCount = 0;
    this.remBytes = new Uint8Array(20);
    this.seeded = false;
  }

  setSeed(seed) {
    const bytes = seed instanceof Uint8Array ? seed : new Uint8Array(seed);
    if (!this.seeded) {
      this.state = sha1(bytes);
      this.seeded = true;
    } else {
      // subsequent setSeed: digest(state || seed)
      const mix = new Uint8Array(this.state.length + bytes.length);
      mix.set(this.state, 0);
      mix.set(bytes, this.state.length);
      this.state = sha1(mix);
    }
    this.remCount = 0;
  }

  nextBytes(out) {
    let index = 0;
    if (this.remCount > 0) {
      const todo = Math.min(out.length, 20 - this.remCount);
      out.set(this.remBytes.subarray(this.remCount, this.remCount + todo), 0);
      this.remCount += todo;
      index += todo;
      if (this.remCount >= 20) this.remCount = 0;
    }
    while (index < out.length) {
      // OpenJDK: output = SHA1(state); then state = state + output + 1
      this.remBytes = sha1(this.state);
      updateState(this.state, this.remBytes);
      const todo = Math.min(20, out.length - index);
      out.set(this.remBytes.subarray(0, todo), index);
      index += todo;
      this.remCount = todo === 20 ? 0 : todo;
    }
    return out;
  }
}

/** state = state + output + 1 using Java signed-byte arithmetic (OpenJDK SHA1PRNG). */
function updateState(state, output) {
  let last = 1;
  let zf = false;
  for (let i = 0; i < state.length; i++) {
    const sv = state[i] > 127 ? state[i] - 256 : state[i];
    const ov = output[i] > 127 ? output[i] - 256 : output[i];
    const v = (sv + ov + last) | 0;
    const t = v & 0xff;
    zf = zf || state[i] !== t;
    state[i] = t;
    last = v >> 8; // arithmetic shift, matches Java int >>
  }
  if (!zf) state[0] = (state[0] + 1) & 0xff;
}

function sha1(bytes) {
  const msg = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
  const ml = msg.length;
  const withOne = new Uint8Array(((ml + 9 + 63) >> 6) << 6);
  withOne.set(msg);
  withOne[ml] = 0x80;
  const bitLenHi = Math.floor((ml * 8) / 0x100000000);
  const bitLenLo = (ml * 8) >>> 0;
  const view = new DataView(withOne.buffer);
  view.setUint32(withOne.length - 8, bitLenHi, false);
  view.setUint32(withOne.length - 4, bitLenLo, false);

  let h0 = 0x67452301;
  let h1 = 0xefcdab89;
  let h2 = 0x98badcfe;
  let h3 = 0x10325476;
  let h4 = 0xc3d2e1f0;
  const w = new Uint32Array(80);

  for (let offset = 0; offset < withOne.length; offset += 64) {
    for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4, false);
    for (let i = 16; i < 80; i++) {
      const x = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16];
      w[i] = ((x << 1) | (x >>> 31)) >>> 0;
    }
    let a = h0,
      b = h1,
      c = h2,
      d = h3,
      e = h4;
    for (let i = 0; i < 80; i++) {
      let f, k;
      if (i < 20) {
        f = (b & c) | (~b & d);
        k = 0x5a827999;
      } else if (i < 40) {
        f = b ^ c ^ d;
        k = 0x6ed9eba1;
      } else if (i < 60) {
        f = (b & c) | (b & d) | (c & d);
        k = 0x8f1bbcdc;
      } else {
        f = b ^ c ^ d;
        k = 0xca62c1d6;
      }
      const temp = (((a << 5) | (a >>> 27)) + f + e + k + w[i]) >>> 0;
      e = d;
      d = c;
      c = ((b << 30) | (b >>> 2)) >>> 0;
      b = a;
      a = temp;
    }
    h0 = (h0 + a) >>> 0;
    h1 = (h1 + b) >>> 0;
    h2 = (h2 + c) >>> 0;
    h3 = (h3 + d) >>> 0;
    h4 = (h4 + e) >>> 0;
  }

  const out = new Uint8Array(20);
  const ov = new DataView(out.buffer);
  ov.setUint32(0, h0, false);
  ov.setUint32(4, h1, false);
  ov.setUint32(8, h2, false);
  ov.setUint32(12, h3, false);
  ov.setUint32(16, h4, false);
  return out;
}

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

export function bytesToHex(bytes) {
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
}
