import { PythGovernanceActionImpl } from "./PythGovernanceAction";
import * as BufferLayout from "@solana/buffer-layout";
import * as BufferLayoutExt from "./BufferLayoutExt";
import { ChainName } from "../chains";

/** Executes an action from the executor contract via the specified executorAddress, callAddress, value, and calldata */
export class EvmExecute extends PythGovernanceActionImpl {
  static layout: BufferLayout.Structure<
    Readonly<{
      executorAddress: string;
      callAddress: string;
      value: bigint;
      calldata: Uint8Array;
    }>
  > = BufferLayout.struct([
    BufferLayoutExt.hexBytes(20, "executorAddress"),
    BufferLayoutExt.hexBytes(20, "callAddress"),
    BufferLayoutExt.u256be("value"),
    BufferLayout.blob(new BufferLayout.GreedyCount(), "calldata"),
  ]);

  constructor(
    targetChainId: ChainName,
    readonly executorAddress: string,
    readonly callAddress: string,
    readonly value: bigint,
    readonly calldata: Buffer
  ) {
    super(targetChainId, "Execute");
  }

  static decode(data: Buffer): EvmExecute | undefined {
    const decoded = PythGovernanceActionImpl.decodeWithPayload(
      data,
      "Execute",
      this.layout
    );
    if (!decoded) return undefined;

    return new EvmExecute(
      decoded[0].targetChainId,
      decoded[1].executorAddress,
      decoded[1].callAddress,
      decoded[1].value,
      Buffer.from(decoded[1].calldata)
    );
  }

  encode(): Buffer {
    // encodeWithPayload creates a buffer using layout.span but EvmExecute.layout span is -1
    // because the calldata length is unknown. So we create a layout with a known calldata length
    // and use that for encoding
    const layout_with_known_span: BufferLayout.Structure<
      Readonly<{
        executorAddress: string;
        callAddress: string;
        value: bigint;
        calldata: Uint8Array;
      }>
    > = BufferLayout.struct([
      BufferLayoutExt.hexBytes(20, "executorAddress"),
      BufferLayoutExt.hexBytes(20, "callAddress"),
      BufferLayoutExt.u256be("value"),
      BufferLayout.blob(this.calldata.length, "calldata"),
    ]);
    return super.encodeWithPayload(layout_with_known_span, {
      executorAddress: this.executorAddress,
      callAddress: this.callAddress,
      value: this.value,
      calldata: this.calldata,
    });
  }
}
