import { Context, extractContractArgs, extractUser, getMethodArguments, User } from '@coinweb/contract-kit';
import { getCallParameters, getContractIssuer } from 'lib/onchain';

import { ResolvedOp, TypedClaim } from '../../types';
import { isResolvedBlockOp, isResolvedReadOp } from '../utils';

let rawContext: Context | null = null;

export const setRawContext = (ctx: Context) => {
  rawContext = ctx;
};

export const getRawContext = () => {
  if (!rawContext) {
    throw new Error('Context not set');
  }

  return rawContext;
};

const getMethodArgs = () => {
  return getMethodArguments(getRawContext()) as [
    methodName: string,
    initialArgs?: unknown[],
    resolvedOps?: ResolvedOp[],
    caller?: User,
    isChild?: boolean,
  ];
};

const extractResolvedOps = () => {
  const resolvedOps = extractContractArgs(getRawContext().tx);

  const extractedOps: ResolvedOp[] = [];

  let i = 0;

  while (i < resolvedOps.length) {
    const op = resolvedOps[i];

    if (isResolvedBlockOp(op)) {
      const { first } = op.BlockOp.blocks_on[0][0];

      //Maybe it is needed to check more conditions here
      if (Array.isArray(first) && first[0] === 'result') {
        const nextAfterBlock = resolvedOps[i + 1];

        if (!isResolvedReadOp(nextAfterBlock)) {
          throw new Error('Wrong subcall result');
        }

        extractedOps.push(
          { ChildOp: 0 },
          ...(nextAfterBlock.ReadOp.results[0].content as TypedClaim<ResolvedOp[]>).body
        );

        i += 2;
        continue;
      }
    }

    extractedOps.push(op);
    i++;
  }

  return extractedOps;
};

export const context = {
  get isChild() {
    return !!getMethodArgs()[4];
  },
  get ops() {
    console.log('Parse ops in context');

    const previousOps = getMethodArgs()[2] ?? [];
    const resolvedOps = extractResolvedOps();

    const allResolvedOps = [...previousOps, ...resolvedOps];

    console.log('new ops >>>', JSON.stringify(resolvedOps));

    return allResolvedOps;
  },
  get methodName() {
    return getMethodArguments(getRawContext())[0] as string;
  },
  get initialArgs() {
    return getMethodArgs()[1] ?? [];
  },
  get user() {
    const { authInfo } = getCallParameters(getRawContext());
    const provided = getMethodArgs()[3];

    const user = (authInfo && extractUser(authInfo)) ?? provided;

    if (!user) {
      throw new Error('User not found');
    }

    return user;
  },
  get issuer() {
    return getContractIssuer(getRawContext());
  },
  get parentTxId() {
    return getRawContext().call.txid;
  },
};
