import {
  Address,
  beginCell,
  Cell,
  Contract,
  contractAddress,
  ContractProvider,
  Sender,
  SendMode,
  TupleItem,
  Dictionary,
} from "@ton/core";

export type VerifierConfig = {};

export function verifierConfigToCell(config: VerifierConfig): Cell {
  return beginCell().endCell();
}

export const Opcodes = {
  verify: 0x3b3cca17,
};

export class Verifier implements Contract {
  constructor(
    readonly address: Address,
    readonly init?: { code: Cell; data: Cell },
  ) {}

  static createFromAddress(address: Address) {
    return new Verifier(address);
  }

  static createFromConfig(config: VerifierConfig, code: Cell, workchain = 0) {
    const data = verifierConfigToCell(config);
    const init = { code, data };
    return new Verifier(contractAddress(workchain, init), init);
  }

  async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
    await provider.internal(via, {
      value,
      sendMode: SendMode.PAY_GAS_SEPARATELY,
      body: beginCell().endCell(),
    });
  }

  async sendVerify(
    provider: ContractProvider,
    via: Sender,
    opts: {
      pi_a: Buffer;
      pi_b: Buffer;
      pi_c: Buffer;
      pubInputs: bigint[];
      value: bigint;
      queryID?: number;
    },
  ) {
    await provider.internal(via, {
      value: opts.value,
      sendMode: SendMode.PAY_GAS_SEPARATELY,
      body: this.buildVerifyBody(opts),
    });
  }

  dictFromInputList(list: bigint[]): Dictionary<bigint, bigint> {
    const dict: Dictionary<bigint, bigint> = Dictionary.empty(
      Dictionary.Keys.BigUint(32),
      Dictionary.Values.BigInt(256),
    );
    for (let i = 0; i < list.length; i++) {
      dict.set(BigInt(i), list[i]);
    }
    return dict;
  }

  buildVerifyBody(opts: {
    pi_a: Buffer;
    pi_b: Buffer;
    pi_c: Buffer;
    pubInputs: bigint[];
  }) {
    const piAcell = beginCell().storeBuffer(opts.pi_a).endCell();
    const piBcell = beginCell().storeBuffer(opts.pi_b).endCell();
    const piCcell = beginCell().storeBuffer(opts.pi_c).endCell();

    const pubDict = this.dictFromInputList(opts.pubInputs);

    const body = beginCell()
      .storeUint(Opcodes.verify, 32)
      .storeRef(piAcell)
      .storeRef(piBcell)
      .storeRef(piCcell)
      .storeDict(pubDict)
      .endCell();

    return body;
  }

  async getVerify(
    provider: ContractProvider,
    opts: {
      pi_a: Buffer;
      pi_b: Buffer;
      pi_c: Buffer;
      pubInputs: bigint[];
    },
  ): Promise<boolean> {
    const pi_a = {
      type: "slice",
      cell: beginCell().storeBuffer(opts.pi_a).endCell(),
    } as TupleItem;
    const pi_b = {
      type: "slice",
      cell: beginCell().storeBuffer(opts.pi_b).endCell(),
    } as TupleItem;
    const pi_c = {
      type: "slice",
      cell: beginCell().storeBuffer(opts.pi_c).endCell(),
    } as TupleItem;
    const pubInputs = {
      type: "slice",
      cell: beginCell()
        .storeDict(this.dictFromInputList(opts.pubInputs))
        .endCell(),
    } as TupleItem;

    const result = await provider.get("verify", [pi_a, pi_b, pi_c, pubInputs]);
    return result.stack.readBoolean();
  }
}
