import _classPrivateFieldLooseBase from "@babel/runtime/helpers/esm/classPrivateFieldLooseBase";
import _classPrivateFieldLooseKey from "@babel/runtime/helpers/esm/classPrivateFieldLooseKey";
// Copyright 2017-2021 @polkadot/api authors & contributors
// SPDX-License-Identifier: Apache-2.0

/* eslint-disable no-dupe-class-members */
import { catchError, first, map, mapTo, mergeMap, of, switchMap, tap } from 'rxjs';
import { assert, isBn, isFunction, isNumber, isString, isU8a, objectSpread } from '@polkadot/util';
import { filterEvents, isKeyringPair } from "../util/index.js";
import { SubmittableResult } from "./Result.js";

const identity = input => input;

function makeEraOptions(api, registry, partialOptions, {
  header,
  mortalLength,
  nonce
}) {
  if (!header) {
    if (isNumber(partialOptions.era)) {
      // since we have no header, it is immortal, remove any option overrides
      // so we only supply the genesisHash and no era to the construction
      delete partialOptions.era;
      delete partialOptions.blockHash;
    }

    return makeSignOptions(api, partialOptions, {
      nonce
    });
  }

  return makeSignOptions(api, partialOptions, {
    blockHash: header.hash,
    era: registry.createType('ExtrinsicEra', {
      current: header.number,
      period: partialOptions.era || mortalLength
    }),
    nonce
  });
}

function makeSignAndSendOptions(partialOptions, statusCb) {
  let options = {};

  if (isFunction(partialOptions)) {
    statusCb = partialOptions;
  } else {
    options = objectSpread({}, partialOptions);
  }

  return [options, statusCb];
}

function makeSignOptions(api, partialOptions, extras) {
  return objectSpread({
    blockHash: api.genesisHash,
    genesisHash: api.genesisHash
  }, partialOptions, extras, {
    runtimeVersion: api.runtimeVersion,
    signedExtensions: api.registry.signedExtensions,
    version: api.extrinsicType
  });
}

function optionsOrNonce(partialOptions = {}) {
  return isBn(partialOptions) || isNumber(partialOptions) ? {
    nonce: partialOptions
  } : partialOptions;
}

