import type { Address } from 'abitype'

import type {
  AuthorizationList,
  SignedAuthorizationList,
} from './authorization.js'
import type { BlobSidecar } from './eip4844.js'
import type {
  FeeValuesEIP1559,
  FeeValuesEIP4844,
  FeeValuesLegacy,
} from './fee.js'
import type { Kzg } from './kzg.js'
import type { Log } from './log.js'
import type { ByteArray, Hash, Hex, Signature } from './misc.js'
import type {
  Branded,
  ExactPartial,
  IsNever,
  Omit,
  OneOf,
  RequiredBy,
} from './utils.js'

export type AccessList = readonly {
  address: Address
  storageKeys: readonly Hex[]
}[]

export type TransactionType =
  | 'legacy'
  | 'eip1559'
  | 'eip2930'
  | 'eip4844'
  | 'eip7702'
  | (string & {})

export type TransactionReceipt<
  quantity = bigint,
  index = number,
  status = 'success' | 'reverted',
  type = TransactionType,
> = {
  /** The actual value per gas deducted from the sender's account for blob gas. Only specified for blob transactions as defined by EIP-4844. */
  blobGasPrice?: quantity | undefined
  /** The amount of blob gas used. Only specified for blob transactions as defined by EIP-4844. */
  blobGasUsed?: quantity | undefined
  /** Hash of block containing this transaction */
  blockHash: Hash
  /** Number of block containing this transaction */
  blockNumber: quantity
  /** Unix timestamp of when this block was included */
  blockTimestamp?: quantity | undefined
  /** Address of new contract or `null` if no contract was created */
  contractAddress: Address | null | undefined
  /** Gas used by this and all preceding transactions in this block */
  cumulativeGasUsed: quantity
  /** Pre-London, it is equal to the transaction's gasPrice. Post-London, it is equal to the actual gas price paid for inclusion. */
  effectiveGasPrice: quantity
  /** Transaction sender */
  from: Address
  /** Gas used by this transaction */
  gasUsed: quantity
  /** List of log objects generated by this transaction */
  logs: Log<quantity, index, false>[]
  /** Logs bloom filter */
  logsBloom: Hex
  /** The post-transaction state root. Only specified for transactions included before the Byzantium upgrade. */
  root?: Hash | undefined
  /** `success` if this transaction was successful or `reverted` if it failed */
  status: status
  /** Transaction recipient or `null` if deploying a contract */
  to: Address | null
  /** Hash of this transaction */
  transactionHash: Hash
  /** Index of this transaction in the block */
  transactionIndex: index
  /** Transaction type */
  type: type
}

export type TransactionBase<
  quantity = bigint,
  index = number,
  isPending extends boolean = boolean,
> = {
  /** Hash of block containing this transaction or `null` if pending */
  blockHash: isPending extends true ? null : Hash
  /** Number of block containing this transaction or `null` if pending */
  blockNumber: isPending extends true ? null : quantity
  /** Unix timestamp of when this block was included */
  blockTimestamp?: quantity | undefined
  /** Transaction sender */
  from: Address
  /** Gas provided for transaction execution */
  gas: quantity
  /** Hash of this transaction */
  hash: Hash
  /** Contract code or a hashed method call */
  input: Hex
  /** Unique number identifying this transaction */
  nonce: index
  /** ECDSA signature r */
  r: Hex
  /** ECDSA signature s */
  s: Hex
  /** Transaction recipient or `null` if deploying a contract */
  to: Address | null
  /** Index of this transaction in the block or `null` if pending */
  transactionIndex: isPending extends true ? null : index
  /** The type represented as hex. */
  typeHex: Hex | null
  /** ECDSA recovery ID */
  v: quantity
  /** Value in wei sent with this transaction */
  value: quantity
  /** The parity of the y-value of the secp256k1 signature. */
  yParity: index
}

export type TransactionLegacy<
  quantity = bigint,
  index = number,
  isPending extends boolean = boolean,
  type = 'legacy',
> = Omit<TransactionBase<quantity, index, isPending>, 'yParity'> & {
  /** EIP-2930 Access List. */
  accessList?: undefined
  authorizationList?: undefined
  blobVersionedHashes?: undefined
  /** Chain ID that this transaction is valid on. */
  chainId?: index | undefined
  yParity?: undefined
  type: type
} & FeeValuesLegacy<quantity>

export type TransactionEIP2930<
  quantity = bigint,
  index = number,
  isPending extends boolean = boolean,
  type = 'eip2930',
> = TransactionBase<quantity, index, isPending> & {
  /** EIP-2930 Access List. */
  accessList: AccessList
  authorizationList?: undefined
  blobVersionedHashes?: undefined
  /** Chain ID that this transaction is valid on. */
  chainId: index
  type: type
} & FeeValuesLegacy<quantity>

export type TransactionEIP1559<
  quantity = bigint,
  index = number,
  isPending extends boolean = boolean,
  type = 'eip1559',
