"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const errors_1 = require("@atomicfinance/errors");
const ajv_1 = __importDefault(require("ajv"));
const lodash_1 = require("lodash");
const Cfd_1 = __importDefault(require("./Cfd"));
const Chain_1 = __importDefault(require("./Chain"));
const Dlc_1 = __importDefault(require("./Dlc"));
const schema_1 = require("./schema");
const Wallet_1 = __importDefault(require("./Wallet"));
class Client {
    /**
     * Client
     */
    constructor(provider, version) {
        /**
         * @type {Array}
         */
        this._providers = [];
        /**
         * @type {string}
         */
        this.version = version;
        if (provider) {
            this.addProvider(provider);
        }
        const ajv = new ajv_1.default();
        this.validateTransaction = ajv.compile(schema_1.Transaction);
        this.validateBlock = ajv.compile(schema_1.Block);
        this._chain = new Chain_1.default(this);
        this._dlc = new Dlc_1.default(this);
        this._cfd = new Cfd_1.default(this);
        this._wallet = new Wallet_1.default(this);
        this.identifier = 'Client';
    }
    /**
     * Add a provider
     * @param {!Provider} provider - The provider instance or RPC connection string
     * @return {Client} Returns instance of Client
     * @throws {InvalidProviderError} When invalid provider is provider
     * @throws {DuplicateProviderError} When same provider is added again
     */
    addProvider(provider) {
        if (!(0, lodash_1.isFunction)(provider.setClient)) {
            throw new errors_1.InvalidProviderError('Provider should have "setClient" method');
        }
        const duplicate = (0, lodash_1.find)(this._providers, (_provider) => provider.constructor === _provider.constructor);
        if (duplicate) {
            throw new errors_1.DuplicateProviderError('Duplicate provider');
        }
        provider.setClient(this);
        this._providers.push(provider);
        return this;
    }
    /**
     * Check the availability of a method.
     * @param {!string} method - Name of the method to look for in the provider stack
     * @param {boolean|object} [requestor=false] - If provided, it returns providers only
     *  above the requestor in the stack.
     * @return {Provider} Returns a provider instance associated with the requested method
     * @throws {NoProviderError} When no provider is available in the stack.
     * @throws {UnimplementedMethodError} When the requested method is not provided
     *  by any provider above requestor in the provider stack
     * @throws {UnsupportedMethodError} When requested method is not supported by
     *  version specified
     */
    getProviderForMethod(method, requestor = false) {
        if (this._providers.length === 0) {
            throw new errors_1.NoProviderError('No provider provided. Add a provider to the client');
        }
        let indexOfRequestor = requestor
            ? (0, lodash_1.findLastIndex)(this._providers, (provider) => requestor.constructor === provider.constructor)
            : this._providers.length;
        if (indexOfRequestor === -1)
            indexOfRequestor = 0;
        const provider = (0, lodash_1.findLast)(this._providers, 
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (provider) => (0, lodash_1.isFunction)(provider[method]), indexOfRequestor - 1);
        if (provider == null) {
            throw new errors_1.UnimplementedMethodError(`Unimplemented method "${method}"`);
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        if ((0, lodash_1.isFunction)(provider._checkMethodVersionSupport)) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            if (!provider._checkMethodVersionSupport(method, this.version)) {
                throw new errors_1.UnsupportedMethodError(`Method "${method}" is not supported by version "${this.version}"`);
            }
        }
        return provider;
    }
    /**
     * Helper method that returns method from a provider.
     * @param {!string} method - Name of the method to look for in the provider stack
     * @param {object} [requestor] - If provided, it returns method from providers only
     *  above the requestor in the stack.
     * @return {function} Returns method from provider instance associated with the requested method
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    getMethod(method, requestor) {
        const provider = this.getProviderForMethod(method, requestor);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return provider[method].bind(provider);
    }
    assertValidTransaction(transaction) {
        if (!this.validateTransaction(transaction)) {
            const { errors } = this.validateTransaction;
            throw new errors_1.InvalidProviderResponseError(`Provider returned an invalid transaction, "${errors[0].dataPath}" ${errors[0].message}`);
        }
    }
    assertValidBlock(block) {
        if (!this.validateBlock(block)) {
            const { errors } = this.validateBlock;
            throw new errors_1.InvalidProviderResponseError(`Provider returned an invalid block, "${errors[0].dataPath}" ${errors[0].message}`);
        }
    }
    get chain() {
        return this._chain;
    }
    get dlc() {
        return this._dlc;
    }
    get cfd() {
        return this._cfd;
    }
    get wallet() {
        return this._wallet;
    }
}
exports.default = Client;
//# sourceMappingURL=Client.js.map