export function createClass({
  api,
  apiType,
  decorateMethod
}) {
  // an instance of the base extrinsic for us to extend
  const ExtrinsicBase = api.registry.createClass('Extrinsic');

  var _ignoreStatusCb = /*#__PURE__*/_classPrivateFieldLooseKey("ignoreStatusCb");

  var _transformResult = /*#__PURE__*/_classPrivateFieldLooseKey("transformResult");

  var _observeSign = /*#__PURE__*/_classPrivateFieldLooseKey("observeSign");

  var _observeStatus = /*#__PURE__*/_classPrivateFieldLooseKey("observeStatus");

  var _observeSend = /*#__PURE__*/_classPrivateFieldLooseKey("observeSend");

  var _observeSubscribe = /*#__PURE__*/_classPrivateFieldLooseKey("observeSubscribe");

  var _signViaSigner = /*#__PURE__*/_classPrivateFieldLooseKey("signViaSigner");

  var _updateSigner = /*#__PURE__*/_classPrivateFieldLooseKey("updateSigner");

  class Submittable extends ExtrinsicBase {
    constructor(registry, extrinsic) {
      super(registry, extrinsic, {
        version: api.extrinsicType
      });
      Object.defineProperty(this, _ignoreStatusCb, {
        writable: true,
        value: void 0
      });
      Object.defineProperty(this, _transformResult, {
        writable: true,
        value: identity
      });
      Object.defineProperty(this, _observeSign, {
        writable: true,
        value: (account, partialOptions) => {
          const address = isKeyringPair(account) ? account.address : account.toString();
          const options = optionsOrNonce(partialOptions);
          let updateId;
          return api.derive.tx.signingInfo(address, options.nonce, options.era).pipe(first(), mergeMap(async signingInfo => {
            const eraOptions = makeEraOptions(api, this.registry, options, signingInfo);

            if (isKeyringPair(account)) {
              this.sign(account, eraOptions);
            } else {
              updateId = await _classPrivateFieldLooseBase(this, _signViaSigner)[_signViaSigner](address, eraOptions, signingInfo.header);
            }
          }), mapTo(updateId));
        }
      });
      Object.defineProperty(this, _observeStatus, {
        writable: true,
        value: (hash, status) => {
          if (!status.isFinalized && !status.isInBlock) {
            return of(_classPrivateFieldLooseBase(this, _transformResult)[_transformResult](new SubmittableResult({
              status
            })));
          }

          const blockHash = status.isInBlock ? status.asInBlock : status.asFinalized;
          return api.derive.tx.events(blockHash).pipe(map(({
            block,
            events
          }) => _classPrivateFieldLooseBase(this, _transformResult)[_transformResult](new SubmittableResult({
            events: filterEvents(hash, block, events, status),
            status
          }))), catchError(internalError => of(_classPrivateFieldLooseBase(this, _transformResult)[_transformResult](new SubmittableResult({
            internalError,
            status
          })))));
        }
      });
      Object.defineProperty(this, _observeSend, {
        writable: true,
        value: (updateId = -1) => {
          return api.rpc.author.submitExtrinsic(this).pipe(tap(hash => {
            _classPrivateFieldLooseBase(this, _updateSigner)[_updateSigner](updateId, hash);
          }));
        }
      });
      Object.defineProperty(this, _observeSubscribe, {
        writable: true,
        value: (updateId = -1) => {
          const hash = this.hash;
          return api.rpc.author.submitAndWatchExtrinsic(this).pipe(switchMap(status => _classPrivateFieldLooseBase(this, _observeStatus)[_observeStatus](hash, status)), tap(status => {
            _classPrivateFieldLooseBase(this, _updateSigner)[_updateSigner](updateId, status);
          }));
        }
      });
      Object.defineProperty(this, _signViaSigner, {
        writable: true,
        value: async (address, options, header) => {
          const signer = options.signer || api.signer;
          assert(signer, 'No signer specified, either via api.setSigner or via sign options. You possibly need to pass through an explicit keypair for the origin so it can be used for signing.');
          const payload = this.registry.createType('SignerPayload', objectSpread({}, options, {
            address,
            blockNumber: header ? header.number : 0,
            method: this.method
          }));
          let result;

          if (signer.signPayload) {
            result = await signer.signPayload(payload.toPayload());
          } else if (signer.signRaw) {
            result = await signer.signRaw(payload.toRaw());
          } else {
            throw new Error('Invalid signer interface, it should implement either signPayload or signRaw (or both)');
          } // Here we explicitly call `toPayload()` again instead of working with an object
          // (reference) as passed to the signer. This means that we are sure that the
          // payload data is not modified from our inputs, but the signer


          super.addSignature(address, result.signature, payload.toPayload());
          return result.id;
        }
      });
      Object.defineProperty(this, _updateSigner, {
        writable: true,
        value: (updateId, status) => {
          if (updateId !== -1 && api.signer && api.signer.update) {
            api.signer.update(updateId, status);
          }
        }
      });
      _classPrivateFieldLooseBase(this, _ignoreStatusCb)[_ignoreStatusCb] = apiType === 'rxjs';
    } // dry run an extrinsic


    dryRun(account, optionsOrHash) {
      if (isString(optionsOrHash) || isU8a(optionsOrHash)) {
        // eslint-disable-next-line @typescript-eslint/no-unsafe-return
        return decorateMethod(() => api.rpc.system.dryRun(this.toHex(), optionsOrHash));
      } // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call


      return decorateMethod(() => _classPrivateFieldLooseBase(this, _observeSign)[_observeSign](account, optionsOrHash).pipe(switchMap(() => api.rpc.system.dryRun(this.toHex()))))();
    } // calculate the payment info for this transaction (if signed and submitted)


    paymentInfo(account, optionsOrHash) {
      if (isString(optionsOrHash) || isU8a(optionsOrHash)) {
        // eslint-disable-next-line @typescript-eslint/no-unsafe-return
        return decorateMethod(() => api.rpc.payment.queryInfo(this.toHex(), optionsOrHash));
      }

      const [allOptions] = makeSignAndSendOptions(optionsOrHash);
      const address = isKeyringPair(account) ? account.address : account.toString(); // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call

      return decorateMethod(() => api.derive.tx.signingInfo(address, allOptions.nonce, allOptions.era).pipe(first(), switchMap(signingInfo => {
        // setup our options (same way as in signAndSend)
        const eraOptions = makeEraOptions(api, this.registry, allOptions, signingInfo);
        const signOptions = makeSignOptions(api, eraOptions, {});
        this.signFake(address, signOptions);
        return api.rpc.payment.queryInfo(this.toHex());
      })))();
    } // send with an immediate Hash result


    // send implementation for both immediate Hash and statusCb variants
    send(statusCb) {
      const isSubscription = api.hasSubscriptions && (_classPrivateFieldLooseBase(this, _ignoreStatusCb)[_ignoreStatusCb] || !!statusCb); // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call

      return decorateMethod(isSubscription ? _classPrivateFieldLooseBase(this, _observeSubscribe)[_observeSubscribe] : _classPrivateFieldLooseBase(this, _observeSend)[_observeSend])(statusCb);
    }
    /**
     * @description Sign a transaction, returning the this to allow chaining, i.e. .sign(...).send(). When options, e.g. nonce/blockHash are not specified, it will be inferred. To retrieve eg. nonce use `signAsync` (the preferred interface, this is provided for backwards compatibility)
     * @deprecated
     */


    sign(account, partialOptions) {
      super.sign(account, makeSignOptions(api, optionsOrNonce(partialOptions), {}));
      return this;
    }
    /**
     * @description Signs a transaction, returning `this` to allow chaining. E.g.: `sign(...).send()`. Like `.signAndSend` this will retrieve the nonce and blockHash to send the tx with.
     */


    signAsync(account, partialOptions) {
      // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call
      return decorateMethod(() => _classPrivateFieldLooseBase(this, _observeSign)[_observeSign](account, partialOptions).pipe(mapTo(this)))();
    } // signAndSend with an immediate Hash result


    // signAndSend implementation for all 3 cases above
    signAndSend(account, partialOptions, optionalStatusCb) {
      const [options, statusCb] = makeSignAndSendOptions(partialOptions, optionalStatusCb);
      const isSubscription = api.hasSubscriptions && (_classPrivateFieldLooseBase(this, _ignoreStatusCb)[_ignoreStatusCb] || !!statusCb); // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call

      return decorateMethod(() => _classPrivateFieldLooseBase(this, _observeSign)[_observeSign](account, options).pipe(switchMap(updateId => isSubscription ? _classPrivateFieldLooseBase(this, _observeSubscribe)[_observeSubscribe](updateId) : _classPrivateFieldLooseBase(this, _observeSend)[_observeSend](updateId))) // FIXME This is wrong, SubmittableResult is _not_ a codec
      )(statusCb);
    } // adds a transform to the result, applied before result is returned


    withResultTransform(transform) {
      _classPrivateFieldLooseBase(this, _transformResult)[_transformResult] = transform;
      return this;
    }

  }

  return Submittable;
}