import {
  type Account,
  type Address,
  type Client,
  createClient,
  createPublicClient,
  createWalletClient,
  http,
  type Transport,
} from "viem";
import { toAccount } from "viem/accounts";
import type { ChainEIP712 } from "viem/zksync";

import type { CustomPaymasterHandler } from "../types/customPaymaster.js";
import { getSmartAccountAddressFromInitialSigner } from "../utils.js";
import {
  type AbstractWalletActions,
  globalWalletActions,
} from "./decorators/abstract.js";

/**
 * Parameters for creating an AbstractClient instance.
 * @interface CreateAbstractClientParameters
 */
export interface CreateAbstractClientParameters {
  /**
   * The account used for signing AGW transactions.
   * @type {Account}
   */
  signer: Account;

  /**
   * The chain configuration supporting EIP-712.
   * @type {ChainEIP712}
   */
  chain: ChainEIP712;

  /**
   * Optional transport layer for network communication.
   * If not provided, a default HTTP transport will be used.
   * @type {Transport}
   * @optional
   */
  transport?: Transport;

  /**
   * The address of the smart account.
   * @type {Address}
   * @optional
   */
  address?: Address;

  /**
   * Whether the client is a Privy cross-app client.
   * @type {boolean}
   * @optional
   */
  isPrivyCrossApp?: boolean;

  /**
   * The transport layer for the underlying public client.
   * @type {Transport}
   * @optional
   */
  publicTransport?: Transport;

  /**
   * The custom paymaster handler.
   * @type {CustomPaymasterHandler}
   * @optional
   */
  customPaymasterHandler?: CustomPaymasterHandler;
}

type AbstractClientActions<
  chain extends ChainEIP712 | undefined = ChainEIP712 | undefined,
  account extends Account | undefined = Account | undefined,
> = AbstractWalletActions<chain, account>;

export type AbstractClient = Client<Transport, ChainEIP712, Account> &
  AbstractClientActions<ChainEIP712, Account>;

export async function createAbstractClient({
  signer,
  chain,
  transport,
  address,
  isPrivyCrossApp = false,
  publicTransport = http(undefined, {
    batch: true,
  }),
  customPaymasterHandler,
}: CreateAbstractClientParameters): Promise<AbstractClient> {
  if (!transport) {
    throw new Error("Transport is required");
  }

  const publicClient = createPublicClient({
    chain: chain,
    transport: publicTransport,
  });

  const smartAccountAddress =
    address ??
    (await getSmartAccountAddressFromInitialSigner(
      signer.address,
      publicClient,
    ));

  const baseClient = createClient({
    account: toAccount(smartAccountAddress),
    chain: chain,
    transport,
  });

  // Create a signer wallet client to handle actual signing
  const signerWalletClient = createWalletClient({
    account: signer,
    chain: chain,
    transport,
  });

  const abstractClient = baseClient.extend(
    globalWalletActions(
      signerWalletClient,
      publicClient,
      isPrivyCrossApp,
      customPaymasterHandler,
    ),
  );
  return abstractClient as AbstractClient;
}
