import { decodeBase58Check } from "./base58.js";
import { readUint32 } from "./bytes.js";

const EXTENDED_PUBLIC_KEY_LENGTH = 78;
const PUBLIC_KEY_LENGTH = 33;
const CHAIN_CODE_LENGTH = 32;

const extendedPublicKeyVersions = /** @type {const} */ ([
  {
    version: 0x0488b21e,
    prefix: "xpub",
    network: "mainnet",
    script: "p2pkh",
    addrType: "p2pkh",
  },
  {
    version: 0x049d7cb2,
    prefix: "ypub",
    network: "mainnet",
    script: "p2sh_p2wpkh",
    addrType: "p2sh",
  },
  {
    version: 0x04b24746,
    prefix: "zpub",
    network: "mainnet",
    script: "v0_p2wpkh",
    addrType: "v0_p2wpkh",
  },
  {
    version: 0x043587cf,
    prefix: "tpub",
    network: "testnet",
    script: "p2pkh",
    addrType: "p2pkh",
  },
  {
    version: 0x044a5262,
    prefix: "upub",
    network: "testnet",
    script: "p2sh_p2wpkh",
    addrType: "p2sh",
  },
  {
    version: 0x045f1cf6,
    prefix: "vpub",
    network: "testnet",
    script: "v0_p2wpkh",
    addrType: "v0_p2wpkh",
  },
]);

/**
 * @typedef {typeof extendedPublicKeyVersions[number]} ExtendedPublicKeyVersion
 */

/**
 * @typedef {Object} ExtendedPublicKey
 * @property {string} text
 * @property {number} depth
 * @property {number} childNumber
 * @property {Uint8Array} parentFingerprint
 * @property {Uint8Array} chainCode
 * @property {Uint8Array} publicKey
 * @property {ExtendedPublicKeyVersion} version
 */

/**
 * @param {number} version
 * @returns {ExtendedPublicKeyVersion}
 */
function findExtendedPublicKeyVersion(version) {
  const metadata = extendedPublicKeyVersions.find((item) => {
    return item.version === version;
  });

  if (!metadata) {
    throw new Error(`Unsupported extended public key version: ${version}`);
  }

  return metadata;
}

/**
 * @param {Uint8Array} publicKey
 */
function validateCompressedPublicKey(publicKey) {
  if (
    publicKey.length !== PUBLIC_KEY_LENGTH ||
    (publicKey[0] !== 0x02 && publicKey[0] !== 0x03)
  ) {
    throw new Error("Expected a compressed public key");
  }
}

/**
 * @param {string} text
 * @returns {Promise<ExtendedPublicKey>}
 */
export async function parseExtendedPublicKey(text) {
  const value = text.trim();
  const bytes = await decodeBase58Check(value);

  if (bytes.length !== EXTENDED_PUBLIC_KEY_LENGTH) {
    throw new Error("Invalid extended public key length");
  }

  const version = findExtendedPublicKeyVersion(readUint32(bytes, 0));
  const parentFingerprint = bytes.slice(5, 9);
  const chainCode = bytes.slice(13, 13 + CHAIN_CODE_LENGTH);
  const publicKey = bytes.slice(45);

  validateCompressedPublicKey(publicKey);

  return {
    text: value,
    depth: bytes[4],
    childNumber: readUint32(bytes, 9),
    parentFingerprint,
    chainCode,
    publicKey,
    version,
  };
}
