import { ethers } from 'ethers';
import {
  buildMerkleRoot,
  effectiveTokenOrNativeWei,
  paymentLeafV1Hash,
} from '@dbis/checkpoint-core';
import type { PaymentLeaf } from './ingress/types';

export function effectiveValue(leaf: PaymentLeaf): bigint {
  return effectiveTokenOrNativeWei(leaf);
}

export function leafHashes(chainId: number, leaves: PaymentLeaf[]): string[] {
  return leaves.map((l) =>
    paymentLeafV1Hash(chainId, {
      txHash: l.txHash,
      from: l.from,
      to: l.to,
      value: effectiveValue(l),
      blockNumber: l.blockNumber,
      blockTimestamp: l.blockTimestamp,
      gasUsed: l.gasUsed,
      success: l.success,
    })
  );
}

export function paymentsRoot(chainId: number, leaves: PaymentLeaf[]): string {
  return buildMerkleRoot(leafHashes(chainId, leaves));
}

const PAYMENT_LEAF_TUPLE =
  'tuple(bytes32,address,address,uint256,uint256,uint64,uint256,bool)';

export function leavesToTuples(leaves: PaymentLeaf[]): [
  string,
  string,
  string,
  bigint,
  bigint,
  bigint,
  bigint,
  boolean,
][] {
  return leaves.map((l) => [
    l.txHash,
    l.from,
    l.to,
    effectiveValue(l),
    BigInt(l.blockNumber),
    BigInt(l.blockTimestamp),
    l.gasUsed,
    l.success,
  ]);
}

export function encodeExtensionLeavesV1(leaves: PaymentLeaf[]): string {
  return ethers.AbiCoder.defaultAbiCoder().encode(
    [PAYMENT_LEAF_TUPLE + '[]'],
    [leavesToTuples(leaves)]
  );
}

export function encodeExtensionLeavesV2(leaves: PaymentLeaf[]): string {
  const PAYMENT_LEAF_V2_TUPLE =
    'tuple(bytes32,address,address,address,uint256,uint256,uint64,uint256,bool,uint32)';
  const tuples = leaves.map((l) => [
    l.txHash,
    l.from,
    l.to,
    l.token ?? ethers.ZeroAddress,
    effectiveValue(l),
    BigInt(l.blockNumber),
    BigInt(l.blockTimestamp),
    l.gasUsed,
    l.success,
    l.tokenLogIndex ?? 0,
  ]);
  return ethers.AbiCoder.defaultAbiCoder().encode(
    ['bytes1', PAYMENT_LEAF_V2_TUPLE + '[]'],
    [ethers.toBeHex(0x02, 1), tuples]
  );
}
