'use client';

function toHex(bytes: Uint8Array): string {
  let hex = '';
  for (const byte of bytes) {
    hex += byte.toString(16).padStart(2, '0');
  }
  return hex;
}

function rotr32(x: number, n: number): number {
  return ((x >>> n) | (x << (32 - n))) >>> 0;
}

export function sha256HexSoftwareFromBytes(bytes: Uint8Array): string {
  const K = new Uint32Array([
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
  ]);

  let h0 = 0x6a09e667;
  let h1 = 0xbb67ae85;
  let h2 = 0x3c6ef372;
  let h3 = 0xa54ff53a;
  let h4 = 0x510e527f;
  let h5 = 0x9b05688c;
  let h6 = 0x1f83d9ab;
  let h7 = 0x5be0cd19;

  const w = new Uint32Array(64);

  const compress = (block: Uint8Array, offset: number) => {
    for (let i = 0; i < 16; i++) {
      const j = offset + i * 4;
      w[i] = ((block[j]! << 24) | (block[j + 1]! << 16) | (block[j + 2]! << 8) | block[j + 3]!) >>> 0;
    }
    for (let i = 16; i < 64; i++) {
      const s0 = (rotr32(w[i - 15]!, 7) ^ rotr32(w[i - 15]!, 18) ^ (w[i - 15]! >>> 3)) >>> 0;
      const s1 = (rotr32(w[i - 2]!, 17) ^ rotr32(w[i - 2]!, 19) ^ (w[i - 2]! >>> 10)) >>> 0;
      w[i] = (w[i - 16]! + s0 + w[i - 7]! + s1) >>> 0;
    }

    let a = h0;
    let b = h1;
    let c = h2;
    let d = h3;
    let e = h4;
    let f = h5;
    let g = h6;
    let h = h7;

    for (let i = 0; i < 64; i++) {
      const S1 = (rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25)) >>> 0;
      const ch = ((e & f) ^ (~e & g)) >>> 0;
      const temp1 = (h + S1 + ch + K[i]! + w[i]!) >>> 0;
      const S0 = (rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22)) >>> 0;
      const maj = ((a & b) ^ (a & c) ^ (b & c)) >>> 0;
      const temp2 = (S0 + maj) >>> 0;

      h = g;
      g = f;
      f = e;
      e = (d + temp1) >>> 0;
      d = c;
      c = b;
      b = a;
      a = (temp1 + temp2) >>> 0;
    }

    h0 = (h0 + a) >>> 0;
    h1 = (h1 + b) >>> 0;
    h2 = (h2 + c) >>> 0;
    h3 = (h3 + d) >>> 0;
    h4 = (h4 + e) >>> 0;
    h5 = (h5 + f) >>> 0;
    h6 = (h6 + g) >>> 0;
    h7 = (h7 + h) >>> 0;
  };

  let offset = 0;
  for (; offset + 64 <= bytes.length; offset += 64) {
    compress(bytes, offset);
  }

  const remainder = bytes.length - offset;
  const tail = new Uint8Array(128);
  tail.set(bytes.subarray(offset));
  tail[remainder] = 0x80;

  const bitLenHi = (bytes.length >>> 29) >>> 0;
  const bitLenLo = (bytes.length << 3) >>> 0;

  const totalTailLen = remainder + 1 + 8 <= 64 ? 64 : 128;
  const lenOffset = totalTailLen - 8;
  tail[lenOffset] = (bitLenHi >>> 24) & 0xff;
  tail[lenOffset + 1] = (bitLenHi >>> 16) & 0xff;
  tail[lenOffset + 2] = (bitLenHi >>> 8) & 0xff;
  tail[lenOffset + 3] = bitLenHi & 0xff;
  tail[lenOffset + 4] = (bitLenLo >>> 24) & 0xff;
  tail[lenOffset + 5] = (bitLenLo >>> 16) & 0xff;
  tail[lenOffset + 6] = (bitLenLo >>> 8) & 0xff;
  tail[lenOffset + 7] = bitLenLo & 0xff;

  compress(tail, 0);
  if (totalTailLen === 128) {
    compress(tail, 64);
  }

  return (
    h0.toString(16).padStart(8, '0') +
    h1.toString(16).padStart(8, '0') +
    h2.toString(16).padStart(8, '0') +
    h3.toString(16).padStart(8, '0') +
    h4.toString(16).padStart(8, '0') +
    h5.toString(16).padStart(8, '0') +
    h6.toString(16).padStart(8, '0') +
    h7.toString(16).padStart(8, '0')
  );
}

let didWarnWebCryptoUnavailable = false;
let didWarnWebCryptoDigestFailed = false;

export async function sha256HexFromBytes(bytes: Uint8Array): Promise<string> {
  const subtle = globalThis.crypto?.subtle;
  if (!subtle) {
    if (!didWarnWebCryptoUnavailable) {
      didWarnWebCryptoUnavailable = true;
      console.warn('[sha256] WebCrypto unavailable; falling back to software SHA-256.');
    }
    return sha256HexSoftwareFromBytes(bytes);
  }

  try {
    const digest = await subtle.digest('SHA-256', bytes as unknown as BufferSource);
    return toHex(new Uint8Array(digest));
  } catch (error) {
    if (!didWarnWebCryptoDigestFailed) {
      didWarnWebCryptoDigestFailed = true;
      console.warn('[sha256] WebCrypto digest failed; falling back to software SHA-256.', error);
    }
    return sha256HexSoftwareFromBytes(bytes);
  }
}

export async function sha256HexFromArrayBuffer(buffer: ArrayBuffer): Promise<string> {
  return sha256HexFromBytes(new Uint8Array(buffer));
}

export async function sha256HexFromString(text: string): Promise<string> {
  return sha256HexFromBytes(new TextEncoder().encode(text));
}
