import {Abi, Address, createPublicClient, createWalletClient, http, PublicClient, WalletClient} from "viem";

import {Accounts} from "./modules/accounts/accounts.js";
import {BATCH_CONFIGS} from "./configs/batch.js";
import {getChain} from "./configs/chains.js";
import {Markets} from "./modules/markets/index.js";
import {Oracle} from "./modules/oracle.js";
import {Orders} from "./modules/orders/orders.js";
import {Positions} from "./modules/positions/positions.js";
import {Tokens} from "./modules/tokens/tokens.js";
import {Trades} from "./modules/trades/trades.js";
import {Utils} from "./modules/utils/utils.js";
import {GmxSdkConfig} from "./types/sdk.js";
import {callContract, CallContractOpts} from "./utils/callContract.js";
import {MAX_TIMEOUT, Multicall, MulticallRequestConfig} from "./utils/multicall.js";

export class GmxSdk {
  public readonly markets = new Markets(this);
  public readonly tokens = new Tokens(this);
  public readonly positions = new Positions(this);
  public readonly orders = new Orders(this);
  public readonly trades = new Trades(this);
  public readonly accounts = new Accounts(this);
  public readonly utils = new Utils(this);
  public readonly oracle: Oracle;

  public publicClient: PublicClient;
  public walletClient: WalletClient;

  constructor(public config: GmxSdkConfig) {
    this.oracle = new Oracle(this);

    if (config.publicClient) {
      this.publicClient = config.publicClient;
    } else {
      // Use any to bypass TypeScript's deep type checking
      const clientParams: any = {
        transport: http(this.config.rpcUrl, {
          retryCount: 0,
          retryDelay: 10000000,
          batch: BATCH_CONFIGS[this.config.chainId].http,
          timeout: MAX_TIMEOUT,
        }),
        pollingInterval: undefined,
        batch: BATCH_CONFIGS[this.config.chainId].client,
        chain: getChain(this.config.chainId),
      };
      this.publicClient = createPublicClient(clientParams) as any;
    }

    this.walletClient =
      config.walletClient ??
      createWalletClient({
        account: config.account as Address,
        chain: getChain(config.chainId),
        transport: http(config.rpcUrl, {
          retryCount: 0,
          retryDelay: 10000000,
          batch: BATCH_CONFIGS[config.chainId].http,
          timeout: MAX_TIMEOUT,
        }),
      });
  }

  setAccount(account: Address) {
    this.config.account = account;
  }

  async executeMulticall<T = any>(request: MulticallRequestConfig<any>) {
    const multicall = await Multicall.getInstance(this);
    return multicall?.call(request, MAX_TIMEOUT) as Promise<T>;
  }

  async callContract(address: Address, abi: Abi, method: string, params: any[], opts?: CallContractOpts) {
    return callContract(this, address, abi, method, params, opts);
  }

  get chainId() {
    return this.config.chainId;
  }

  get chain() {
    return getChain(this.chainId);
  }

  get account() {
    return this.config.account as Address;
  }
}
