"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.
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("./types");
const util_1 = require("@polkadot/util");
const sanitize_1 = __importDefault(require("./sanitize"));
const typeSplit_1 = require("./typeSplit");
// decode an enum of either of the following forms
//  { _enum: ['A', 'B', 'C'] }
//  { _enum: { A: AccountId, B: Balance, C: u32 } }
function _decodeEnum(value, details) {
    value.info = types_1.TypeDefInfo.Enum;
    // not as pretty, but remain compatible with oo7 for both struct and Array types
    value.sub = Array.isArray(details)
        ? details.map((name) => ({
            info: types_1.TypeDefInfo.Plain,
            name,
            type: 'Null'
        }))
        : Object.entries(details).map(([name, type]) => 
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        getTypeDef(type || 'Null', { name }));
    return value;
}
// decode a set of the form
//   { _set: { A: 0b0001, B: 0b0010, C: 0b0100 } }
function _decodeSet(value, details) {
    value.info = types_1.TypeDefInfo.Set;
    value.sub = Object.entries(details).map(([name, index]) => ({
        index,
        info: types_1.TypeDefInfo.Plain,
        name,
        type: name
    }));
    return value;
}
// decode a struct, set or enum
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function _decodeStruct(value, type, _) {
    const parsed = JSON.parse(type);
    const keys = Object.keys(parsed);
    if (keys.length === 1 && keys[0] === '_enum') {
        return _decodeEnum(value, parsed[keys[0]]);
    }
    else if (keys.length === 1 && keys[0] === '_set') {
        return _decodeSet(value, parsed[keys[0]]);
    }
    value.sub = keys.map((name) => 
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    getTypeDef(parsed[name], { name }));
    return value;
}
// decode a fixed vector, e.g. [u8;32]
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function _decodeFixedVec(value, type, _) {
    const [vecType, _vecLen] = type.substr(1, type.length - 2).split(';');
    const vecLen = parseInt(_vecLen.trim(), 10);
    // as a first round, only u8 via u8aFixed, we can add more support
    util_1.assert(vecLen <= 256, `${type}: Only support for [Type; <length>], where length <= 256`);
    value.ext = { length: vecLen, type: vecType };
    return value;
}
// decode a tuple
function _decodeTuple(value, _, subType) {
    value.sub = subType.length === 0
        ? []
        : typeSplit_1.typeSplit(subType).map((inner) => 
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        getTypeDef(inner));
    return value;
}
function hasWrapper(type, [start, end]) {
    if (type.substr(0, start.length) !== start) {
        return false;
    }
    util_1.assert(type.endsWith(end), `Expected '${start}' closing with '${end}' on ${type}`);
    return true;
}
const nestedExtraction = [
    ['[', ']', types_1.TypeDefInfo.VecFixed, _decodeFixedVec],
    ['{', '}', types_1.TypeDefInfo.Struct, _decodeStruct],
    ['(', ')', types_1.TypeDefInfo.Tuple, _decodeTuple],
    // the inner for these are the same as tuple, multiple values
    ['BTreeMap<', '>', types_1.TypeDefInfo.BTreeMap, _decodeTuple],
    ['Result<', '>', types_1.TypeDefInfo.Result, _decodeTuple]
];
const wrappedExtraction = [
    ['BTreeSet<', '>', types_1.TypeDefInfo.BTreeSet],
    ['Compact<', '>', types_1.TypeDefInfo.Compact],
    ['Linkage<', '>', types_1.TypeDefInfo.Linkage],
    ['Option<', '>', types_1.TypeDefInfo.Option],
    ['Vec<', '>', types_1.TypeDefInfo.Vec]
];
function extractSubType(type, [start, end]) {
    return type.substr(start.length, type.length - start.length - end.length);
}
function getTypeDef(_type, { name, displayName } = {}) {
    // create the type via Type, allowing types to be sanitized
    const type = sanitize_1.default(_type);
    const value = { info: types_1.TypeDefInfo.Plain, displayName, name, type };
    const nested = nestedExtraction.find((nested) => hasWrapper(type, nested));
    if (nested) {
        value.info = nested[2];
        return nested[3](value, type, extractSubType(type, nested));
    }
    const wrapped = wrappedExtraction.find((wrapped) => hasWrapper(type, wrapped));
    if (wrapped) {
        value.info = wrapped[2];
        value.sub = getTypeDef(extractSubType(type, wrapped));
    }
    return value;
}
exports.getTypeDef = getTypeDef;
