import {
  constructBlock,
  constructContinueTx,
  constructContractRef,
  FullCallInfo,
  NewTx,
  PreparedOperation,
} from '@coinweb/contract-kit';
import { CwebBlock } from '@coinweb/contract-kit/dist/esm/operations/block';
import { GBlock } from '@coinweb/contract-kit/dist/types/operations/generics';

import { ExecutorMethodArgs, ResolvedOp, ResolvedSlotOp, Task } from '../../../types';
import { constructResultBlockFilter, constructResultClaimRead } from '../../claims/result';
import { context, getRawContext } from '../../context';
import { isPreparedBlockOp, isPreparedExecOp } from '../typeGuards';

export const prepareInThreadTxs = ({
  cwebPerCall,
  outThread,
  outThreadFee,
  tasks,
}: {
  tasks: Task[];
  cwebPerCall: bigint;
  outThread: number;
  outThreadFee: bigint;
}) => {
  let txFee = 0n;
  let callsPrepared = 0;
  const resolvedSlotOps = new Array(outThread).fill({ SlotOp: 0 }) satisfies ResolvedSlotOp[];

  const resolvedChildOps: ResolvedOp[] = [...context.ops, ...resolvedSlotOps];

  //Arg for the main call
  const callArgs: PreparedOperation[] = [];

  //Info for separate child call
  const childCalls: FullCallInfo[] = [];

  //Block ops for separate child call
  const childBlocks = tasks
    .filter((task): task is { op: GBlock<CwebBlock>; batchId: number } => isPreparedBlockOp(task.op))
    .map(({ op }) => op);

  tasks.forEach((task) => {
    if (isPreparedExecOp(task.op)) {
      console.log('Child call info');
      const id = task.op.ExecOp.id;

      callArgs.push(constructBlock([constructResultBlockFilter(id)]), constructResultClaimRead(id));
      txFee += 200n;

      childCalls.push({
        callInfo: {
          ref: constructContractRef(context.issuer, []),
          methodInfo: {
            methodName: context.methodName,
            methodArgs: [
              context.initialArgs,
              [...resolvedChildOps, { ExecOp: { id } }],
              context.user,
              true,
            ] satisfies ExecutorMethodArgs,
          },
          contractInfo: {
            providedCweb: cwebPerCall - 700n,
            authenticated: null,
          },
          contractArgs: [],
        },
      });

      callsPrepared++;
    } else {
      callArgs.push(task.op);
      txFee += 100n;
    }

    resolvedChildOps.push({ SlotOp: 0 });
  });

  const returnTxs: NewTx[] = [];

  if (callArgs.length) {
    const latestCallArg = callArgs.at(-1)!;

    if ('StoreOp' in latestCallArg && (latestCallArg.StoreOp.key.first_part as [string])[0] === 'result') {
      if (callArgs.length > 1) {
        throw new Error('Unexpected count of result ops');
      }

      returnTxs.push(constructContinueTx(getRawContext(), [latestCallArg]));
    } else {
      txFee += 700n + outThreadFee;
      callsPrepared++;

      returnTxs.push(
        constructContinueTx(
          getRawContext(),
          [],
          [
            {
              callInfo: {
                ref: constructContractRef(context.issuer, []),
                methodInfo: {
                  methodName: context.methodName,
                  methodArgs: [
                    context.initialArgs,
                    [...context.ops, ...resolvedSlotOps],
                    context.user,
                    false,
                  ] satisfies ExecutorMethodArgs,
                },
                contractInfo: {
                  providedCweb: cwebPerCall - txFee,
                  authenticated: null,
                },
                contractArgs: callArgs,
              },
            },
          ]
        )
      );

      if (childCalls.length) {
        returnTxs.push(constructContinueTx(getRawContext(), childBlocks, childCalls));
      }
    }
  }

  return { txs: returnTxs, calls: callsPrepared };
};
