import {
  constructContinueTx,
  constructContractRef,
  FullCallInfo,
  GTake,
  NewTx,
  PreparedOperation,
} from '@coinweb/contract-kit';
import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';

import { ExecutorMethodArgs, PreparedExtendedStoreOp, PreparedOp } from '../../../types';
import { context, getRawContext } from '../../context';
import { constructLockCall, constructExecOpsCall, constructUnlockCall } from '../../mutex';
import {
  isPreparedBlockOp,
  isPreparedExecOp,
  isPreparedLockOp,
  isPreparedStoreOp,
  isPreparedTakeOp,
  isPreparedUnlockOp,
} from '../../utils/opTypeGuards';

export const prepareOutThreadTxs = ({
  ops,
  cwebPerCall,
}: {
  ops: PreparedOp[];
  cwebPerCall: bigint;
}): {
  txs: NewTx[];
  txFee: bigint;
  calls: number;
} => {
  const siblingCallResolvedOps = [...context.ops];

  const preparedCalls: FullCallInfo[] = [];
  const preparedOps: PreparedOperation[] = [];
  const preparedExecOps: (PreparedExtendedStoreOp | GTake<CwebTake>)[] = [];

  let txFee = 0n;
  let callsPrepared = 0;

  ops.forEach((op) => {
    switch (true) {
      case isPreparedExecOp(op): {
        const id = op.ExecOp.id;

        const callInfo = {
          callInfo: {
            ref: constructContractRef(context.issuer, []),
            methodInfo: {
              methodName: context.methodName,
              methodArgs: [
                context.initialArgs,
                [...siblingCallResolvedOps, { ExecOp: { id } }],
                context.user,
                id,
                context.parentId ?? context.thisId,
                false,
              ] satisfies ExecutorMethodArgs,
            },
            contractInfo: {
              providedCweb: cwebPerCall,
              authenticated: null,
            },
            contractArgs: [],
          },
        };

        preparedCalls.push(callInfo);
        txFee += 700n;

        callsPrepared++;
        break;
      }
      case isPreparedStoreOp(op):
      case isPreparedTakeOp(op): {
        preparedExecOps.push(op);
        break;
      }
      case isPreparedLockOp(op): {
        const { callInfo, fee, outThreadOps } = constructLockCall({
          issuer: context.issuer,
          lockId: op.LockOp.lockId,
          timestamp: op.LockOp.timestamp,
          keys: op.LockOp.keys,
          processId: context.thisId,
        });

        preparedCalls.push({ callInfo });
        txFee += fee;

        preparedOps.push(...outThreadOps);

        break;
      }
      case isPreparedUnlockOp(op): {
        console.log('prepareOutThreadTxs >>> unlockOp');
        const { callInfo, fee } = constructUnlockCall(context.issuer, op.UnlockOp.lockId, op.UnlockOp.timestamp, false);

        preparedCalls.push({ callInfo });
        txFee += fee;

        break;
      }
      case isPreparedBlockOp(op): {
        break;
      }
      default:
        preparedOps.push(op);
        txFee += 100n;
    }

    siblingCallResolvedOps.push({ SlotOp: { ok: true } });
  });

  if (preparedExecOps.length > 0) {
    const { callInfo, fee } = constructExecOpsCall({
      issuer: context.issuer,
      ops: preparedExecOps,
      processId: context.thisId,
    });

    preparedCalls.push({ callInfo });
    txFee += fee;
  }

  if (preparedCalls.length > 0 || preparedOps.length > 0) {
    return {
      txs: [constructContinueTx(getRawContext(), preparedOps, preparedCalls)],
      txFee,
      calls: callsPrepared,
    };
  }

  return {
    txs: [],
    txFee: 0n,
    calls: 0,
  };
};
