'use strict';

var utils_assert = require('./assert.cjs');
require('./error.cjs');

/**
 * Attempts to call `value.toJSON()`, returns `JSON.parse(JSON.stringify(value))` if method doesn't exist.
 * @template T
 * @param {T} value
 * @returns {object}
 */
const maybeToJSON = (value) => (typeof value?.toJSON === 'function'
  ? value.toJSON()
  : JSON.parse(JSON.stringify(value)));

/**
 * Stringifies the provided value converted to JSON.
 * @template T
 * @param {T} value
 * @returns {string}
 */
const maybeToJSONString = (value) => JSON.stringify(maybeToJSON(value));

/**
 * Attempts to convert provided value to the Cheqd Payload or JSON.
 * @param {*} value
 * @returns {object}
 */
const maybeToCheqdPayloadOrJSON = (obj) => (typeof obj?.toCheqdPayload === 'function' // eslint-disable-line no-nested-ternary
  ? obj.toCheqdPayload()
  : typeof obj?.apply === 'function' // eslint-disable-line no-nested-ternary
    ? obj.apply(maybeToCheqdPayloadOrJSON)
    : typeof value !== 'object' && typeof value !== 'function'
      ? obj
      : maybeToJSON(obj));

/**
 * Returns bytes of the value converted to a stringified JSON.
 * @template T
 * @param {T} value
 * @returns {string}
 */
const maybeToJSONStringBytes = (value) => (typeof value?.maybeToJSONStringBytes === 'function'
  ? value.toJSONStringBytes()
  : Uint8Array.from(Buffer.from(maybeToJSONString(value))));

/**
 * Attempts to compare two values using `value.eq(other)`, returns `boolean`.
 * @template T
 * @param {T} value
 * @param {T} other
 * @returns {boolean}
 */
const maybeEq = (value, other) => (typeof value?.eq === 'function' ? value.eq(other) : value === other);

/**
 * Attempts to call `value.toHuman()` or `value.toJSON`, returns `value` if methods don't exist.
 * @template T
 * @param {T} value
 * @returns {object}
 */
const maybeToHuman = (obj) => (obj && typeof obj.toHuman === 'function' ? obj.toHuman() : maybeToJSON(obj));

/**
 * Attempts to call `value.toNumber()`, returns `+value` if method doesn't exist.
 * @template T
 * @param {T} value
 * @returns {number}
 */
const maybeToNumber = (value) => (typeof value?.toNumber === 'function' ? value.toNumber() : +value);

/**
 * Marks function that it can't be used as a constructor.
 */
const NotAConstructor = Symbol.for(
  '@docknetwork/credential-sdk/NotAConstructor',
);

/**
 * Attempts to intantiate new object of the supplied class using provided arguments.
 * @param Class
 * @param args
 */
const maybeNew = (Class, args) => (!Class[NotAConstructor] ? new Class(...args) : Class.apply(Class, args));

/**
 * Attempts to create new instance of the supplied class using `Class.from(obj)`, instantiates class if `from` doesn't exist.
 * @param Class
 * @param args
 */
const maybeFrom = (klass, obj) => (typeof klass.from === 'function' ? klass.from(obj) : maybeNew(klass, [obj]));

/**
 * Applies function to the first value of the provided object that passes the check.
 * @template T
 * @template I
 * @template O
 * @param {function(I): boolean} check
 * @param {function(I): O} fn
 * @param {T} value
 * @returns {O}
 */
const applyToValue = (check, fn, value) => {
  if (check(value)) {
    return fn(value);
  } else if (typeof value?.apply === 'function') {
    let res;
    utils_assert.MustBeCalledOnce.ensure(
      (obj) => {
        res = applyToValue(check, fn, obj);
      },
      (wrapped) => value.apply(wrapped),
    );
    return res;
  }

  throw new Error("`fn` can't be applied because value didn't pass the check");
};

exports.NotAConstructor = NotAConstructor;
exports.applyToValue = applyToValue;
exports.maybeEq = maybeEq;
exports.maybeFrom = maybeFrom;
exports.maybeNew = maybeNew;
exports.maybeToCheqdPayloadOrJSON = maybeToCheqdPayloadOrJSON;
exports.maybeToHuman = maybeToHuman;
exports.maybeToJSON = maybeToJSON;
exports.maybeToJSONString = maybeToJSONString;
exports.maybeToJSONStringBytes = maybeToJSONStringBytes;
exports.maybeToNumber = maybeToNumber;
//# sourceMappingURL=interfaces.cjs.map
