import {
  type Account,
  type Address,
  type Chain,
  type Client,
  createClient,
  createWalletClient,
  getContract,
  type Hex,
  http,
  type Transport,
  type WalletClient,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { arbitrum, base, optimism } from 'viem/chains';
import { ChainID, type SupportedEvmChain } from '../../chains.js';
import type { EVMConfig } from '../../config.js';
import { ERC20ABI } from './abi/erc20.js';
import { SOURCE_CHAIN_GUARD_ABI } from './abi/source-chain-guard.js';
import { PERMIT2_ABI } from './abi/permit2.js';
import { SINGLE_CHAIN_GUARD_LIMIT_ABI } from './abi/single-chain-guard-limit.js';
import { hyperEVM } from './connectors/hyperevm.js';

export const chainIdToProviderChainMap: Record<SupportedEvmChain, Chain> = {
  [ChainID.Arbitrum]: arbitrum,
  [ChainID.Optimism]: optimism,
  [ChainID.Base]: base,
  [ChainID.Hyperliquid]: hyperEVM,
};

export class ChainProvider {
  private readonly privateKey: Hex;
  public readonly chainId: SupportedEvmChain;
  public readonly publicClient: Client;
  public readonly walletClient: WalletClient<Transport, Chain, Account>;

  constructor(config: EVMConfig) {
    this.privateKey = config.privateKey as Hex;
    this.chainId = config.chainId;

    this.publicClient = this.getClient(config.rpcProviderUrl);
    this.walletClient = this.getWalletClient(config.rpcProviderUrl);
  }

  static getClient(chainId: SupportedEvmChain, rpcProviderUrl?: string): Client {
    const chain = chainIdToProviderChainMap[chainId];

    return createClient({
      chain,
      transport: http(rpcProviderUrl),
    });
  }

  getClient(rpcProviderUrl?: string): Client {
    return ChainProvider.getClient(this.chainId, rpcProviderUrl);
  }

  getAccount(): Account {
    return privateKeyToAccount(this.privateKey);
  }

  getWalletClient(rpcProviderUrl?: string): WalletClient<Transport, Chain, Account> {
    const chain = chainIdToProviderChainMap[this.chainId];

    return createWalletClient({
      account: privateKeyToAccount(this.privateKey),
      chain,
      transport: http(rpcProviderUrl),
    });
  }

  getPermit2Contract(permit2Address: Address) {
    return getContract({
      address: permit2Address,
      abi: PERMIT2_ABI,
      client: {
        wallet: this.walletClient,
        public: this.publicClient,
        chain: chainIdToProviderChainMap[this.chainId],
        account: this.walletClient.account,
      },
    });
  }

  getSingleChainAuctioneerContract(address: Address) {
    return getContract({
      address: address,
      abi: SINGLE_CHAIN_GUARD_LIMIT_ABI,
      client: {
        wallet: this.walletClient,
        public: this.publicClient,
        chain: chainIdToProviderChainMap[this.chainId],
        account: this.walletClient.account,
      },
    });
  }

  // Any type since the inferred type of this node exceeds the maximum length the compiler will serialize
  getCrossChainAuctioneerContract(auctioneerAddress: Address): any {
    return getContract({
      address: auctioneerAddress,
      abi: SOURCE_CHAIN_GUARD_ABI,
      client: {
        wallet: this.walletClient,
        public: this.publicClient,
        chain: chainIdToProviderChainMap[this.chainId],
        account: this.walletClient.account,
      },
    });
  }

  getERC20Contract(tokenAddress: Address) {
    return getContract({
      address: tokenAddress,
      abi: ERC20ABI,
      client: {
        wallet: this.walletClient,
        public: this.publicClient,
        chain: chainIdToProviderChainMap[this.chainId],
        account: this.walletClient.account,
      },
    });
  }
}
