import type {Buffer} from 'buffer';

import {
  BlockheightBasedTransactionConfirmationStrategy,
  Connection,
  DurableNonceTransactionConfirmationStrategy,
  TransactionConfirmationStrategy,
} from '../connection';
import type {TransactionSignature} from '../transaction';
import type {ConfirmOptions} from '../connection';
import {SendTransactionError} from '../errors';

/**
 * Send and confirm a raw transaction
 *
 * If `commitment` option is not specified, defaults to 'max' commitment.
 *
 * @param {Connection} connection
 * @param {Buffer} rawTransaction
 * @param {TransactionConfirmationStrategy} confirmationStrategy
 * @param {ConfirmOptions} [options]
 * @returns {Promise<TransactionSignature>}
 */
export async function sendAndConfirmRawTransaction(
  connection: Connection,
  rawTransaction: Buffer,
  confirmationStrategy: TransactionConfirmationStrategy,
  options?: ConfirmOptions,
): Promise<TransactionSignature>;

/**
 * @deprecated Calling `sendAndConfirmRawTransaction()` without a `confirmationStrategy`
 * is no longer supported and will be removed in a future version.
 */
// eslint-disable-next-line no-redeclare
export async function sendAndConfirmRawTransaction(
  connection: Connection,
  rawTransaction: Buffer,
  options?: ConfirmOptions,
): Promise<TransactionSignature>;

// eslint-disable-next-line no-redeclare
export async function sendAndConfirmRawTransaction(
  connection: Connection,
  rawTransaction: Buffer,
  confirmationStrategyOrConfirmOptions:
    | TransactionConfirmationStrategy
    | ConfirmOptions
    | undefined,
  maybeConfirmOptions?: ConfirmOptions,
): Promise<TransactionSignature> {
  let confirmationStrategy: TransactionConfirmationStrategy | undefined;
  let options: ConfirmOptions | undefined;
  if (
    confirmationStrategyOrConfirmOptions &&
    Object.prototype.hasOwnProperty.call(
      confirmationStrategyOrConfirmOptions,
      'lastValidBlockHeight',
    )
  ) {
    confirmationStrategy =
      confirmationStrategyOrConfirmOptions as BlockheightBasedTransactionConfirmationStrategy;
    options = maybeConfirmOptions;
  } else if (
    confirmationStrategyOrConfirmOptions &&
    Object.prototype.hasOwnProperty.call(
      confirmationStrategyOrConfirmOptions,
      'nonceValue',
    )
  ) {
    confirmationStrategy =
      confirmationStrategyOrConfirmOptions as DurableNonceTransactionConfirmationStrategy;
    options = maybeConfirmOptions;
  } else {
    options = confirmationStrategyOrConfirmOptions as
      | ConfirmOptions
      | undefined;
  }
  const sendOptions = options && {
    skipPreflight: options.skipPreflight,
    preflightCommitment: options.preflightCommitment || options.commitment,
    minContextSlot: options.minContextSlot,
  };

  const signature = await connection.sendRawTransaction(
    rawTransaction,
    sendOptions,
  );

  const commitment = options && options.commitment;
  const confirmationPromise = confirmationStrategy
    ? connection.confirmTransaction(confirmationStrategy, commitment)
    : connection.confirmTransaction(signature, commitment);
  const status = (await confirmationPromise).value;

  if (status.err) {
    if (signature != null) {
      throw new SendTransactionError({
        action: sendOptions?.skipPreflight ? 'send' : 'simulate',
        signature: signature,
        transactionMessage: `Status: (${JSON.stringify(status)})`,
      });
    }
    throw new Error(
      `Raw transaction ${signature} failed (${JSON.stringify(status)})`,
    );
  }

  return signature;
}
