const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';

const ALPHABET_MAP = new Map<string, number>();
for (let i = 0; i < BASE58_ALPHABET.length; i++) {
  ALPHABET_MAP.set(BASE58_ALPHABET[i], i);
}

export function base58Decode(str: string): Uint8Array {
  if (str.length === 0) return new Uint8Array(0);

  // Count leading '1's (zero bytes)
  let leadingZeros = 0;
  while (leadingZeros < str.length && str[leadingZeros] === '1') {
    leadingZeros++;
  }

  // Decode base58 to big integer (stored as byte array)
  const size = Math.ceil(str.length * 733 / 1000) + 1; // log(58) / log(256)
  const bytes = new Uint8Array(size);

  for (let i = leadingZeros; i < str.length; i++) {
    const val = ALPHABET_MAP.get(str[i]);
    if (val === undefined) throw new Error(`Invalid base58 character: ${str[i]}`);

    let carry = val;
    for (let j = size - 1; j >= 0; j--) {
      carry += 256 * bytes[j];
      bytes[j] = carry % 256;
      carry = Math.floor(carry / 256);
    }
  }

  // Skip leading zeros in the decoded bytes
  let start = 0;
  while (start < size && bytes[start] === 0) {
    start++;
  }

  const result = new Uint8Array(leadingZeros + (size - start));
  // Leading zeros from '1' characters
  for (let i = 0; i < leadingZeros; i++) {
    result[i] = 0;
  }
  // Decoded bytes
  for (let i = start; i < size; i++) {
    result[leadingZeros + (i - start)] = bytes[i];
  }

  return result;
}

export function base58Encode(bytes: Uint8Array): string {
  if (bytes.length === 0) return '';

  // Count leading zero bytes
  let leadingZeros = 0;
  while (leadingZeros < bytes.length && bytes[leadingZeros] === 0) {
    leadingZeros++;
  }

  // Encode to base58
  const size = Math.ceil(bytes.length * 138 / 100) + 1; // log(256) / log(58)
  const digits = new Uint8Array(size);

  for (let i = leadingZeros; i < bytes.length; i++) {
    let carry = bytes[i];
    for (let j = size - 1; j >= 0; j--) {
      carry += 256 * digits[j];
      digits[j] = carry % 58;
      carry = Math.floor(carry / 58);
    }
  }

  // Skip leading zeros in base58 output
  let start = 0;
  while (start < size && digits[start] === 0) {
    start++;
  }

  let result = '1'.repeat(leadingZeros);
  for (let i = start; i < size; i++) {
    result += BASE58_ALPHABET[digits[i]];
  }

  return result;
}

/**
 * Convert an SS58 address to a base58-encoded raw pubkey (Cesium+ v1 format).
 *
 * SS58 layout: [prefix (1 or 2 bytes)] [32 bytes pubkey] [2 bytes checksum]
 * - If first byte has bit 6 set (& 0x40), prefix is 2 bytes
 * - Otherwise prefix is 1 byte
 */
export function ss58ToV1Pubkey(ss58: string): string {
  const raw = base58Decode(ss58);

  // Determine prefix length
  const prefixLen = (raw[0] & 0x40) ? 2 : 1;

  // Extract 32-byte pubkey (skip prefix, drop 2-byte checksum)
  const pubkey = raw.slice(prefixLen, prefixLen + 32);

  if (pubkey.length !== 32) {
    throw new Error(`Invalid SS58 address: expected 32-byte pubkey, got ${pubkey.length}`);
  }

  return base58Encode(pubkey);
}