> = TransactionBase<quantity, index, isPending> & {
  /** EIP-2930 Access List. */
  accessList: AccessList
  authorizationList?: undefined
  blobVersionedHashes?: undefined
  /** Chain ID that this transaction is valid on. */
  chainId: index
  type: type
} & FeeValuesEIP1559<quantity>

export type TransactionEIP4844<
  quantity = bigint,
  index = number,
  isPending extends boolean = boolean,
  type = 'eip4844',
> = TransactionBase<quantity, index, isPending> & {
  /** EIP-2930 Access List. */
  accessList: AccessList
  authorizationList?: undefined
  /** List of versioned blob hashes associated with the transaction's blobs. */
  blobVersionedHashes: readonly Hex[]
  /** Chain ID that this transaction is valid on. */
  chainId: index
  type: type
} & FeeValuesEIP4844<quantity>

export type TransactionEIP7702<
  quantity = bigint,
  index = number,
  isPending extends boolean = boolean,
  type = 'eip7702',
> = TransactionBase<quantity, index, isPending> & {
  /** EIP-2930 Access List. */
  accessList: AccessList
  /** Authorization list for the transaction. */
  authorizationList: SignedAuthorizationList
  blobVersionedHashes?: undefined
  /** Chain ID that this transaction is valid on. */
  chainId: index
  type: type
} & FeeValuesEIP1559<quantity>

export type Transaction<
  quantity = bigint,
  index = number,
  isPending extends boolean = boolean,
> = OneOf<
  | TransactionLegacy<quantity, index, isPending>
  | TransactionEIP2930<quantity, index, isPending>
  | TransactionEIP1559<quantity, index, isPending>
  | TransactionEIP4844<quantity, index, isPending>
  | TransactionEIP7702<quantity, index, isPending>
>

////////////////////////////////////////////////////////////////////////////////////////////
// Request
////////////////////////////////////////////////////////////////////////////////////////////

export type TransactionRequestBase<
  quantity = bigint,
  index = number,
  type = string,
> = {
  /** Contract code or a hashed method call with encoded args */
  data?: Hex | undefined
  /** Transaction sender */
  from?: Address | undefined
  /** Gas provided for transaction execution */
  gas?: quantity | undefined
  /** Unique number identifying this transaction */
  nonce?: index | undefined
  /** Transaction recipient */
  to?: Address | null | undefined
  /** Transaction type */
  type?: type | undefined
  /** Value in wei sent with this transaction */
  value?: quantity | undefined
}

export type TransactionRequestLegacy<
  quantity = bigint,
  index = number,
  type = 'legacy',
> = TransactionRequestBase<quantity, index, type> &
  ExactPartial<FeeValuesLegacy<quantity>>

export type TransactionRequestEIP2930<
  quantity = bigint,
  index = number,
  type = 'eip2930',
> = TransactionRequestBase<quantity, index, type> &
  ExactPartial<FeeValuesLegacy<quantity>> & {
    accessList?: AccessList | undefined
  }

export type TransactionRequestEIP1559<
  quantity = bigint,
  index = number,
  type = 'eip1559',
> = TransactionRequestBase<quantity, index, type> &
  ExactPartial<FeeValuesEIP1559<quantity>> & {
    accessList?: AccessList | undefined
  }

export type TransactionRequestEIP4844<
  quantity = bigint,
  index = number,
  type = 'eip4844',
> = RequiredBy<TransactionRequestBase<quantity, index, type>, 'to'> &
  ExactPartial<FeeValuesEIP4844<quantity>> & {
    accessList?: AccessList | undefined
    sidecars?: readonly BlobSidecar<Hex>[] | undefined
  } & OneOf<
    | {
        blobs?: readonly Hex[] | readonly ByteArray[] | undefined
        blobVersionedHashes: readonly Hex[]
      }
    | {
        blobs: readonly Hex[] | readonly ByteArray[]
        blobVersionedHashes?: readonly Hex[] | undefined
        kzg?: Kzg | undefined
      }
  >

export type TransactionRequestEIP7702<
  quantity = bigint,
  index = number,
  type = 'eip7702',
> = TransactionRequestBase<quantity, index, type> &
  ExactPartial<FeeValuesEIP1559<quantity>> & {
    accessList?: AccessList | undefined
    authorizationList?: AuthorizationList<index, boolean> | undefined
  }

export type TransactionRequest<quantity = bigint, index = number> = OneOf<
  | TransactionRequestLegacy<quantity, index>
  | TransactionRequestEIP2930<quantity, index>
  | TransactionRequestEIP1559<quantity, index>
  | TransactionRequestEIP4844<quantity, index>
  | TransactionRequestEIP7702<quantity, index>
>

export type TransactionRequestGeneric<
  quantity = bigint,
  index = number,
