"use strict";
// 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.
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("../types");
const util_1 = require("@polkadot/util");
exports.SPECIAL_TYPES = ['AccountId', 'AccountIndex', 'Address', 'Balance'];
const identity = (value) => value;
function paramsNotation(outer, inner, transform = identity) {
    let arrayStr = '';
    if (inner) {
        arrayStr = '<' + (Array.isArray(inner) ? inner : [inner]).map(transform).join(', ') + '>';
    }
    return `${outer}${arrayStr}`;
}
exports.paramsNotation = paramsNotation;
function encodeWithParams(typeDef, outer = typeDef.displayName || typeDef.type) {
    const { info, sub, params } = typeDef;
    switch (info) {
        case types_1.TypeDefInfo.BTreeMap:
        case types_1.TypeDefInfo.BTreeSet:
        case types_1.TypeDefInfo.Compact:
        case types_1.TypeDefInfo.Linkage:
        case types_1.TypeDefInfo.Option:
        case types_1.TypeDefInfo.Result:
        case types_1.TypeDefInfo.Vec:
            return paramsNotation(outer, params || sub, 
            // eslint-disable-next-line @typescript-eslint/no-use-before-define
            (param) => displayType(param));
        default:
            return outer;
    }
}
function encodeSubTypes(sub, asEnum) {
    const inner = sub.reduce((result, type) => {
        return Object.assign(Object.assign({}, result), { 
            // eslint-disable-next-line @typescript-eslint/no-use-before-define
            [type.name]: encodeType(type) });
    }, {});
    return JSON.stringify(asEnum
        ? { _enum: inner }
        : inner);
}
function encodeEnum(typeDef) {
    util_1.assert(typeDef.sub && Array.isArray(typeDef.sub), 'Unable to encode Enum type');
    const sub = typeDef.sub;
    const isClikeEnum = sub.reduce((bool, { type }) => bool && type === 'Null', true);
    if (isClikeEnum) {
        return `[${sub
            .map(({ name }) => `"${name}"`)
            .join(', ')}]`;
    }
    return encodeSubTypes(sub, true);
}
function encodeStruct(typeDef) {
    util_1.assert(typeDef.sub && Array.isArray(typeDef.sub), 'Unable to encode Struct type');
    const sub = typeDef.sub;
    return encodeSubTypes(sub);
}
function encodeTuple(typeDef) {
    util_1.assert(typeDef.sub && Array.isArray(typeDef.sub), 'Unable to encode Tuple type');
    const sub = typeDef.sub;
    return `(${sub
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        .map((type) => encodeType(type))
        .join(', ')})`;
}
function encodeVecFixed(typeDef) {
    util_1.assert(typeDef.ext, 'Unable to encode VecFixed type');
    const { type, length } = typeDef.ext;
    return `[${type};${length}]`;
}
// We setup a record here to ensure we have comprehensive coverage (any item not covered will result
// in a compile-time error with the missing index)
const encoders = {
    [types_1.TypeDefInfo.BTreeMap]: (typeDef) => encodeWithParams(typeDef, 'BTreeMap'),
    [types_1.TypeDefInfo.BTreeSet]: (typeDef) => encodeWithParams(typeDef, 'BTreeSet'),
    [types_1.TypeDefInfo.Compact]: (typeDef) => encodeWithParams(typeDef, 'Compact'),
    [types_1.TypeDefInfo.Enum]: (typeDef) => encodeEnum(typeDef),
    [types_1.TypeDefInfo.Linkage]: (typeDef) => encodeWithParams(typeDef, 'Linkage'),
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    [types_1.TypeDefInfo.Null]: (typeDef) => 'Null',
    [types_1.TypeDefInfo.Option]: (typeDef) => encodeWithParams(typeDef, 'Option'),
    [types_1.TypeDefInfo.Plain]: (typeDef) => typeDef.displayName || typeDef.type,
    [types_1.TypeDefInfo.Result]: (typeDef) => encodeWithParams(typeDef, 'Result'),
    [types_1.TypeDefInfo.Set]: (typeDef) => typeDef.type,
    [types_1.TypeDefInfo.Struct]: (typeDef) => encodeStruct(typeDef),
    [types_1.TypeDefInfo.Tuple]: (typeDef) => encodeTuple(typeDef),
    [types_1.TypeDefInfo.Vec]: (typeDef) => encodeWithParams(typeDef, 'Vec'),
    [types_1.TypeDefInfo.VecFixed]: (typeDef) => encodeVecFixed(typeDef)
};
function encodeType(typeDef) {
    const encoder = encoders[typeDef.info];
    util_1.assert(encoder, `Cannot encode type: ${typeDef}.`);
    return encoder(typeDef);
}
exports.encodeType = encodeType;
function displayType(typeDef) {
    if (typeDef.displayName) {
        return encodeWithParams(typeDef);
    }
    switch (typeDef.info) {
        case types_1.TypeDefInfo.Struct:
        case types_1.TypeDefInfo.Enum:
            return encodeWithParams(typeDef);
        default:
            return encodeType(typeDef);
    }
}
exports.displayType = displayType;
function withTypeString(typeDef) {
    const type = exports.SPECIAL_TYPES.includes(typeDef.displayName)
        ? typeDef.displayName
        : encodeType(typeDef);
    return Object.assign(Object.assign({}, typeDef), { type });
}
exports.withTypeString = withTypeString;
