// Minimal scrypt implementation adapted from scrypt-js (MIT License).
// Source: https://github.com/ricmoo/scrypt-js
// This is bundled here to avoid external dependencies for MV3 background use.

function rotl(a, b) {
  return (a << b) | (a >>> (32 - b));
}

function salsa20_8(B) {
  const x = new Uint32Array(16);
  for (let i = 0; i < 16; i += 1) {
    x[i] = B[i];
  }
  for (let i = 0; i < 8; i += 2) {
    x[4] ^= rotl(x[0] + x[12], 7);
    x[8] ^= rotl(x[4] + x[0], 9);
    x[12] ^= rotl(x[8] + x[4], 13);
    x[0] ^= rotl(x[12] + x[8], 18);

    x[9] ^= rotl(x[5] + x[1], 7);
    x[13] ^= rotl(x[9] + x[5], 9);
    x[1] ^= rotl(x[13] + x[9], 13);
    x[5] ^= rotl(x[1] + x[13], 18);

    x[14] ^= rotl(x[10] + x[6], 7);
    x[2] ^= rotl(x[14] + x[10], 9);
    x[6] ^= rotl(x[2] + x[14], 13);
    x[10] ^= rotl(x[6] + x[2], 18);

    x[3] ^= rotl(x[15] + x[11], 7);
    x[7] ^= rotl(x[3] + x[15], 9);
    x[11] ^= rotl(x[7] + x[3], 13);
    x[15] ^= rotl(x[11] + x[7], 18);

    x[1] ^= rotl(x[0] + x[3], 7);
    x[2] ^= rotl(x[1] + x[0], 9);
    x[3] ^= rotl(x[2] + x[1], 13);
    x[0] ^= rotl(x[3] + x[2], 18);

    x[6] ^= rotl(x[5] + x[4], 7);
    x[7] ^= rotl(x[6] + x[5], 9);
    x[4] ^= rotl(x[7] + x[6], 13);
    x[5] ^= rotl(x[4] + x[7], 18);

    x[11] ^= rotl(x[10] + x[9], 7);
    x[8] ^= rotl(x[11] + x[10], 9);
    x[9] ^= rotl(x[8] + x[11], 13);
    x[10] ^= rotl(x[9] + x[8], 18);

    x[12] ^= rotl(x[15] + x[14], 7);
    x[13] ^= rotl(x[12] + x[15], 9);
    x[14] ^= rotl(x[13] + x[12], 13);
    x[15] ^= rotl(x[14] + x[13], 18);
  }
  for (let i = 0; i < 16; i += 1) {
    B[i] = (B[i] + x[i]) >>> 0;
  }
}

function blockMix(B, r) {
  const X = new Uint32Array(16);
  const out = new Uint32Array(B.length);
  const B32 = B;
  let i;
  for (i = 0; i < 16; i += 1) {
    X[i] = B32[B32.length - 16 + i];
  }
  for (i = 0; i < 2 * r; i += 1) {
    for (let j = 0; j < 16; j += 1) {
      X[j] ^= B32[i * 16 + j];
    }
    salsa20_8(X);
    const dest = i % 2 === 0 ? (i / 2) * 16 : (r + (i - 1) / 2) * 16;
    for (let j = 0; j < 16; j += 1) {
      out[dest + j] = X[j];
    }
  }
  return out;
}

function integerify(B, r) {
  const index = (2 * r - 1) * 16;
  return B[index];
}

function romix(B, r, N) {
  let X = B;
  const V = new Array(N);
  for (let i = 0; i < N; i += 1) {
    V[i] = X;
    X = blockMix(X, r);
  }
  for (let i = 0; i < N; i += 1) {
    const j = integerify(X, r) & (N - 1);
    const T = new Uint32Array(X.length);
    for (let k = 0; k < X.length; k += 1) {
      T[k] = X[k] ^ V[j][k];
    }
    X = blockMix(T, r);
  }
  return X;
}

function pbkdf2HmacSha256(password, salt, iterations, dkLen) {
  return crypto.subtle
    .importKey("raw", password, { name: "HMAC", hash: "SHA-256" }, false, [
      "sign",
    ])
    .then((key) => {
      const hLen = 32;
      const l = Math.ceil(dkLen / hLen);
      const out = new Uint8Array(dkLen);
      let offset = 0;

      const f = async (i) => {
        const blockIndex = new Uint8Array(4);
        blockIndex[0] = (i >>> 24) & 0xff;
        blockIndex[1] = (i >>> 16) & 0xff;
        blockIndex[2] = (i >>> 8) & 0xff;
        blockIndex[3] = i & 0xff;

        let u = new Uint8Array(
          await crypto.subtle.sign("HMAC", key, concatBytes(salt, blockIndex))
        );
        const t = u.slice();
        for (let j = 1; j < iterations; j += 1) {
          u = new Uint8Array(await crypto.subtle.sign("HMAC", key, u));
          for (let k = 0; k < t.length; k += 1) {
            t[k] ^= u[k];
          }
        }
        out.set(t, offset);
        offset += hLen;
      };

      const blocks = [];
      for (let i = 1; i <= l; i += 1) {
        blocks.push(f(i));
      }
      return Promise.all(blocks).then(() => out.slice(0, dkLen));
    });
}

function concatBytes(a, b) {
  const out = new Uint8Array(a.length + b.length);
  out.set(a, 0);
  out.set(b, a.length);
  return out;
}

export async function scrypt(password, salt, N, r, p, dkLen) {
  if (N <= 1 || (N & (N - 1)) !== 0) {
    throw new Error("N must be a power of two.");
  }
  if (r <= 0 || p <= 0) {
    throw new Error("Invalid r or p.");
  }
  const B = await pbkdf2HmacSha256(password, salt, 1, p * 128 * r);
  const B32 = new Uint32Array(B.buffer);
  for (let i = 0; i < p; i += 1) {
    const block = B32.subarray(i * 32 * r, (i + 1) * 32 * r);
    const mixed = romix(block, r, N);
    B32.set(mixed, i * 32 * r);
  }
  const DK = await pbkdf2HmacSha256(B, password, 1, dkLen);
  return new Uint8Array(DK);
}
