import { PsbtV2, psbtIn, psbtOut } from "@ledgerhq/psbtv2";
import type { DerivationAccessors, DerivationElementType } from "./types";

/**
 * Returns accessors for BIP32 derivation operations based on element type.
 */
export function getDerivationAccessors(
  psbt: PsbtV2,
  type: DerivationElementType,
): DerivationAccessors {
  if (type === "input") {
    return {
      getKeyDatas: (i, kt) => psbt.getInputKeyDatas(i, kt),
      getBip32Derivation: (i, pk) => psbt.getInputBip32Derivation(i, pk),
      getTapBip32Derivation: (i, pk) => psbt.getInputTapBip32Derivation(i, pk),
      setBip32Derivation: (i, pk, fp, p) => psbt.setInputBip32Derivation(i, pk, fp, p),
      setTapBip32Derivation: (i, pk, h, fp, p) => psbt.setInputTapBip32Derivation(i, pk, h, fp, p),
      bip32KeyType: psbtIn.BIP32_DERIVATION,
      tapBip32KeyType: psbtIn.TAP_BIP32_DERIVATION,
    };
  }
  return {
    getKeyDatas: (i, kt) => psbt.getOutputKeyDatas(i, kt),
    getBip32Derivation: (i, pk) => psbt.getOutputBip32Derivation(i, pk),
    getTapBip32Derivation: (i, pk) => psbt.getOutputTapBip32Derivation(i, pk),
    setBip32Derivation: (i, pk, fp, p) => psbt.setOutputBip32Derivation(i, pk, fp, p),
    setTapBip32Derivation: (i, pk, h, fp, p) => psbt.setOutputTapBip32Derivation(i, pk, h, fp, p),
    bip32KeyType: psbtOut.BIP_32_DERIVATION,
    tapBip32KeyType: psbtOut.TAP_BIP32_DERIVATION,
  };
}

/**
 * Generic method to check BIP32 derivation for either an input or output.
 */
export function checkElementBip32Derivation(
  accessors: DerivationAccessors,
  elementIndex: number,
  masterFp: Buffer,
): { belongsToSigner: boolean; accountPath: number[] } {
  const keyDatas = accessors.getKeyDatas(elementIndex, accessors.bip32KeyType);
  for (const pubkey of keyDatas) {
    const derivationInfo = accessors.getBip32Derivation(elementIndex, pubkey);
    if (derivationInfo?.masterFingerprint.equals(masterFp)) {
      return extractAccountPath(derivationInfo.path);
    }
  }

  const tapKeyDatas = accessors.getKeyDatas(elementIndex, accessors.tapBip32KeyType);
  for (const pubkey of tapKeyDatas) {
    const derivationInfo = accessors.getTapBip32Derivation(elementIndex, pubkey);
    if (derivationInfo?.masterFingerprint.equals(masterFp)) {
      return extractAccountPath(derivationInfo.path);
    }
  }

  return { belongsToSigner: false, accountPath: [] };
}

/**
 * Returns belongsToSigner: true because this function is only called after
 * a master fingerprint match against the connected signer (hardware wallet).
 * This is unrelated to the BIP44 "internal chain" (change = 1) concept.
 */
export function extractAccountPath(fullPath: number[]): {
  belongsToSigner: true;
  accountPath: number[];
} {
  const accountPath = fullPath.length >= 2 ? fullPath.slice(0, -2) : [];
  return { belongsToSigner: true, accountPath };
}

export function checkBip32Derivation(
  psbt: PsbtV2,
  inputIndex: number,
  masterFp: Buffer,
): { belongsToSigner: boolean; accountPath: number[] } {
  const accessors = getDerivationAccessors(psbt, "input");
  return checkElementBip32Derivation(accessors, inputIndex, masterFp);
}

/**
 * Checks if an output has a valid BIP32 derivation with the correct master fingerprint.
 */
export function checkOutputBip32Derivation(
  psbt: PsbtV2,
  outputIndex: number,
  masterFp: Buffer,
): boolean {
  const accessors = getDerivationAccessors(psbt, "output");
  const result = checkElementBip32Derivation(accessors, outputIndex, masterFp);
  return result.belongsToSigner;
}