> = TransactionRequestBase<quantity, index> & {
  accessList?: AccessList | undefined
  blobs?: readonly Hex[] | readonly ByteArray[] | undefined
  blobVersionedHashes?: readonly Hex[] | undefined
  gasPrice?: quantity | undefined
  maxFeePerBlobGas?: quantity | undefined
  maxFeePerGas?: quantity | undefined
  maxPriorityFeePerGas?: quantity | undefined
  type?: string | undefined
}

////////////////////////////////////////////////////////////////////////////////////////////
// Serializable
////////////////////////////////////////////////////////////////////////////////////////////

export type TransactionSerializedEIP1559 = `0x02${string}`
export type TransactionSerializedEIP2930 = `0x01${string}`
export type TransactionSerializedEIP4844 = `0x03${string}`
export type TransactionSerializedEIP7702 = `0x04${string}`
export type TransactionSerializedLegacy = Branded<`0x${string}`, 'legacy'>
export type TransactionSerializedGeneric = `0x${string}`
export type TransactionSerialized<
  type extends TransactionType = TransactionType,
  result =
    | (type extends 'eip1559' ? TransactionSerializedEIP1559 : never)
    | (type extends 'eip2930' ? TransactionSerializedEIP2930 : never)
    | (type extends 'eip4844' ? TransactionSerializedEIP4844 : never)
    | (type extends 'eip7702' ? TransactionSerializedEIP7702 : never)
    | (type extends 'legacy' ? TransactionSerializedLegacy : never),
> = IsNever<result> extends true ? TransactionSerializedGeneric : result

export type TransactionSerializableBase<
  quantity = bigint,
  index = number,
> = Omit<TransactionRequestBase<quantity, index>, 'from'> &
  ExactPartial<Signature>

export type TransactionSerializableLegacy<
  quantity = bigint,
  index = number,
> = TransactionSerializableBase<quantity, index> &
  ExactPartial<FeeValuesLegacy<quantity>> & {
    chainId?: number | undefined
    type?: 'legacy' | undefined
  }

export type TransactionSerializableEIP2930<
  quantity = bigint,
  index = number,
> = TransactionSerializableBase<quantity, index> &
  ExactPartial<FeeValuesLegacy<quantity>> & {
    accessList?: AccessList | undefined
    chainId: number
    type?: 'eip2930' | undefined
    yParity?: number | undefined
  }

export type TransactionSerializableEIP1559<
  quantity = bigint,
  index = number,
> = TransactionSerializableBase<quantity, index> &
  ExactPartial<FeeValuesEIP1559<quantity>> & {
    accessList?: AccessList | undefined
    chainId: number
    type?: 'eip1559' | undefined
    yParity?: number | undefined
  }

export type TransactionSerializableEIP4844<
  quantity = bigint,
  index = number,
  nullableSidecars extends boolean = boolean,
> = RequiredBy<TransactionSerializableBase<quantity, index>, 'to'> &
  ExactPartial<FeeValuesEIP4844<quantity>> & {
    accessList?: AccessList | undefined
    chainId: number
    sidecars?:
      | readonly BlobSidecar<Hex>[]
      | (nullableSidecars extends true ? false : never)
      | undefined
    type?: 'eip4844' | undefined
    yParity?: number | undefined
  } & OneOf<
    | {
        blobs?: readonly Hex[] | readonly ByteArray[] | undefined
        blobVersionedHashes: readonly Hex[]
      }
    | {
        blobs: readonly Hex[] | readonly ByteArray[]
        blobVersionedHashes?: readonly Hex[] | undefined
        kzg: Kzg
      }
  >

export type TransactionSerializableEIP7702<
  quantity = bigint,
  index = number,
> = TransactionSerializableBase<quantity, index> &
  ExactPartial<FeeValuesEIP1559<quantity>> & {
    accessList?: AccessList | undefined
    authorizationList: SignedAuthorizationList
    chainId: number
    type?: 'eip7702' | undefined
    yParity?: number | undefined
  }

export type TransactionSerializable<quantity = bigint, index = number> = OneOf<
  | TransactionSerializableLegacy<quantity, index>
  | TransactionSerializableEIP2930<quantity, index>
  | TransactionSerializableEIP1559<quantity, index>
  | TransactionSerializableEIP4844<quantity, index>
  | TransactionSerializableEIP7702<quantity, index>
>

export type TransactionSerializableGeneric<
  quantity = bigint,
  index = number,
> = TransactionSerializableBase<quantity, index> & {
  accessList?: AccessList | undefined
  authorizationList?: AuthorizationList<index, boolean> | undefined
  blobs?: readonly Hex[] | readonly ByteArray[] | undefined
  blobVersionedHashes?: readonly Hex[] | undefined
  chainId?: number | undefined
  gasPrice?: quantity | undefined
  maxFeePerBlobGas?: quantity | undefined
  maxFeePerGas?: quantity | undefined
  maxPriorityFeePerGas?: quantity | undefined
  sidecars?: readonly BlobSidecar<Hex>[] | false | undefined
  type?: string | undefined
}
