import 'dart:typed_data';

const int _mask32 = 0xFFFFFFFF;

const List<int> _s = <int>[
  7,
  12,
  17,
  22,
  7,
  12,
  17,
  22,
  7,
  12,
  17,
  22,
  7,
  12,
  17,
  22,
  5,
  9,
  14,
  20,
  5,
  9,
  14,
  20,
  5,
  9,
  14,
  20,
  5,
  9,
  14,
  20,
  4,
  11,
  16,
  23,
  4,
  11,
  16,
  23,
  4,
  11,
  16,
  23,
  4,
  11,
  16,
  23,
  6,
  10,
  15,
  21,
  6,
  10,
  15,
  21,
  6,
  10,
  15,
  21,
  6,
  10,
  15,
  21,
];

const List<int> _k = <int>[
  0xd76aa478,
  0xe8c7b756,
  0x242070db,
  0xc1bdceee,
  0xf57c0faf,
  0x4787c62a,
  0xa8304613,
  0xfd469501,
  0x698098d8,
  0x8b44f7af,
  0xffff5bb1,
  0x895cd7be,
  0x6b901122,
  0xfd987193,
  0xa679438e,
  0x49b40821,
  0xf61e2562,
  0xc040b340,
  0x265e5a51,
  0xe9b6c7aa,
  0xd62f105d,
  0x02441453,
  0xd8a1e681,
  0xe7d3fbc8,
  0x21e1cde6,
  0xc33707d6,
  0xf4d50d87,
  0x455a14ed,
  0xa9e3e905,
  0xfcefa3f8,
  0x676f02d9,
  0x8d2a4c8a,
  0xfffa3942,
  0x8771f681,
  0x6d9d6122,
  0xfde5380c,
  0xa4beea44,
  0x4bdecfa9,
  0xf6bb4b60,
  0xbebfbc70,
  0x289b7ec6,
  0xeaa127fa,
  0xd4ef3085,
  0x04881d05,
  0xd9d4d039,
  0xe6db99e5,
  0x1fa27cf8,
  0xc4ac5665,
  0xf4292244,
  0x432aff97,
  0xab9423a7,
  0xfc93a039,
  0x655b59c3,
  0x8f0ccc92,
  0xffeff47d,
  0x85845dd1,
  0x6fa87e4f,
  0xfe2ce6e0,
  0xa3014314,
  0x4e0811a1,
  0xf7537e82,
  0xbd3af235,
  0x2ad7d2bb,
  0xeb86d391,
];

String md5HexLower(List<int> input) {
  final source = Uint8List.fromList(input);
  final originalLength = source.length;
  final bitLength = originalLength * 8;

  final paddingLength = (56 - ((originalLength + 1) % 64) + 64) % 64;
  final totalLength = originalLength + 1 + paddingLength + 8;

  final bytes = Uint8List(totalLength);
  bytes.setRange(0, originalLength, source);
  bytes[originalLength] = 0x80;

  for (var index = 0; index < 8; index++) {
    bytes[totalLength - 8 + index] = (bitLength >> (8 * index)) & 0xFF;
  }

  var a0 = 0x67452301;
  var b0 = 0xEFCDAB89;
  var c0 = 0x98BADCFE;
  var d0 = 0x10325476;

  for (var chunkOffset = 0; chunkOffset < bytes.length; chunkOffset += 64) {
    final m = Uint32List(16);

    for (var j = 0; j < 16; j++) {
      final base = chunkOffset + j * 4;
      m[j] =
          bytes[base] |
          (bytes[base + 1] << 8) |
          (bytes[base + 2] << 16) |
          (bytes[base + 3] << 24);
    }

    var a = a0;
    var b = b0;
    var c = c0;
    var d = d0;

    for (var i = 0; i < 64; i++) {
      late int f;
      late int g;

      if (i < 16) {
        f = ((b & c) | ((~b) & d)) & _mask32;
        g = i;
      } else if (i < 32) {
        f = ((d & b) | ((~d) & c)) & _mask32;
        g = (5 * i + 1) % 16;
      } else if (i < 48) {
        f = (b ^ c ^ d) & _mask32;
        g = (3 * i + 5) % 16;
      } else {
        f = (c ^ (b | (~d))) & _mask32;
        g = (7 * i) % 16;
      }

      final temp = d;
      d = c;
      c = b;

      final sum = (a + f + _k[i] + m[g]) & _mask32;
      b = (b + _leftRotate(sum, _s[i])) & _mask32;
      a = temp;
    }

    a0 = (a0 + a) & _mask32;
    b0 = (b0 + b) & _mask32;
    c0 = (c0 + c) & _mask32;
    d0 = (d0 + d) & _mask32;
  }

  final output = StringBuffer();
  _appendWordHex(output, a0);
  _appendWordHex(output, b0);
  _appendWordHex(output, c0);
  _appendWordHex(output, d0);
  return output.toString();
}

int _leftRotate(int value, int shift) {
  return ((value << shift) | ((value & _mask32) >> (32 - shift))) & _mask32;
}

void _appendWordHex(StringBuffer output, int value) {
  for (var offset = 0; offset < 32; offset += 8) {
    final byte = (value >> offset) & 0xFF;
    output.write(byte.toRadixString(16).padLeft(2, '0'));
  }
}
