import { PsbtV2, detectScriptType } from "@ledgerhq/psbtv2";
import { pathArrayToString, pathStringToArray } from "../bip32";
import { checkBip32Derivation } from "./derivationAccessors";
import type { ScriptType } from "./types";

export function arePathsEqual(path1: number[], path2: number[]): boolean {
  if (path1.length !== path2.length) return false;
  return path1.every((elem, idx) => elem === path2[idx]);
}

export function validateAccountPathConsistency(
  accountPath: number[],
  newAccountPath: number[],
  inputIndex: number,
): void {
  if (accountPath.length > 0 && !arePathsEqual(accountPath, newAccountPath)) {
    throw new Error(
      `Mixed accounts detected in PSBT. Input ${inputIndex} uses account path ` +
        `${pathArrayToString(newAccountPath)} but expected ` +
        `${pathArrayToString(accountPath)}. All internal inputs must belong to the same account.`,
    );
  }
}

export function validateScriptTypeConsistency(
  detectedScriptType: ScriptType | undefined,
  newScriptType: ScriptType | undefined,
  inputIndex: number,
): void {
  if (detectedScriptType && newScriptType && detectedScriptType !== newScriptType) {
    throw new Error(
      `Mixed input types detected in PSBT. Input ${inputIndex} uses ${newScriptType} ` +
        `but expected ${detectedScriptType}. All internal inputs must use the same script type.`,
    );
  }
}

export function resolveAccountPathFromOptions(accountPathOption?: string): number[] {
  if (!accountPathOption) {
    throw new Error(
      "No internal inputs found in PSBT (no BIP32 derivation matching device fingerprint) " +
        "and no account path provided in options. Please provide accountPath in options " +
        "(e.g., \"m/84'/0'/0'\" for native segwit)",
    );
  }
  return pathStringToArray(accountPathOption);
}

/**
 * Determines the script type for a single input from witness UTXO or redeem script.
 */
export function determineInputScriptType(psbt: PsbtV2, inputIndex: number): ScriptType | undefined {
  const witnessUtxo = psbt.getInputWitnessUtxo(inputIndex);
  if (witnessUtxo) {
    return detectScriptType(witnessUtxo.scriptPubKey);
  }

  const redeemScript = psbt.getInputRedeemScript(inputIndex);
  if (redeemScript) {
    return "p2sh-p2wpkh";
  }

  return undefined;
}

/**
 * Analyzes a single input to determine if it belongs to the connected signer and extracts account path and script type.
 */
export function analyzeInput(
  psbt: PsbtV2,
  inputIndex: number,
  masterFp: Buffer,
): {
  belongsToSigner: boolean;
  accountPath: number[];
  scriptType: ScriptType | undefined;
} {
  const derivationResult = checkBip32Derivation(psbt, inputIndex, masterFp);
  const scriptType = determineInputScriptType(psbt, inputIndex);

  return {
    belongsToSigner: derivationResult.belongsToSigner,
    accountPath: derivationResult.accountPath,
    scriptType,
  };
}

/**
 * Analyzes all inputs and returns resolved account path, detected script type, and internal input indices.
 */
export function analyzeAllInputs(
  psbt: PsbtV2,
  inputCount: number,
  masterFp: Buffer,
  accountPathOption?: string,
): {
  accountPath: number[];
  detectedScriptType: ScriptType | undefined;
  internalInputIndices: number[];
} {
  const internalInputIndices: number[] = [];
  let accountPath: number[] = [];
  let detectedScriptType: ScriptType | undefined;

  for (let i = 0; i < inputCount; i++) {
    const inputInfo = analyzeInput(psbt, i, masterFp);

    if (!inputInfo.belongsToSigner) {
      continue;
    }

    internalInputIndices.push(i);
    validateAccountPathConsistency(accountPath, inputInfo.accountPath, i);

    if (accountPath.length === 0) {
      accountPath = inputInfo.accountPath;
    }

    validateScriptTypeConsistency(detectedScriptType, inputInfo.scriptType, i);

    if (!detectedScriptType) {
      detectedScriptType = inputInfo.scriptType;
    }
  }

  if (internalInputIndices.length === 0) {
    accountPath = resolveAccountPathFromOptions(accountPathOption);
  }

  return { accountPath, detectedScriptType, internalInputIndices };
}
