"use strict";
/* eslint-disable @typescript-eslint/no-var-requires */
// Copyright 2017-2020 @polkadot/types authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fromMetadata_1 = __importDefault(require("../../../metadata/Decorated/extrinsics/fromMetadata"));
const util_1 = require("@polkadot/util");
const Raw_1 = __importDefault(require("../Raw"));
const Event_1 = require("../../primitive/Generic/Event");
const signedExtensions_1 = require("../../primitive/Extrinsic/signedExtensions");
const createClass_1 = require("./createClass");
const getTypeClass_1 = require("./getTypeClass");
const getTypeDef_1 = require("./getTypeDef");
// create error mapping from metadata
function decorateErrors(_, metadata, metadataErrors) {
    // decorate the errors
    metadata.asLatest.modules.forEach((section, sectionIndex) => {
        const sectionName = util_1.stringCamelCase(section.name.toString());
        section.errors.forEach(({ name, documentation }, index) => {
            const eventIndex = new Uint8Array([sectionIndex, index]);
            metadataErrors[util_1.u8aToHex(eventIndex)] = {
                documentation: documentation.map((doc) => doc.toString()),
                index,
                name: name.toString(),
                section: sectionName
            };
        });
    });
}
// create event classes from metadata
function decorateEvents(registry, metadata, metadataEvents) {
    // decorate the events
    metadata.asLatest.modules
        .filter(({ events }) => events.isSome)
        .forEach((section, sectionIndex) => {
        const sectionName = util_1.stringCamelCase(section.name.toString());
        section.events.unwrap().forEach((meta, methodIndex) => {
            const methodName = meta.name.toString();
            const eventIndex = new Uint8Array([sectionIndex, methodIndex]);
            const typeDef = meta.args.map((arg) => getTypeDef_1.getTypeDef(arg.toString()));
            let Types = [];
            try {
                Types = typeDef.map((typeDef) => getTypeClass_1.getTypeClass(registry, typeDef));
            }
            catch (error) {
                console.error(error);
            }
            metadataEvents[util_1.u8aToHex(eventIndex)] = class extends Event_1.EventData {
                constructor(registry, value) {
                    super(registry, Types, value, typeDef, meta, sectionName, methodName);
                }
            };
        });
    });
}
// create extrinsic mapping from metadata
function decorateExtrinsics(registry, metadata, metadataCalls) {
    const extrinsics = fromMetadata_1.default(registry, metadata);
    // decorate the extrinsics
    Object.values(extrinsics).forEach((methods) => Object.values(methods).forEach((method) => {
        metadataCalls[util_1.u8aToHex(method.callIndex)] = method;
    }));
}
class TypeRegistry {
    constructor() {
        this._classes = new Map();
        this._definitions = new Map();
        this._metadataCalls = {};
        this._metadataErrors = {};
        this._metadataEvents = {};
        this._metadataExtensions = signedExtensions_1.defaultExtensions;
        // we only want to import these on creation, i.e. we want to avoid types
        // weird side-effects from circular references. (Since registry is injected
        // into types, this can  be a real concern now)
        const baseTypes = require('../../index.types');
        const definitions = require('../../interfaces/definitions');
        // since these are classes, they are injected first
        this.register(Object.assign({ Raw: Raw_1.default }, baseTypes));
        // since these are definitions, they would only get created when needed
        Object.values(definitions).forEach(({ types }) => this.register(types));
    }
    findMetaCall(callIndex) {
        const hexIndex = util_1.u8aToHex(callIndex);
        const fn = this._metadataCalls[hexIndex];
        util_1.assert(!util_1.isUndefined(fn), `findMetaCall: Unable to find Call with index ${hexIndex}/[${callIndex}]`);
        return fn;
    }
    findMetaError(errorIndex) {
        const hexIndex = util_1.u8aToHex(errorIndex);
        const error = this._metadataErrors[hexIndex];
        util_1.assert(!util_1.isUndefined(error), `findMetaError: Unable to find Error with index ${hexIndex}/[${errorIndex}]`);
        return error;
    }
    findMetaEvent(eventIndex) {
        const hexIndex = util_1.u8aToHex(eventIndex);
        const Event = this._metadataEvents[hexIndex];
        util_1.assert(!util_1.isUndefined(Event), `findMetaEvent: Unable to find Event with index ${hexIndex}/[${eventIndex}]`);
        return Event;
    }
    get(name) {
        let Type = this._classes.get(name);
        // we have not already created the type, attempt it
        if (!Type) {
            const definition = this._definitions.get(name);
            // we have a definition, so create the class now (lazily)
            if (definition) {
                const BaseType = createClass_1.createClass(this, definition);
                // NOTE If we didn't extend here, we would have strange artifacts. An example is
                // Balance, with this, new Balance() instanceof u128 is true, but Balance !== u128
                // Additionally, we now pass through the registry, which is a link to ourselves
                Type = class extends BaseType {
                };
                this._classes.set(name, Type);
            }
        }
        return Type;
    }
    getDefinition(name) {
        return this._definitions.get(name);
    }
    getOrThrow(name, msg) {
        const type = this.get(name);
        if (util_1.isUndefined(type)) {
            throw new Error(msg || `type ${name} not found`);
        }
        return type;
    }
    getSignedExtensionExtra() {
        return signedExtensions_1.expandExtensionTypes(this._metadataExtensions, 'extra');
    }
    getSignedExtensionTypes() {
        return signedExtensions_1.expandExtensionTypes(this._metadataExtensions, 'types');
    }
    hasClass(name) {
        return this._classes.has(name);
    }
    hasDef(name) {
        return this._definitions.has(name);
    }
    hasType(name) {
        return this.hasClass(name) || this.hasDef(name);
    }
    // eslint-disable-next-line no-dupe-class-members
    register(arg1, arg2) {
        // NOTE Constructors appear as functions here
        if (util_1.isFunction(arg1)) {
            this._classes.set(arg1.name, arg1);
        }
        else if (util_1.isString(arg1)) {
            util_1.assert(util_1.isFunction(arg2), `Expected class definition passed to '${arg1}' registration`);
            this._classes.set(arg1, arg2);
        }
        else {
            this.registerObject(arg1);
        }
    }
    registerObject(obj) {
        Object.entries(obj).forEach(([name, type]) => {
            if (util_1.isFunction(type)) {
                // This _looks_ a bit funny, but `typeof Clazz === 'function'
                this._classes.set(name, type);
            }
            else {
                const def = util_1.isString(type)
                    ? type
                    : JSON.stringify(type);
                // we already have this type, remove the classes registered for it
                if (this._classes.has(name)) {
                    this._classes.delete(name);
                }
                this._definitions.set(name, def);
            }
        });
    }
    // sets the metadata
    setMetadata(metadata) {
        decorateExtrinsics(this, metadata, this._metadataCalls);
        decorateErrors(this, metadata, this._metadataErrors);
        decorateEvents(this, metadata, this._metadataEvents);
        // setup the available extensions
        this._metadataExtensions = metadata.asLatest.extrinsic.version.gtn(0)
            ? metadata.asLatest.extrinsic.signedExtensions.map((key) => key.toString())
            : signedExtensions_1.defaultExtensions;
        const unknown = signedExtensions_1.findUnknownExtensions(this._metadataExtensions);
        if (unknown.length) {
            console.warn(`Unknown signed extensions [${unknown.join(', ')}] found, treating them as no-efect`);
        }
    }
}
exports.TypeRegistry = TypeRegistry;
