import { constructStore } from '@coinweb/contract-kit/dist/esm/operations/store';

import { constructResultClaim } from '../../../claims/result';
import { stopExecution } from '../../../executor';
import { opMarker } from '../../../global';
import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp } from '../../../utils';
import { uuid } from '../../../utils';
import { getAwaitedTasksCount, pushAwaitedTask } from '../../runtime/awaitedTasks';
import { getUsedOps, saveUsedOps, shiftResolvedOp } from '../../runtime/resolvedOps';

let isRootDetected = false;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const cwait = <TAsyncCallback extends (...args: any[]) => Promise<unknown>>(asyncCallback: TAsyncCallback) => {
  let isRoot = false;

  return (async (...args: Parameters<TAsyncCallback>) => {
    if (!isRootDetected) {
      isRootDetected = true;
      isRoot = true;
    }

    if (isRoot) {
      return asyncCallback(...args);
    }

    const { op, isOp } = shiftResolvedOp();

    if (!isOp) {
      pushAwaitedTask({
        ExecOp: {
          id: uuid(),
        },
      });

      const result = new Promise(() => null) as Promise<unknown> & { [opMarker]: boolean };

      result[opMarker] = true;

      return result as ReturnType<TAsyncCallback>;
    } else {
      if (isResolvedSlotOp(op)) {
        return new Promise(() => null);
      }

      if (isResolvedExecOp(op)) {
        saveUsedOps();

        asyncCallback(...args);

        if (!getAwaitedTasksCount()) {
          pushAwaitedTask(constructStore(constructResultClaim(op.ExecOp.id, getUsedOps())));
        }

        stopExecution();

        return new Promise(() => null);
      }

      if (isResolvedChildOp(op)) {
        return asyncCallback(...args);
      }

      throw new Error('Read operation not found');
    }
  }) as TAsyncCallback;
};
