import type {
  Account,
  Address,
  BaseError,
  Chain,
  Client,
  Hex,
  PublicClient,
  SendTransactionRequest,
  Transport,
  WalletClient,
} from "viem";
import { getChainId, sendRawTransaction } from "viem/actions";
import {
  assertCurrentChain,
  type GetTransactionErrorParameters,
  getAction,
  getTransactionError,
  parseAccount,
} from "viem/utils";
import type {
  ChainEIP712,
  SendEip712TransactionParameters,
  SendEip712TransactionReturnType,
} from "viem/zksync";

import { INSUFFICIENT_BALANCE_SELECTOR } from "../constants.js";
import { AccountNotFoundError } from "../errors/account.js";
import { InsufficientBalanceError } from "../errors/insufficientBalance.js";
import type { CustomPaymasterHandler } from "../types/customPaymaster.js";
import { prepareTransactionRequest } from "./prepareTransaction.js";
import { signTransaction } from "./signTransaction.js";

export async function sendTransactionInternal<
  const request extends SendTransactionRequest<chain, chainOverride>,
  chain extends ChainEIP712 | undefined = ChainEIP712 | undefined,
  account extends Account | undefined = Account | undefined,
  chainOverride extends ChainEIP712 | undefined = ChainEIP712 | undefined,
  TReturnType = SendEip712TransactionReturnType,
>(
  client: Client<Transport, ChainEIP712, Account>,
  signerClient: WalletClient<Transport, ChainEIP712, Account>,
  publicClient: PublicClient<Transport, ChainEIP712>,
  parameters: SendEip712TransactionParameters<
    chain,
    account,
    chainOverride,
    request
  >,
  validator: Address,
  validationHookData: Record<string, Hex> = {},
  customPaymasterHandler: CustomPaymasterHandler | undefined = undefined,
  sendSerializedTransaction?: (
    serializedTransaction: `0x${string}`,
  ) => Promise<TReturnType>,
): Promise<TReturnType> {
  const { chain = client.chain } = parameters;

  if (!signerClient.account)
    throw new AccountNotFoundError({
      docsPath: "/docs/actions/wallet/sendTransaction",
    });
  const account = parseAccount(signerClient.account);

  try {
    // assertEip712Request(parameters)

    // Prepare the request for signing (assign appropriate fees, etc.)
    const request = await prepareTransactionRequest(
      client,
      signerClient,
      publicClient,
      {
        ...parameters,
        parameters: ["gas", "nonce", "fees"],
        isSponsored:
          customPaymasterHandler !== undefined ||
          (parameters as any).paymaster !== undefined,
        nonceManager: account.nonceManager,
      } as any,
    );

    let chainId: number | undefined;
    if (chain !== null) {
      chainId = await getAction(signerClient, getChainId, "getChainId")({});
      assertCurrentChain({
        currentChainId: chainId,
        chain,
      });
    }

    const serializedTransaction = await signTransaction(
      client,
      signerClient,
      publicClient,
      {
        ...request,
        chainId,
      } as any,
      validator,
      validationHookData,
      customPaymasterHandler,
    );
    if (sendSerializedTransaction) {
      return await sendSerializedTransaction(serializedTransaction);
    }
    return (await getAction(
      client,
      sendRawTransaction,
      "sendRawTransaction",
    )({
      serializedTransaction,
    })) as TReturnType;
  } catch (err) {
    if (
      err instanceof Error &&
      err.message.includes(INSUFFICIENT_BALANCE_SELECTOR)
    ) {
      throw new InsufficientBalanceError();
    }
    throw getTransactionError(err as BaseError, {
      ...(parameters as GetTransactionErrorParameters),
      account,
      chain: chain as Chain,
    });
  }
}
